diff --git a/.github/dependabot.yml b/.github/dependabot.yml index dab5e766..612ef038 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,6 +7,7 @@ version: 2 updates: - package-ecosystem: 'npm' # See documentation for possible values directory: '/' # Location of package manifests + target-branch: 'develop' versioning-strategy: increase-if-necessary schedule: interval: 'weekly' @@ -26,6 +27,7 @@ updates: - package-ecosystem: 'github-actions' directory: '/' + target-branch: 'develop' schedule: interval: 'weekly' cooldown: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df995b1d..d9dd4939 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,9 +6,9 @@ permissions: on: workflow_dispatch: push: - branches: [main] + branches: [main, develop] pull_request: - branches: [main] + branches: [main, develop] env: NPM_CONFIG_IGNORE_SCRIPTS: true @@ -20,6 +20,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - run: npm i - run: npm run lint + - run: npm run format:check test: runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5c7791f4..c24be2d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,6 @@ jobs: node-version: 24 registry-url: https://registry.npmjs.org/ - name: run tests - # REVISIT: remove "npm explore better-sqlite3 -- npm run install" with cds^10 run: | npm i -g @sap/cds-dk npm i diff --git a/.oxfmtrc.jsonc b/.oxfmtrc.jsonc new file mode 100644 index 00000000..b24ae03d --- /dev/null +++ b/.oxfmtrc.jsonc @@ -0,0 +1,21 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "arrowParens": "avoid", + "bracketSpacing": true, + "embeddedLanguageFormatting": "auto", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxSingleQuote": false, + "printWidth": 120, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false, + "vueIndentScriptAndStyle": false, + "sortPackageJson": false, + "ignorePatterns": ["*.md", "node_modules/**", "package-lock.json"] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 72acf229..e598b9ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). The format is based on [Keep a Changelog](http://keepachangelog.com/). +## Version 2.1.0 - tbd + +### Added + +- Support for `telemetry-to-caas` kind for CaaS (Collector as a Service). By default, mTLS certificates are provisioned and rotated automatically via the Zero Trust Identity (ZTI) sidecar; alternatively, mTLS certificates can be provided manually via `x509` credentials. +- Queue worker transactions are traced as coherent ` - tx` spans under the `cds.spawn - run task` root, instead of orphaned per-call spans +- The span processor is now configurable via `cds.requires.telemetry.tracing.processor = { kind, config? }` (`BatchSpanProcessor` or `SimpleSpanProcessor`); defaults to `BatchSpanProcessor`, and to `SimpleSpanProcessor` in the `[development]` profile +- Exported log records now carry the tenant as attribute `sap.tenancy.tenant_id` (when available from `cds.context`), aligning log metadata with traces and metrics + +### Changed + +### Fixed + +- No longer crashes on startup when Dynatrace OneAgent is active (`DT_NODE_PRELOAD_OPTIONS` set, kind `*-to-dynatrace`, no `@opentelemetry/exporter-trace-otlp-proto` dependency): the tracing factory no longer builds a tracer provider with an undefined span processor (which crashed on the first span in `MultiSpanProcessor.onStart`). On this path it now registers a recording tracer provider without an exporter, so the CDS spans exist for OneAgent's in-process OpenTelemetry capture to pick up (no export by us, no duplicates) +- Logging no longer recurses through `@opentelemetry/sdk-logs` 0.221's export path: the log-processor construction now adapts to the installed sdk-logs version (0.221+ takes an `{ exporter }` options object, earlier versions the positional exporter), and a re-entrancy guard was added to the `cds.log.format` interception +- Cloud SDK outbound requests are traced again (patch getter-only `@sap-cloud-sdk/http-client` exports via `Object.defineProperty`) +- Raw SQL no longer leaks into HANA INSERT `prepare` span names (now uses operation + table, matching SELECT) +- Queue `*_storage_time_in_seconds` metrics are now correct on HANA (timezone-naive `min`/`max` timestamp aggregates were parsed as local time, skewing the values by the machine's UTC offset) + ## Version 2.0.1 - 2026-07-03 ### Fixed diff --git a/README.md b/README.md index 6843b802..12dff245 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,14 @@ Documentation can be found at [cap.cloud.sap](https://cap.cloud.sap/docs) and [o - [`telemetry-to-cloud-logging`](#telemetry-to-cloud-logging) - [`telemetry-to-jaeger`](#telemetry-to-jaeger) - [`telemetry-to-otlp`](#telemetry-to-otlp) +- [Running with SAP Cloud ALM (beta)](#running-with-sap-cloud-alm-beta) - [Detailed Configuration Options](#detailed-configuration-options) - [Configuration Pass Through](#configuration-pass-through) - [Resource Attributes](#resource-attributes) - [Instrumentations](#instrumentations) - [Sampler](#sampler) - [Propagators](#propagators) + - [Span Processor](#span-processor) - [Exporters](#exporters) - [High Resolution Timestamps (beta)](#high-resolution-timestamps-beta) - [Environment Variables](#environment-variables) @@ -94,8 +96,7 @@ Example trace in Dynatrace: An example trace printed to the console can be found in [`telemetry-to-console`](#telemetry-to-console). -In environments where Dynatrace OneAgent is installed (e.g., SAP BTP CF), no OpenTelemetry exporter is needed to transport the traces to Dynatrace. -`@cap-js/telemetry` recognizes this and ignores any exporter config if the predefined kind [`telemetry-to-dynatrace`](#telemetry-to-dynatrace) is used. +To export traces to Dynatrace, use the predefined kind [`telemetry-to-dynatrace`](#telemetry-to-dynatrace). If [Dynatrace OneAgent](#dynatrace-oneagent) is present, the CDS spans can alternatively be captured in-process by OneAgent, without the OTLP trace exporter. ### Metrics @@ -219,7 +220,7 @@ Hence, a Dynatrace instance is required and the app must be bound to that Dynatr Use via `cds.requires.telemetry.kind = 'to-dynatrace'`. Required additional dependencies: -- `@opentelemetry/exporter-trace-otlp-proto` (optional, see [Leveraging Dynatrace OneAgent](#leveraging-dynatrace-oneagent)) +- `@opentelemetry/exporter-trace-otlp-proto` - `@opentelemetry/exporter-metrics-otlp-proto` The necessary scopes for exporting traces (`openTelemetryTrace.ingest`) and metrics (`metrics.ingest`) are not part of the standard `apitoken` and must be requested. @@ -248,16 +249,11 @@ In Dynatrace itself, you need to ensure that the following two features are enab - From the Dynatrace menu, go to Settings > Server-side service monitoring > Deep monitoring > Distributed tracing. - Turn on Send W3C Trace Context HTTP headers. -#### Leveraging Dynatrace OneAgent - -If [Dynatrace OneAgent](https://www.dynatrace.com/platform/oneagent) is present, for example on SAP BTP CF, it will collect and transport the traces created by `@cap-js/telemetry` automatically. -(Your app still needs to be bound to a Dynatrace instance, of course. However, `@dynatrace/oneagent-sdk` is not required.) -Hence, additional dependency `@opentelemetry/exporter-trace-otlp-proto` and scope `openTelemetryTrace.ingest` are not required. +#### Dynatrace OneAgent -Please note, however, that Dynatrace only exports traces triggered by incoming HTTP requests. -That is, traces for background tasks started by `cds.spawn`, for example, would not be exported. +If [Dynatrace OneAgent](https://www.dynatrace.com/platform/oneagent) is present, for example on SAP BTP CF, and the OTLP trace exporter is *not* installed, `@cap-js/telemetry` does not export traces itself. Instead it registers a recording tracer provider without an exporter and relies on OneAgent's in-process OpenTelemetry capture to pick up the CDS spans — no OTLP export, no duplicate spans. -If dependency `@opentelemetry/exporter-trace-otlp-proto` is present anyway, `@cap-js/telemetry` will export the traces via OpenTelemetry as well. +Installing the OTLP trace exporter (`@opentelemetry/exporter-trace-otlp-proto`, see [Required additional dependencies](#telemetry-to-dynatrace) above) takes precedence: `@cap-js/telemetry` then exports the spans itself instead of relying on in-process capture, and the `openTelemetryTrace.ingest` scope must be granted. ### `telemetry-to-cloud-logging` @@ -283,12 +279,13 @@ In order to receive OpenTelemetry credentials in the binding to the SAP Cloud Lo If you are binding your app to SAP Cloud Logging via a [user-provided service instance](https://docs.cloudfoundry.org/devguide/services/user-provided.html), make sure that it has the tag `Cloud Logging`. -> Tip: To add the required tag to an existing user-provided service, you can use: +> Tip: To add the required tag to an existing user-provided service, you can use: > ``` > cf update-user-provided-service {service-name} -t "Cloud Logging" > ``` > For detailed information about binding resolution in CAP, consult [`cds.connect()` → Service Bindings](https://cap.cloud.sap/docs/node.js/cds-connect#service-bindings). + ### `telemetry-to-jaeger` Exports traces to Jaeger. @@ -340,6 +337,16 @@ Please note that `@cap-js/telemetry` does not validate the configuration via env +## Running with SAP Cloud ALM (beta) + +When your application already runs the SAP Cloud ALM agent extension (`@sap/xotel-agent-ext-js`), that agent owns the OpenTelemetry SDK. `@cap-js/telemetry` detects it and, instead of setting up its own tracer provider, contributes its CDS spans to the agent's tracing pipeline (as a delegate on the agent's span processor). Both run on a single, shared OpenTelemetry SDK instance, so the CDS spans appear alongside the data the agent already collects — no second SDK, no duplicate setup. + +This requires `@sap/xotel-agent-ext-js` 2.0.4 or later. For enabling and onboarding the SAP Cloud ALM agent itself, refer to the [SAP Cloud ALM documentation](https://support.sap.com/en/alm/sap-cloud-alm/operations/expert-portal/data-collection-infrastructure.html). + +> Note: this coexistence is currently in beta. Running the SAP Cloud ALM agent together with Dynatrace OneAgent is not supported. + + + ## Detailed Configuration Options @@ -428,6 +435,21 @@ Default: ``` +### Span Processor + +Configure via `cds.requires.telemetry.tracing.processor = { kind, config? }`, where `kind` is one of `BatchSpanProcessor` or `SimpleSpanProcessor` and `config` is passed through to the processor's constructor. + +Default: `BatchSpanProcessor`, except in the `[development]` profile, which uses `SimpleSpanProcessor` so spans are exported immediately. +```json +{ + "kind": "BatchSpanProcessor" +} +``` + +> [!NOTE] +> The `ConsoleSpanExporter` (the default `telemetry-to-console` exporter) has only been tested with the `SimpleSpanProcessor`. Combining it with the `BatchSpanProcessor` is not recommended. + + ### Exporters Configure via: diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 00000000..5678ede2 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,134 @@ +# Testing + +This document is the single, authoritative place for the hard-won, non-obvious knowledge behind the `@cap-js/telemetry` test suite. It is meant for contributors: read it before adding or debugging a test. Test files keep only the rationale that is local to a specific test; anything general or repeated lives here. + +## Running the tests + +```sh +npm test # vitest, sqlite in-memory (the default) +node_modules/.bin/vitest run # same, without the --silent from the npm script +``` + +- **Runner:** [Vitest](https://vitest.dev). Config in [`vitest.config.mjs`](vitest.config.mjs). +- **Default database:** `@cap-js/sqlite`, in-memory. No external services are needed for the default run. +- **Test app:** a small bookshop under [`test/bookshop`](test/bookshop) — CDS model, services, and test-only wiring (exporters/reader, ignore hooks). Each test spins it up with `cds.test(__dirname + '/bookshop', ...)`. +- **CI matrix:** Node 22 & 24 × cds 9 & 10 (see [`.github/workflows/ci.yml`](.github/workflows/ci.yml)). The lint job additionally runs ESLint and `oxfmt --check`. + +Lint / format locally: + +```sh +npx eslint . --max-warnings=0 # npm run lint +npx oxfmt --check # npm run format:check +``` + +## sqlite vs HANA + +The suite runs on two databases, and the difference in DB **isolation model** drives most of the test infrastructure. + +| | sqlite (default / PR CI) | HANA (separate workflow) | +| --- | --- | --- | +| DB per test file | **Own** in-memory DB — each file is fully isolated | **One shared** HDI container across *all* files | +| File parallelism | Full parallelism | **Serial** (`fileParallelism: false`) | +| Timeouts | 42s test / 30s hook | **10×** test timeout | +| Retries | 0 (deterministic) | `retry: 2` (self-heal unlucky timing) | +| Outbox bleed | Impossible (fresh DB) | Must be actively prevented (see below) | + +Because HANA reuses one container for the whole run, a background queue/outbox worker from one file can still be draining when the next file starts and would dispatch leftover rows — adding foreign `cds.spawn - run task` root spans that break exact root-count assertions. The queue/outbox test files therefore: + +- **clear the outbox** in `beforeEach` (before resetting the span buffer, so the `DELETE`'s own spans aren't captured), and +- **settle** in `afterAll`: clear, wait for the last worker iteration, clear again — every clear timeout-bounded via `clearOutbox` so a draining pool can't hang the hook. + +All of this is a **no-op on sqlite** (fresh in-memory DB per file), gated on the HANA signal. + +### How the HANA path is signalled + +- The HANA job runs when `process.env.CI && process.env.HANA_DRIVER` are set. On that path `vitest.config.mjs` raises the timeout, disables file parallelism, enables retries, and excludes the multitenancy suites (see [Sanctioned skips](#sanctioned-skips)). +- It also sets **`process.env.TELEMETRY_TEST_HANA = '1'`** in the config module. Test files that must branch at **collection time** (before `cds.test()` applies its `--profile`) read this env var rather than `cds.env`: reading `cds.env` that early would freeze the env singleton before the profile is applied, so the tracer provider would be built with the wrong exporter and no spans would be captured. +- HANA runs from its **own workflow**, [`.github/workflows/hana.yml`](.github/workflows/hana.yml) — `workflow_dispatch` only, against a protected `hana` environment with a pre-provisioned HDI container. It is **not** part of the PR CI. + +## Configuration via profiles (not env) + +Test configuration lives in **[`test/bookshop/.cdsrc.json`](test/bookshop/.cdsrc.json)** as cds config profiles, selected per test file: + +```js +cds.test(dir, '--profile', 'tracing-in-memory') // one profile +cds.test(dir, '--profile', 'metrics-outbox, multitenancy') // profiles compose +``` + +| Profile | What it does | +| --- | --- | +| `[logging]` | Disables the tracing exporter (`false`) so no outbox-scan trace primer leaks into the console spy; wires a `ConsoleLogRecordExporter` + custom processor; sets `log.format: json` and `cls_custom_fields: ['foo']`. | +| `[metrics]` | Wires `MyInMemoryMetricReader`; short `exportIntervalMillis` (100). | +| `[metrics-outbox]` | Enables the queue (`_queue: true`) + in-memory reader; `exportIntervalMillis` 1000 (leaves the shared HANA worker DB headroom). | +| `[metrics-outbox-disabled]` | Queue metrics off (`_queue: false`) — asserts no `queue.*` datapoints are ever exported. | +| `[tracing-in-memory]` | Wires `MyInMemorySpanExporter` as the trace exporter. | +| `[sampler-ignore-authors]` | Adds `/odata/v4/admin/Authors` to the sampler's `ignoreIncomingPaths`. | +| `[native-fetch]` | `remote.native_fetch = true` — routes outbound remote calls through native fetch (undici instrumentation) instead of the Cloud SDK. | +| `[no-scheduling]` | `requires.scheduling: false` — disables cds 10's default periodic outbox reads (they cause spurious passport set/reset pairs). | +| `[persistent-outbox]` | file-based messaging with a persistent outbox. | +| `[inboxed]` | file-based messaging with `inboxed: true` (producer- **and** consumer-side queue workers). | +| `[without-outbox]` | file-based messaging with `outboxed: false` (writes to the file directly from the producer tx). | + +> The `[multitenancy]` profile lives in the app's own `test/bookshop/package.json` (auth users + `multitenancy: true`), composed with the above where needed. + +### Load-order gotcha (from #486) + +`package.json` cds config is loaded **after** `.cdsrc.json` (last-writer-wins). So any base default that a profile must be able to override has to live in the **`.cdsrc.json` base**, not in `package.json` — otherwise `package.json` would clobber the profile. #486 moved the base `cds.log` / `messaging` defaults into `.cdsrc.json` for exactly this reason. + +> **Do not** reintroduce `process.env.cds_*` string-JSON config. That fragile pattern (config via stringified JSON in env vars, order-sensitive against the `@sap/cds` require) was removed in #486. Use profiles. + +## In-memory test infrastructure + +Two exporter-shaped classes capture telemetry into module-level arrays that tests import directly — asserting on **structured spans/datapoints**, never scraping `console.dir` output: + +- **[`test/bookshop/lib/MyInMemorySpanExporter.js`](test/bookshop/lib/MyInMemorySpanExporter.js)** — spans accumulate in `captured`; helpers `groupedByTrace()` / `rootSpans()` / `reset()`. Wired via the `tracing-in-memory` profile. +- **[`test/bookshop/lib/MyInMemoryMetricReader.js`](test/bookshop/lib/MyInMemoryMetricReader.js)** — metrics captured via the metrics profiles. It honors **DELTA temporality**, matching production (`lib/metrics/index.js` configures the real exporter with `AggregationTemporality.DELTA`), so the tests validate the real export shape. Under DELTA, counter datapoints report only the increment since the last collection, so the reader folds SUM increments into running totals while GAUGE datapoints keep their latest absolute value. + +**KEY RULE:** neither module may `require('@sap/cds')` at module top. Doing so once broke span capture — the cds require has to happen inside the test file, *after* the profile is applied. Both modules stay dependency-light (only `@opentelemetry/*` primitives + node timers). + +Cross-file correctness of the metric reader's process-level singletons relies on Vitest isolating each file in its own worker (`pool: 'forks'`, `isolate: true`); two files sharing the module in one process would bleed counter totals together. + +## Shared test helpers — `test/utils.js` + +Centralized in [`test/utils.js`](test/utils.js) (added in #488) so the ~10 tracing/metrics suites stop copy-pasting them. See the doc comments at each definition for full detail: + +- **`flushSpans()`** — force-flush the tracer provider's span processor so buffered spans reach `captured`. +- **`eventually(fn, { flush, timeout, interval })`** — state-based wait: repeatedly flush + re-run the assertion until it holds or times out. Replaces fixed `wait(...)` sleeps that flake on HANA (background/spawned work flushes after any reasonable fixed window). `flush` defaults to `flushSpans`. +- **`makeExpectEventually(flush, { timeout, interval })`** — builds an `expectEventually(assertion)` bound to a specific flush target + poll defaults (metric suites pass the reader's `forceFlush`). +- **`clearOutbox(timeout)`** — best-effort, timeout-bounded outbox `DELETE` that can never hang the surrounding hook (a draining HANA pool could otherwise block indefinitely). +- **`asExternalClient(fn)`** — runs a client request under `suppressTracing`. The in-process test client would otherwise create an outgoing **CLIENT** span for every request (an artificial extra root that also overwrites any manually-set `traceparent`). Real callers are separate, un-instrumented processes; this models that so the incoming SERVER span is created normally and stays the trace root. +- **`isOutboxScanTrace(g)` / `meaningful(groups)`** — filter out the queue scheduler's pure outbox-scan bookkeeping traces (a `db - tx` root touching only `cds.outbox.Messages`) so exact root-count assertions stay stable on the shared HANA container. + +**The flush + poll pattern:** instead of `await wait(500)` then asserting, wrap assertions in `eventually`/`expectEventually` — it flushes, checks, and returns the instant the state holds (fast on sqlite, resilient to HANA's variable worker latency). + +## HTTP instrumentation (from #475) + +HTTP instrumentation is enabled in the test app. Consequences the tests rely on: + +- **Incoming** requests produce a **SERVER** span that becomes each request trace's **root**; existing ` - tx` spans reparent under it (reparenting). The SERVER span also adopts the W3C trace context from an incoming `traceparent` header. +- **Outgoing** requests produce **CLIENT** spans. + +Because the test HTTP client runs in-process, its outgoing requests would themselves create CLIENT-span roots and pollute the trace. Tests therefore wrap client requests in **`asExternalClient`** (see above) to model an external, un-instrumented caller. + +## Sanctioned skips + +Only **two** skips are allowed (per #477). Any *new* skip must be justified against this bar; everything else that is skipped is tracked debt. + +1. **SAP Passport** — [`test/passport.test.js`](test/passport.test.js) skips on **sqlite** (`db.kind === 'sqlite'`). SAP Passport is a HANA session-context feature with no sqlite equivalent; it runs on HANA. +2. **Multitenancy on HANA** — `tracing-mt.test.js` and `metrics-outbox-multitenant.test.js` are **excluded from the HANA job** (in `vitest.config.mjs`). MTX tenant subscription needs a bound BTP Service Manager to provision per-tenant HDI containers, which the single pre-provisioned HDI container in CI lacks. They run fully on sqlite (in-memory tenants). + +This is the inverse pairing: passport is sqlite-skip / HANA-run; multitenancy is HANA-skip / sqlite-run. + +Other skips are **debt tracked in #477**, not sanctioned exceptions: + +- **§1 — queue-worker tracing on sqlite:** `tracing-scheduled`, `tracing-outboxed-batch`, `tracing-messaging-inboxed`, `tracing-messaging-persistent-outbox` skip their worker-span cases on sqlite **when `@sap/cds` < 10.1** (gated inline via `cds.version`). cds 10.1 routes the sqlite queue worker through `cds.spawn` (the queue-spawn fix), so the `cds.spawn - run task` root span appears and the tests run; on earlier cds it's absent. Both CI matrix legs are currently below that bar — the cds-9 leg force-installs `@sap/cds@9`, and the cds-10 leg resolves `@sap/cds` 10.0.x transitively from the lockfile — so the cases stay **dormant (skipped) on CI** until the transitive `@sap/cds` reaches 10.1. HANA always runs regardless of version. +- **§3 — unimplemented stubs:** placeholder `test.skip` cases in `tracing.test.js` and `tracing-mt.test.js` (individual handlers, remote, `$batch`, `srv.emit`, `cds.spawn` under multitenancy) — real coverage gaps to be written. + +## Known caveats & gotchas + +- **`startup > NO_TELEMETRY=true` local artifact.** [`test/startup.test.js`](test/startup.test.js) shells out to `cds serve` with env overrides. In some local shells the `NO_TELEMETRY=true` case can fail due to inherited environment; it passes in CI and in a clean environment. This is a pre-existing local-env artifact, not a product bug. +- **Internal-registry lockfile trap for `@sap/*` installs.** Installing `@sap/*` packages against SAP's internal registry can rewrite `package-lock.json` to internal URLs. Always install against the public npm registry and verify the lockfile is clean before committing: + + ```sh + grep -c int.repositories.cloud.sap package-lock.json # must be 0 + ``` diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 70fb6cd8..00000000 --- a/jest.config.js +++ /dev/null @@ -1,14 +0,0 @@ -const config = { - testTimeout: 42000, - testMatch: ['**/*.test.js'] -} - -if (process.env.CI && process.env.HANA_DRIVER) { - config.testTimeout *= 10 - config.testMatch = ['**/tracing-attributes.test.js', '**/passport.test.js'] - - if (process.env.HANA_PROM) - process.env.cds_requires_telemetry_tracing = JSON.stringify({ _hana_prom: process.env.HANA_PROM === 'true' }) -} - -module.exports = config diff --git a/lib/exporter/ConsoleMetricExporter.js b/lib/exporter/ConsoleMetricExporter.js index 21493a3d..e4d1f425 100644 --- a/lib/exporter/ConsoleMetricExporter.js +++ b/lib/exporter/ConsoleMetricExporter.js @@ -119,6 +119,7 @@ class ConsoleMetricExporter extends StandardConsoleMetricExporter { // export other metrics for (const tenant of Object.keys(other)) { for (const [k, v] of Object.entries(other[tenant])) { + // prettier-ignore LOG.info(`${k}${tenant !== 'undefined' ? ` of tenant "${tenant}"` : ''}: ${inspect(v.length === 1 ? v[0] : v)}`) } } diff --git a/lib/index.js b/lib/index.js index 18a7b3cc..b49c289e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -13,7 +13,22 @@ const { registerInstrumentations } = require('@opentelemetry/instrumentation') const tracing = require('./tracing') const metrics = require('./metrics') const logging = require('./logging') -const { getDiagLogLevel, getResource, hasDependency, _require } = require('./utils') +const zti = require('./zti') +const { getDiagLogLevel, hasDependency, _require } = require('./utils/common') +const { getResource } = require('./utils/resource') +const { setupStaticCerts } = require('./utils/mtls') + +// Provision the client certificate for mTLS exporters once at startup: prefer ZTI (which also +// starts watching for rotations), else fall back to statically configured x509. ZTI is an +// asynchronous source — SVID files may land shortly after startup — whereas static x509 is +// present now or never. Record which kind we got so exporter wiring knows whether a missing +// cert is expected-and-transient (async) or a hard misconfiguration. Only relevant for CaaS. +function _provisionCerts() { + if (!/to-caas$/.test(cds.env.requires.telemetry.kind || '')) return + const asyncCerts = zti.setup() + if (!asyncCerts) setupStaticCerts() + cds.env.requires.telemetry._asyncCerts = asyncCerts +} function _getInstrumentations() { const _instrumentations = cds.env.requires.telemetry.instrumentations @@ -117,4 +132,10 @@ function setup_with_calm() { if (cds.env.requires.telemetry.logging) logging() } -module.exports = hasDependency('@sap/xotel-agent-ext-js') ? setup_with_calm : setup_standalone +module.exports = () => { + // provision mTLS certs before building exporters + _provisionCerts() + + if (hasDependency('@sap/xotel-agent-ext-js')) setup_with_calm() + else setup_standalone() +} diff --git a/lib/logging/index.js b/lib/logging/index.js index 08840201..94644f0c 100644 --- a/lib/logging/index.js +++ b/lib/logging/index.js @@ -3,7 +3,9 @@ const LOG = cds.log('telemetry') const { getStringFromEnv } = require('@opentelemetry/core') -const { getCredsForCLSAsUPS, augmentCLCreds, _require } = require('../utils') +const { getCredsForCLSAsUPS, augmentCLCreds } = require('../utils/credentials') +const { createCaaSExporter } = require('../utils/caas') +const { _require } = require('../utils/common') const _protocol2module = { grpc: '@opentelemetry/exporter-logs-otlp-grpc', @@ -11,12 +13,21 @@ const _protocol2module = { 'http/json': '@opentelemetry/exporter-logs-otlp-http' } +// @opentelemetry/sdk-logs 0.221 changed the log processor constructors +function logProcessorArg(exporter) { + const version = require('@opentelemetry/sdk-logs/package.json').version + const [major, minor] = version.split('.').map(Number) + const usesOptionsObject = major > 0 || minor >= 221 + return usesOptionsObject ? { exporter } : exporter +} + function _getExporter() { let { kind, logging: { exporter: loggingExporter }, credentials } = cds.env.requires.telemetry + let exporter // for kind telemetry-to-otlp based on env vars if (loggingExporter === 'env') { @@ -40,13 +51,26 @@ function _getExporter() { if (kind.match(/to-cloud-logging$/)) { if (!credentials) credentials = getCredsForCLSAsUPS() - if (!credentials) throw new Error('No SAP Cloud Logging credentials found. Make sure the bound service instance uses the tag "Cloud Logging".') + if (!credentials) + throw new Error( + 'No SAP Cloud Logging credentials found. Make sure the bound service instance uses the tag "Cloud Logging".' + ) augmentCLCreds(credentials) config.url ??= credentials.url config.credentials ??= credentials.credentials } - const exporter = new loggingExporterModule[loggingExporter.class](config) + if (kind === 'telemetry-to-caas') { + exporter = createCaaSExporter( + credentials, + config, + '/v1/logs', + cfg => new loggingExporterModule[loggingExporter.class](cfg) + ) + } + + exporter ??= new loggingExporterModule[loggingExporter.class](config) + LOG._debug && LOG.debug('Using logs exporter:', exporter) return exporter @@ -64,7 +88,7 @@ function _getCustomProcessor(exporter) { if (!loggingProcessorModule[loggingProcessor.class]) throw new Error(`Unknown logs processor "${loggingProcessor.class}" in module "${loggingProcessor.module}"`) - const processor = new loggingProcessorModule[loggingProcessor.class](exporter) + const processor = new loggingProcessorModule[loggingProcessor.class](logProcessorArg(exporter)) LOG._debug && LOG.debug('Using logs processor:', processor) return processor @@ -86,13 +110,14 @@ module.exports = resource => { const custom_fields = cds.env.log.cls_custom_fields || [] // intercept logs via format + let emitting = false //> re-entrancy guard const { format: _format } = cds.log const format = (cds.log.format = function (module, level, ...args) { const res = _format.call(this, module, level, ...args) let log try { - log = res.length === 1 && res[0].startsWith?.('{"') && JSON.parse(res[0]) + log = !emitting && res.length === 1 && res[0].startsWith?.('{"') && JSON.parse(res[0]) } catch { // ignore } @@ -102,6 +127,7 @@ module.exports = resource => { const attributes = { 'log.type': 'LogRecord' } + if (log.tenant_id) attributes['sap.tenancy.tenant_id'] = log.tenant_id let e = args.find(a => a instanceof Error) if (e) { attributes['exception.message'] = e.message @@ -112,12 +138,17 @@ module.exports = resource => { log.msg = log.msg.replace(e.stack, e.stack?.split('\n')[0]) } for (const field of custom_fields) if (field in log) attributes[field] = log[field] - logger.emit({ - severityNumber: SeverityNumber[severity], - severityText: severity, - body: log.msg, - attributes - }) + emitting = true + try { + logger.emit({ + severityNumber: SeverityNumber[severity], + severityText: severity, + body: log.msg, + attributes + }) + } finally { + emitting = false + } } return res @@ -134,8 +165,8 @@ module.exports = resource => { const processor = _getCustomProcessor(exporter) || (process.env.NODE_ENV === 'production' - ? new BatchLogRecordProcessor(exporter) - : new SimpleLogRecordProcessor(exporter)) + ? new BatchLogRecordProcessor(logProcessorArg(exporter)) + : new SimpleLogRecordProcessor(logProcessorArg(exporter))) /* * either add processor as delegate in CALM... diff --git a/lib/metrics/index.js b/lib/metrics/index.js index bd9be759..a9c93895 100644 --- a/lib/metrics/index.js +++ b/lib/metrics/index.js @@ -6,7 +6,10 @@ const { getStringFromEnv } = require('@opentelemetry/core') const { resourceFromAttributes } = require('@opentelemetry/resources') const { AggregationTemporality, MeterProvider, PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics') -const { getDynatraceMetadata, getCredsForDTAsUPS, getCredsForCLSAsUPS, augmentCLCreds, _require } = require('../utils') +const { getDynatraceMetadata } = require('../utils/resource') +const { getCredsForDTAsUPS, getCredsForCLSAsUPS, augmentCLCreds } = require('../utils/credentials') +const { createCaaSExporter } = require('../utils/caas') +const { _require } = require('../utils/common') const _protocol2module = { grpc: '@opentelemetry/exporter-metrics-otlp-grpc', @@ -20,6 +23,7 @@ function _getExporter() { metrics: { exporter: metricsExporter }, credentials } = cds.env.requires.telemetry + let exporter if (metricsExporter === 'env') { // ... process env to determine exporter module to use @@ -50,7 +54,8 @@ function _getExporter() { // Augment configuration depending on 'kind' of telemetry if (kind.match(/to-dynatrace$/)) { if (!credentials) credentials = getCredsForDTAsUPS() - if (!credentials) throw new Error('No Dynatrace credentials found. Make sure the bound service instance uses the tag "dynatrace".') + if (!credentials) + throw new Error('No Dynatrace credentials found. Make sure the bound service instance uses the tag "dynatrace".') config.url ??= `${credentials.apiurl}/v2/otlp/v1/metrics` config.headers ??= {} @@ -67,15 +72,29 @@ function _getExporter() { if (kind.match(/to-cloud-logging$/)) { if (!credentials) credentials = getCredsForCLSAsUPS() - if (!credentials) throw new Error('No SAP Cloud Logging credentials found. Make sure the bound service instance uses the tag "Cloud Logging".') + if (!credentials) + throw new Error( + 'No SAP Cloud Logging credentials found. Make sure the bound service instance uses the tag "Cloud Logging".' + ) augmentCLCreds(credentials) config.url ??= credentials.url config.credentials ??= credentials.credentials } - const exporter = new metricsExporterModule[metricsExporter.class](config) + if (kind === 'telemetry-to-caas') { + exporter = createCaaSExporter( + credentials, + config, + '/v1/metrics', + cfg => new metricsExporterModule[metricsExporter.class](cfg) + ) + } + + exporter ??= new metricsExporterModule[metricsExporter.class](config) + LOG._debug && LOG.debug('Using metrics exporter:', exporter) + return exporter } diff --git a/lib/metrics/queue.js b/lib/metrics/queue.js index d6cbaa37..f9599713 100644 --- a/lib/metrics/queue.js +++ b/lib/metrics/queue.js @@ -2,6 +2,8 @@ const cds = require('@sap/cds') const { metrics, ValueType } = require('@opentelemetry/api') +const { timestampToEpoch } = require('../utils/time') + const LOG = cds.log('telemetry') const PERSISTENT_QUEUE_DB_NAME = 'cds.outbox.Messages' @@ -111,14 +113,17 @@ function initQueueObservation(statistics) { batchResult.observe(observables.remainingEntries, stats.remainingEntries, observationAttributes) // 'maxTimestamp' holds the most recent timestamp - const minStorageTimeSeconds = stats.maxTimestamp ? Math.floor((now - new Date(stats.maxTimestamp)) / 1000) : 0 + const maxEpoch = timestampToEpoch(stats.maxTimestamp) + const minStorageTimeSeconds = maxEpoch ? Math.floor((now - maxEpoch) / 1000) : 0 batchResult.observe(observables.minStorageTimeSeconds, minStorageTimeSeconds, observationAttributes) - const medStorageTimeSeconds = stats.medTimestamp ? Math.floor((now - new Date(stats.medTimestamp)) / 1000) : 0 + const medEpoch = timestampToEpoch(stats.medTimestamp) + const medStorageTimeSeconds = medEpoch ? Math.floor((now - medEpoch) / 1000) : 0 batchResult.observe(observables.medStorageTimeSeconds, medStorageTimeSeconds, observationAttributes) // 'minTimestamp' holds the least recent timestamp - const maxStorageTimeSeconds = stats.minTimestamp ? Math.floor((now - new Date(stats.minTimestamp)) / 1000) : 0 + const minEpoch = timestampToEpoch(stats.minTimestamp) + const maxStorageTimeSeconds = minEpoch ? Math.floor((now - minEpoch) / 1000) : 0 batchResult.observe(observables.maxStorageTimeInSeconds, maxStorageTimeSeconds, observationAttributes) batchResult.observe(observables.incomingMessages, stats.incomingMessages, observationAttributes) diff --git a/lib/tracing/cds.js b/lib/tracing/cds.js index c6766273..07445ef0 100644 --- a/lib/tracing/cds.js +++ b/lib/tracing/cds.js @@ -46,6 +46,25 @@ module.exports = () => { } }) + // Wrap `srv.tx(fn)` so the queue worker's two transactions (SELECT+UPDATE lock tx, + // then handle+DELETE dispatch tx) appear as child spans of the `cds.spawn - run task` + // root instead of each top-level CAP call inside them becoming an orphan root. + // Only wraps when there is no current `srv.context` (i.e. not already inside a request). + const _tx_proto = cds.Service.prototype.tx + cds.Service.prototype.tx = wrap(_tx_proto, { + wrapper: function tx() { + const fnIdx = typeof arguments[0] === 'function' ? 0 : typeof arguments[1] === 'function' ? 1 : -1 + if (fnIdx < 0) return _tx_proto.apply(this, arguments) + // Skip if this service is already handling a request (has an active EventContext), + // or if this is a nested .tx() call (cds.Service.tx is a no-op when already in tx). + // Do NOT skip for a bare {} context set by processInboundMsg — that's the entry point + // for file-based messaging consumer delivery and should get a root span. + if (this.context instanceof cds.EventContext) return _tx_proto.apply(this, arguments) + const name = `${this.name || 'cds'} - tx` + return trace(name, _tx_proto, this, arguments, {}) + } + }) + const { spawn: _spawn } = cds cds.spawn = wrap(_spawn, { wrapper: function spawn() { diff --git a/lib/tracing/cloud_sdk.js b/lib/tracing/cloud_sdk.js index 792472a4..c19737c4 100644 --- a/lib/tracing/cloud_sdk.js +++ b/lib/tracing/cloud_sdk.js @@ -15,7 +15,10 @@ function _cloudSdkSpanName(destination, requestConfig) { return `${method}${path ? ' ' + path : ''}` } -// REVISIT: unverified! +// Instruments @sap-cloud-sdk/http-client's exports (the outbound path CAP uses by +// default when the cloud sdk is installed) to emit a CLIENT span per remote call. +// Note: cloud sdk v4 exposes executeHttpRequest(WithOrigin) as getter-only properties, +// so a plain assignment silently fails -> we must use Object.defineProperty with a value. module.exports = () => { try { require.resolve('@sap-cloud-sdk/http-client') @@ -25,26 +28,30 @@ module.exports = () => { const cloudSDK = require('@sap-cloud-sdk/http-client') const { executeHttpRequest: _execute, executeHttpRequestWithOrigin: _executeWithOrigin } = cloudSDK - cloudSDK.executeHttpRequest = wrap(_execute, { + const _executeHttpRequest = wrap(_execute, { wrapper: function executeHttpRequest(destination, requestConfig) { - return trace( - _cloudSdkSpanName(destination, requestConfig), - _execute, - this, - arguments, - { kind: SpanKind.CLIENT, outbound: destination.name } - ) + return trace(_cloudSdkSpanName(destination, requestConfig), _execute, this, arguments, { + kind: SpanKind.CLIENT, + outbound: destination.name + }) } }) - cloudSDK.executeHttpRequestWithOrigin = wrap(_executeWithOrigin, { + Object.defineProperty(cloudSDK, 'executeHttpRequest', { + value: _executeHttpRequest, + writable: true, + configurable: true + }) + const _executeHttpRequestWithOrigin = wrap(_executeWithOrigin, { wrapper: function executeHttpRequestWithOrigin(destination, requestConfig) { - return trace( - _cloudSdkSpanName(destination, requestConfig), - _executeWithOrigin, - this, - arguments, - { kind: SpanKind.CLIENT, outbound: destination.name } - ) + return trace(_cloudSdkSpanName(destination, requestConfig), _executeWithOrigin, this, arguments, { + kind: SpanKind.CLIENT, + outbound: destination.name + }) } }) + Object.defineProperty(cloudSDK, 'executeHttpRequestWithOrigin', { + value: _executeHttpRequestWithOrigin, + writable: true, + configurable: true + }) } diff --git a/lib/tracing/index.js b/lib/tracing/index.js index 8a887c3a..44a9b08c 100644 --- a/lib/tracing/index.js +++ b/lib/tracing/index.js @@ -6,14 +6,10 @@ const { resourceFromAttributes } = require('@opentelemetry/resources') const { BatchSpanProcessor, SimpleSpanProcessor, SamplingDecision } = require('@opentelemetry/sdk-trace-base') const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node') -const { - getDynatraceMetadata, - getCredsForDTAsUPS, - getCredsForCLSAsUPS, - augmentCLCreds, - hasDependency, - _require -} = require('../utils') +const { getDynatraceMetadata } = require('../utils/resource') +const { getCredsForDTAsUPS, getCredsForCLSAsUPS, augmentCLCreds } = require('../utils/credentials') +const { createCaaSExporter } = require('../utils/caas') +const { hasDependency, _require } = require('../utils/common') function _getSampler() { const { ignoreIncomingPaths } = cds.env.requires.telemetry.tracing?.sampler || {} @@ -81,6 +77,7 @@ function _getExporter() { tracing: { exporter: tracingExporter }, credentials } = cds.env.requires.telemetry + let exporter // for kind telemetry-to-otlp based on env vars if (tracingExporter === 'env') { @@ -106,7 +103,8 @@ function _getExporter() { if (kind.match(/to-dynatrace$/)) { if (!credentials) credentials = getCredsForDTAsUPS() - if (!credentials) throw new Error('No Dynatrace credentials found. Make sure the bound service instance uses the tag "dynatrace".') + if (!credentials) + throw new Error('No Dynatrace credentials found. Make sure the bound service instance uses the tag "dynatrace".') config.url ??= `${credentials.apiurl}/v2/otlp/v1/traces` config.headers ??= {} // credentials.rest_apitoken?.token is deprecated and only supported for compatibility reasons @@ -119,13 +117,26 @@ function _getExporter() { if (kind.match(/to-cloud-logging$/)) { if (!credentials) credentials = getCredsForCLSAsUPS() - if (!credentials) throw new Error('No SAP Cloud Logging credentials found. Make sure the bound service instance uses the tag "Cloud Logging".') + if (!credentials) + throw new Error( + 'No SAP Cloud Logging credentials found. Make sure the bound service instance uses the tag "Cloud Logging".' + ) augmentCLCreds(credentials) config.url ??= credentials.url config.credentials ??= credentials.credentials } - const exporter = new tracingExporterModule[tracingExporter.class](config) + if (kind === 'telemetry-to-caas') { + exporter = createCaaSExporter( + credentials, + config, + '/v1/traces', + cfg => new tracingExporterModule[tracingExporter.class](cfg) + ) + } + + exporter ??= new tracingExporterModule[tracingExporter.class](config) + LOG._debug && LOG.debug('Using trace exporter:', exporter) return exporter @@ -149,46 +160,56 @@ module.exports = resource => { require('./cds')() require('./cloud_sdk')() - /* - * create processor - */ - let processor + // On the OneAgent path — Dynatrace OneAgent present (DT_NODE_PRELOAD_OPTIONS), kind *-to-dynatrace, + // no OTLP trace exporter installed — we don't export spans ourselves: OneAgent captures them + // in-process, and exporting would only duplicate them. So no span processor is built on this path; + // everywhere else we build one around the configured exporter. const via_one_agent = process.env.DT_NODE_PRELOAD_OPTIONS && cds.env.requires.telemetry.kind.match(/to-dynatrace$/) && !hasDependency('@opentelemetry/exporter-trace-otlp-proto') + + if (!resource && via_one_agent) + throw new Error('Dynatrace OneAgent with @sap/xotel-agent-ext-js (CALM) is not supported') + + /* + * create processor + */ + let processor if (via_one_agent) { - // if Dynatrace OneAgent is present, no exporter is needed - LOG._info && LOG.info('Dynatrace OneAgent detected, disabling trace exporter') + LOG._info && LOG.info('Dynatrace OneAgent detected: relying on in-process trace capture') } else { const exporter = _getExporter() - const processorConfig = cds.env.requires.telemetry.tracing.processor?.config || {} - processor = - process.env.NODE_ENV === 'production' - ? new BatchSpanProcessor(exporter, processorConfig) - : new SimpleSpanProcessor(exporter, processorConfig) + const { kind: processorKind, config: processorConfig = {} } = cds.env.requires.telemetry.tracing.processor || {} + const processors = { BatchSpanProcessor, SimpleSpanProcessor } + if (!processors[processorKind]) throw new Error(`Unknown span processor ${processorKind}`) + processor = new processors[processorKind](exporter, processorConfig) } /* * either add processor as delegate in CALM... */ if (!resource) { - LOG.warn("@sap/xotel-agent-ext-js found, adding @cap-js/telemetry's span processor as delegate") + LOG._info && LOG.info("@sap/xotel-agent-ext-js found, adding @cap-js/telemetry's span processor as delegate") try { const { getCompositeSpanProcessor } = require('@sap/xotel-agent-ext-js') getCompositeSpanProcessor().addDelegate(processor) - return } catch (error) { LOG.error('Failed to add span processor as delegate:', error) throw error } + return } /* * ... or initialize and return provider */ resource = resourceFromAttributes({}).merge(resource).merge(getDynatraceMetadata()) - const tracerProvider = new NodeTracerProvider({ resource, spanProcessors: [processor], sampler: _getSampler() }) + const tracerProvider = new NodeTracerProvider({ + resource, + spanProcessors: processor ? [processor] : [], + sampler: _getSampler() + }) tracerProvider.register({ propagator: _getPropagator() }) return tracerProvider } diff --git a/lib/tracing/locate.js b/lib/tracing/locate.js index 63ab4e7c..e511f006 100644 --- a/lib/tracing/locate.js +++ b/lib/tracing/locate.js @@ -33,7 +33,7 @@ const _locate = async fn => { } return location } catch (err) { - // REVISIT: the above does not work in tests (jest?) + // Stay quiet during tests: a locate miss must not pollute log-matching assertions. if (process.env.NODE_ENV !== 'test' && LOG._warn) { LOG.warn(`Unable to locate function "${fn.name}" due to error:`, err) } diff --git a/lib/tracing/trace.js b/lib/tracing/trace.js index dc9d70db..1ece9323 100644 --- a/lib/tracing/trace.js +++ b/lib/tracing/trace.js @@ -27,7 +27,7 @@ const { SEMATTRS_DB_SQL_TABLE: ATTR_DB_SQL_TABLE } = require('@opentelemetry/semantic-conventions') -const { _hrnow } = require('../utils') +const { _hrnow } = require('../utils/time') const DB_KINDS = { hana: 'sap.hana' @@ -313,7 +313,9 @@ function trace(req, fn, that, args, opts = {}) { // Matches "@cap-js/ - " optionally followed by " ", // where is prepare | exec | stmt.. Covers both the sqlite/pg case // (SQL is already baked in) and the HANA-promisified case (SQL not yet appended). - const dbNameMatch = name.match(/^(@cap-js\/\w+ - (?:prepare|exec|stmt\.\w+))(?:\s.*)?$/) + // Note: [\s\S] (not .) so multi-line SQL — e.g. HANA's INSERT ... WITH SRC AS (...) — + // is matched and stripped too; `.` alone would miss it and leak the raw statement. + const dbNameMatch = name.match(/^(@cap-js\/\w+ - (?:prepare|exec|stmt\.\w+))(?:\s[\s\S]*)?$/) if (dbNameMatch && (options.attributes[ATTR_DB_OPERATION_NAME] || options.attributes[ATTR_DB_SQL_TABLE])) { const SQL_VERB = { READ: 'SELECT', CREATE: 'INSERT' } const op = options.attributes[ATTR_DB_OPERATION_NAME] diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index f72c814e..00000000 --- a/lib/utils.js +++ /dev/null @@ -1,209 +0,0 @@ -const cds = require('@sap/cds') -const LOG = cds.log('telemetry') - -const fs = require('fs') - -const { DiagLogLevel } = require('@opentelemetry/api') -const { hrTimeToMilliseconds, getStringFromEnv } = require('@opentelemetry/core') -const { resourceFromAttributes } = require('@opentelemetry/resources') -const { - ATTR_SERVICE_NAME, - ATTR_SERVICE_VERSION, - SEMRESATTRS_SERVICE_NAMESPACE: ATTR_SERVICE_NAMESPACE, - SEMRESATTRS_SERVICE_INSTANCE_ID: ATTR_SERVICE_INSTANCE_ID -} = require('@opentelemetry/semantic-conventions') - -function getDiagLogLevel() { - if (process.env.OTEL_LOG_LEVEL) { - let level = Number(process.env.OTEL_LOG_LEVEL) - if (Number.isInteger(level)) return level - level = DiagLogLevel[process.env.OTEL_LOG_LEVEL.toUpperCase()] - if (!level) LOG.warn(`Unknown OTEL_LOG_LEVEL value: "${process.env.OTEL_LOG_LEVEL}", defaulting to "INFO"`) - return level ?? DiagLogLevel.INFO - } - - if (LOG._trace) return DiagLogLevel.VERBOSE - if (LOG._debug) return DiagLogLevel.DEBUG - if (LOG._info) return DiagLogLevel.INFO - if (LOG._warn) return DiagLogLevel.WARN - if (LOG._error) return DiagLogLevel.ERROR - return DiagLogLevel.NONE -} - -function getResource() { - const VCAP_APPLICATION = process.env.VCAP_APPLICATION && JSON.parse(process.env.VCAP_APPLICATION) - - let PKG - try { - PKG = require(cds.root + '/package.json') - } catch (err) { - LOG._info && LOG.info('Unable to require package.json to resolve app name and version due to error:', err) - } - - const name = VCAP_APPLICATION?.name || PKG?.name || 'CAP Application' - const version = PKG?.version || VCAP_APPLICATION?.application_version || '1.0.0' - - const attributes = {} - - // Service - attributes[ATTR_SERVICE_NAME] = getStringFromEnv('OTEL_SERVICE_NAME') || name - attributes[ATTR_SERVICE_VERSION] = getStringFromEnv('OTEL_SERVICE_VERSION') || version - - // Service (Experimental) - if (getStringFromEnv('OTEL_SERVICE_NAMESPACE')) - attributes[ATTR_SERVICE_NAMESPACE] = getStringFromEnv('OTEL_SERVICE_NAMESPACE') - if (VCAP_APPLICATION) attributes[ATTR_SERVICE_INSTANCE_ID] = VCAP_APPLICATION.instance_id - - if (process.env.CF_INSTANCE_GUID) { - attributes[ATTR_SERVICE_INSTANCE_ID] = process.env.CF_INSTANCE_GUID - attributes['sap.cf.instance_id'] = process.env.CF_INSTANCE_GUID - } - - if (VCAP_APPLICATION) { - attributes['sap.cf.source_id'] = VCAP_APPLICATION.application_id - attributes['sap.cf.app_id'] = VCAP_APPLICATION.application_id - attributes['sap.cf.app_name'] = VCAP_APPLICATION.name - attributes['sap.cf.space_id'] = VCAP_APPLICATION.space_id - attributes['sap.cf.space_name'] = VCAP_APPLICATION.space_name - attributes['sap.cf.org_id'] = VCAP_APPLICATION.organization_id - attributes['sap.cf.org_name'] = VCAP_APPLICATION.organization_name - attributes['sap.cf.source_type'] = 'APP/PROC/WEB' - attributes['sap.cf.process.id'] = VCAP_APPLICATION.process_id - attributes['sap.cf.process.instance_id'] = VCAP_APPLICATION.instance_id - attributes['sap.cf.process.type'] = VCAP_APPLICATION.process_type - } - - if (cds.env.requires.telemetry?.resource?.attributes) - Object.assign(attributes, cds.env.requires.telemetry.resource.attributes) - - return resourceFromAttributes(attributes) -} - -let dtmetadata -function getDynatraceMetadata() { - if (dtmetadata) return dtmetadata - - dtmetadata = resourceFromAttributes({}) - for (let name of [ - 'dt_metadata_e617c525669e072eebe3d0f08212e8f2.json', - '/var/lib/dynatrace/enrichment/dt_metadata.json' - ]) { - try { - LOG._debug && LOG.debug(`Trying to read dtmetadata source "${name}" ...`) - const content = fs - .readFileSync(name.startsWith('/var') ? name : fs.readFileSync(name).toString('utf-8').trim()) - .toString('utf-8') - LOG._debug && LOG.debug('Successful') - dtmetadata = dtmetadata.merge(resourceFromAttributes(JSON.parse(content))) - break - } catch (err) { - LOG._debug && LOG.debug('Failed with error:', err) - } - } - return dtmetadata -} - -function getCredsForDTAsUPS() { - if (!process.env.VCAP_SERVICES) return - const vcap = JSON.parse(process.env.VCAP_SERVICES) - - // Legacy Compat: - // > APMs requirement is that the instance name contains "dynatrace" - // > In addition to matching predicate defined in package.json, also support - // > - name matching /dynatrace/ - // ... in case binding info is available from environment variable VCAP_SERVICES - const dt = vcap['user-provided']?.find(b => b.name.match(/dynatrace/)) - if (dt) return dt.credentials -} - -function getCredsForCLSAsUPS() { - if (!process.env.VCAP_SERVICES) return - const vcap = JSON.parse(process.env.VCAP_SERVICES) - let ups - - // Legacy Compat: - // > In addition to matching predicate defined in package.json, also support - // > - tag: "cloud-logging" - // > - name matching /cloud-logging/ - // ... in case binding info is available from environment variable VCAP_SERVICES - ups = vcap['user-provided']?.find(e => e.tags.includes('cloud-logging') || e.tags.includes('Cloud Logging')) - if (ups) return ups.credentials - - ups = vcap['user-provided']?.find(b => b.name.match(/cloud-logging/)) - if (ups) { - // prettier-ignore - LOG._warn && LOG.warn('User-provided service instances of SAP Cloud Logging should have the tag "Cloud Logging"') - return ups.credentials - } -} - -function augmentCLCreds(credentials) { - if (credentials._augmented) return - credentials._augmented = true - - // prettier-ignore - if (!credentials['ingest-otlp-endpoint']) - throw new Error('No OpenTelemetry credentials found in binding to SAP Cloud Logging. Make sure to create the service instance with config: "{ ingest_otlp: { enabled: true } }".') - - credentials.url = 'https://' + credentials['ingest-otlp-endpoint'] - - const grpc = _require('@grpc/grpc-js') - const secureContext = require('tls').createSecureContext({ - cert: credentials['ingest-otlp-cert'], - key: credentials['ingest-otlp-key'] - }) - credentials.credentials = grpc.credentials.createFromSecureContext(secureContext) -} - -let PKG -function hasDependency(name) { - if (!PKG) { - try { - PKG = require(cds.root + '/package.json') - } catch (err) { - LOG._info && LOG.info(`Unable to require package.json to check for dependency "${name}" due to error:`, err) - return false - } - } - return !!PKG.dependencies[name] -} - -const now = Date.now() -const hrTimeInMS = Number(`${hrTimeToMilliseconds(process.hrtime())}`.split('.')[0]) -const diff = now - hrTimeInMS -const EPOCH_OFFSET_S = Number(`${diff}`.slice(0, -3)) -const EPOCH_OFFSET_MS = Number(`${diff}`.slice(-3) + '000000') - -// returns [seconds, nanoseconds] since unix epoch -function _hrnow() { - const hrtime = process.hrtime() - let s = hrtime[0] + EPOCH_OFFSET_S - let ns = hrtime[1] + EPOCH_OFFSET_MS - if (ns >= 1000000000) { - s++ - ns -= 1000000000 - } - return [s, ns] -} - -function _require(name) { - name = Array.isArray(name) ? name[0] : name - try { - return require(name.startsWith('./') ? cds.utils.path.join(cds.root, name) : name) - } catch (err) { - err.message = `Cannot find module '${name}'. Make sure to install it with 'npm i ${name}'\n` + err.message - throw err - } -} - -module.exports = { - getDiagLogLevel, - getResource, - getDynatraceMetadata, - getCredsForDTAsUPS, - getCredsForCLSAsUPS, - augmentCLCreds, - hasDependency, - _hrnow, - _require -} diff --git a/lib/utils/caas.js b/lib/utils/caas.js new file mode 100644 index 00000000..82cad13c --- /dev/null +++ b/lib/utils/caas.js @@ -0,0 +1,72 @@ +const cds = require('@sap/cds') +const LOG = cds.log('telemetry') + +const { augmentCaaSCreds } = require('./credentials') +const { certsAvailable } = require('./mtls') + +const MAX_BUFFER_SIZE = Number(process.env.TELEMETRY_MAX_BUFFER_SIZE ?? 10) + +function wrapExporterWithBuffer(exporter) { + const buffer = [] + const originalExport = exporter.export.bind(exporter) + + exporter.export = function (items, callback) { + if (certsAvailable()) { + if (buffer.length > 0) { + LOG._debug && LOG.debug(`Flushing ${buffer.length} buffered items`) + for (const buffered of buffer) { + originalExport(buffered, () => {}) + } + buffer.length = 0 + } + // Certs have arrived: drop the buffering wrapper so all future exports go straight through. + exporter.export = originalExport + return originalExport(items, callback) + } + // Not ready yet — buffer this export batch. MAX_BUFFER_SIZE caps the + // number of pending batches (not records). The batch is ack'd as success + // to the SDK now; if certs never arrive, buffered batches are dropped. + if (buffer.length >= MAX_BUFFER_SIZE) { + buffer.shift() + LOG._warn && LOG.warn('Buffer full, dropping oldest batch') + } + buffer.push(items) + callback({ code: 0 }) + } + + return exporter +} + +function createCaaSExporter(credentials, config, signalPath, createExporterFn) { + if (!credentials) throw new Error('No CaaS credentials found.') + + augmentCaaSCreds(credentials) + + // Whether certs arrive asynchronously (ZTI SVID files, possibly after startup) is decided once + // at startup (lib/index.js). If so, a missing cert now is expected — it will arrive via the + // watcher — so we buffer instead of failing. Otherwise a missing cert is a hard misconfiguration. + const asyncCerts = cds.env.requires.telemetry?._asyncCerts ?? false + + if (!certsAvailable() && !asyncCerts) { + throw new Error('CaaS requires mTLS. Bind zero-trust-identity service or configure x509 credentials.') + } + + const exporterConfig = { + ...config, + url: credentials.baseUrl + signalPath, + httpAgentOptions: credentials.httpAgentOptions + } + + let exporter = createExporterFn(exporterConfig) + + // Certs not yet available but arriving async: buffer exports until the watcher delivers them + if (asyncCerts && !certsAvailable()) exporter = wrapExporterWithBuffer(exporter) + + return exporter +} + +module.exports = { + createCaaSExporter, + // exported for testing only (reached in production via createCaaSExporter) + wrapExporterWithBuffer +} diff --git a/lib/utils/common.js b/lib/utils/common.js new file mode 100644 index 00000000..e8e8f0c1 --- /dev/null +++ b/lib/utils/common.js @@ -0,0 +1,50 @@ +const cds = require('@sap/cds') +const LOG = cds.log('telemetry') + +const { DiagLogLevel } = require('@opentelemetry/api') + +function getDiagLogLevel() { + if (process.env.OTEL_LOG_LEVEL) { + let level = Number(process.env.OTEL_LOG_LEVEL) + if (Number.isInteger(level)) return level + level = DiagLogLevel[process.env.OTEL_LOG_LEVEL.toUpperCase()] + if (!level) LOG.warn(`Unknown OTEL_LOG_LEVEL value: "${process.env.OTEL_LOG_LEVEL}", defaulting to "INFO"`) + return level ?? DiagLogLevel.INFO + } + + if (LOG._trace) return DiagLogLevel.VERBOSE + if (LOG._debug) return DiagLogLevel.DEBUG + if (LOG._info) return DiagLogLevel.INFO + if (LOG._warn) return DiagLogLevel.WARN + if (LOG._error) return DiagLogLevel.ERROR + return DiagLogLevel.NONE +} + +let PKG +function hasDependency(name) { + if (!PKG) { + try { + PKG = require(cds.root + '/package.json') + } catch (err) { + LOG._info && LOG.info(`Unable to require package.json to check for dependency "${name}" due to error:`, err) + return false + } + } + return !!PKG.dependencies[name] +} + +function _require(name) { + name = Array.isArray(name) ? name[0] : name + try { + return require(name.startsWith('./') ? cds.utils.path.join(cds.root, name) : name) + } catch (err) { + err.message = `Cannot find module '${name}'. Make sure to install it with 'npm i ${name}'\n` + err.message + throw err + } +} + +module.exports = { + getDiagLogLevel, + hasDependency, + _require +} diff --git a/lib/utils/credentials.js b/lib/utils/credentials.js new file mode 100644 index 00000000..5010977a --- /dev/null +++ b/lib/utils/credentials.js @@ -0,0 +1,76 @@ +const cds = require('@sap/cds') +const LOG = cds.log('telemetry') + +const { _require } = require('./common') +const { getRotatingAgentFactory } = require('./mtls') + +function getCredsForDTAsUPS() { + if (!process.env.VCAP_SERVICES) return + const vcap = JSON.parse(process.env.VCAP_SERVICES) + + // Legacy Compat: + // > APMs requirement is that the instance name contains "dynatrace" + // > In addition to matching predicate defined in package.json, also support + // > - name matching /dynatrace/ + // ... in case binding info is available from environment variable VCAP_SERVICES + const dt = vcap['user-provided']?.find(b => b.name.match(/dynatrace/)) + if (dt) return dt.credentials +} + +function getCredsForCLSAsUPS() { + if (!process.env.VCAP_SERVICES) return + const vcap = JSON.parse(process.env.VCAP_SERVICES) + let ups + + // Legacy Compat: + // > In addition to matching predicate defined in package.json, also support + // > - tag: "cloud-logging" + // > - name matching /cloud-logging/ + // ... in case binding info is available from environment variable VCAP_SERVICES + ups = vcap['user-provided']?.find(e => e.tags.includes('cloud-logging') || e.tags.includes('Cloud Logging')) + if (ups) return ups.credentials + + ups = vcap['user-provided']?.find(b => b.name.match(/cloud-logging/)) + if (ups) { + // prettier-ignore + LOG._warn && LOG.warn('User-provided service instances of SAP Cloud Logging should have the tag "Cloud Logging"') + return ups.credentials + } +} + +function augmentCaaSCreds(credentials) { + if (credentials._augmented) return + credentials._augmented = true + + if (!credentials.otlp?.http) { + throw new Error('No OTLP HTTP endpoint in CaaS credentials') + } + + credentials.baseUrl = credentials.otlp.http + credentials.httpAgentOptions = getRotatingAgentFactory() +} + +function augmentCLCreds(credentials) { + if (credentials._augmented) return + credentials._augmented = true + + // prettier-ignore + if (!credentials['ingest-otlp-endpoint']) + throw new Error('No OpenTelemetry credentials found in binding to SAP Cloud Logging. Make sure to create the service instance with config: "{ ingest_otlp: { enabled: true } }".') + + credentials.url = 'https://' + credentials['ingest-otlp-endpoint'] + + const grpc = _require('@grpc/grpc-js') + const secureContext = require('tls').createSecureContext({ + cert: credentials['ingest-otlp-cert'], + key: credentials['ingest-otlp-key'] + }) + credentials.credentials = grpc.credentials.createFromSecureContext(secureContext) +} + +module.exports = { + getCredsForDTAsUPS, + getCredsForCLSAsUPS, + augmentCLCreds, + augmentCaaSCreds +} diff --git a/lib/utils/mtls.js b/lib/utils/mtls.js new file mode 100644 index 00000000..d95e35b7 --- /dev/null +++ b/lib/utils/mtls.js @@ -0,0 +1,109 @@ +const cds = require('@sap/cds') +const LOG = cds.log('telemetry') + +// Generic HTTP mTLS transport for OTLP exporters. It is agnostic to where the client +// certificate comes from: it reads the current cert/key from cds.env and hot-swaps them +// whenever a cert source announces a rotation. ZTI is one such source, but static x509 or +// any future provider works the same way — the only contract is the 'svid' event + cds.env. + +// True once a usable client certificate is present in cds.env, regardless of its source +// (ZTI-loaded or statically configured). Both cert and key are required — a mismatched pair +// would only surface as a TLS handshake failure later, so the fail-fast path must reject it. +// Pure predicate: no I/O, no watcher side effects. +function certsAvailable() { + const x509 = cds.env.requires?.telemetry?.x509 + return !!(x509?.cert && x509?.key) +} + +function decodeCredentials(creds) { + if (creds.cert.startsWith('LS0t') || !creds.cert.startsWith('-----BEGIN')) { + const cert = Buffer.from(creds.cert, 'base64').toString('utf-8') + const key = Buffer.from(creds.key, 'base64').toString('utf-8') + if (!cert.includes('-----BEGIN') || !key.includes('-----BEGIN')) { + throw new Error('Decoded x509 cert or key is not valid PEM data') + } + return { cert, key } + } + return { cert: creds.cert, key: creds.key } +} + +// Provision statically configured x509 credentials into cds.env, decoding base64 if needed. +// The static counterpart to ZTI cert sourcing; both feed the same cds.env.x509 slot that +// certsAvailable() and the rotating agent read. Cert provisioning is orchestrated once at +// startup (see lib/index.js); this is the non-ZTI fallback. +function setupStaticCerts() { + const { x509 } = cds.env.requires.telemetry || {} + if (!x509?.cert || !x509?.key) return false + + const { cert, key } = decodeCredentials(x509) + cds.env.requires.telemetry.x509 = { cert, key } + return true +} + +let _RotatingCertAgent = null + +function getRotatingCertAgentClass() { + if (_RotatingCertAgent) return _RotatingCertAgent + const https = require('https') + + _RotatingCertAgent = class RotatingCertAgent extends https.Agent { + constructor() { + const x509 = cds.env.requires.telemetry?.x509 + super({ keepAlive: true, cert: x509?.cert, key: x509?.key }) + this._boundRotate = this._rotate.bind(this) + cds.on('svid', this._boundRotate) + } + + _rotate(payload) { + try { + const { cert, key } = payload ?? cds.env.requires.telemetry?.x509 ?? {} + if (!cert || !key) { + LOG._warn && LOG.warn('RotatingCertAgent: no credentials in event payload or cds.env') + return + } + this.options.cert = cert + this.options.key = key + this.destroy() + LOG._debug && LOG.debug('Certificate rotated') + } catch (err) { + LOG._error && LOG.error('Failed to rotate certificate:', err) + } + } + + _cleanup() { + cds.off('svid', this._boundRotate) + this.destroy() + } + } + return _RotatingCertAgent +} + +let _rotatingAgent = null + +function createRotatingAgent() { + if (!_rotatingAgent) { + const RotatingCertAgent = getRotatingCertAgentClass() + _rotatingAgent = new RotatingCertAgent() + } + return _rotatingAgent +} + +function getRotatingAgentFactory() { + return () => createRotatingAgent() +} + +function reset() { + if (_rotatingAgent) { + _rotatingAgent._cleanup() + _rotatingAgent = null + } +} + +module.exports = { + certsAvailable, + setupStaticCerts, + getRotatingAgentFactory, + // exported for testing only (used internally within this module in production) + getRotatingCertAgentClass, + reset +} diff --git a/lib/utils/resource.js b/lib/utils/resource.js new file mode 100644 index 00000000..45e83a19 --- /dev/null +++ b/lib/utils/resource.js @@ -0,0 +1,91 @@ +const cds = require('@sap/cds') +const LOG = cds.log('telemetry') + +const fs = require('fs') + +const { getStringFromEnv } = require('@opentelemetry/core') +const { resourceFromAttributes } = require('@opentelemetry/resources') +const { + ATTR_SERVICE_NAME, + ATTR_SERVICE_VERSION, + SEMRESATTRS_SERVICE_NAMESPACE: ATTR_SERVICE_NAMESPACE, + SEMRESATTRS_SERVICE_INSTANCE_ID: ATTR_SERVICE_INSTANCE_ID +} = require('@opentelemetry/semantic-conventions') + +function getResource() { + const VCAP_APPLICATION = process.env.VCAP_APPLICATION && JSON.parse(process.env.VCAP_APPLICATION) + + let PKG + try { + PKG = require(cds.root + '/package.json') + } catch (err) { + LOG._info && LOG.info('Unable to require package.json to resolve app name and version due to error:', err) + } + + const name = VCAP_APPLICATION?.name || PKG?.name || 'CAP Application' + const version = PKG?.version || VCAP_APPLICATION?.application_version || '1.0.0' + + const attributes = {} + + // Service + attributes[ATTR_SERVICE_NAME] = getStringFromEnv('OTEL_SERVICE_NAME') || name + attributes[ATTR_SERVICE_VERSION] = getStringFromEnv('OTEL_SERVICE_VERSION') || version + + // Service (Experimental) + if (getStringFromEnv('OTEL_SERVICE_NAMESPACE')) + attributes[ATTR_SERVICE_NAMESPACE] = getStringFromEnv('OTEL_SERVICE_NAMESPACE') + if (VCAP_APPLICATION) attributes[ATTR_SERVICE_INSTANCE_ID] = VCAP_APPLICATION.instance_id + + if (process.env.CF_INSTANCE_GUID) { + attributes[ATTR_SERVICE_INSTANCE_ID] = process.env.CF_INSTANCE_GUID + attributes['sap.cf.instance_id'] = process.env.CF_INSTANCE_GUID + } + + if (VCAP_APPLICATION) { + attributes['sap.cf.source_id'] = VCAP_APPLICATION.application_id + attributes['sap.cf.app_id'] = VCAP_APPLICATION.application_id + attributes['sap.cf.app_name'] = VCAP_APPLICATION.name + attributes['sap.cf.space_id'] = VCAP_APPLICATION.space_id + attributes['sap.cf.space_name'] = VCAP_APPLICATION.space_name + attributes['sap.cf.org_id'] = VCAP_APPLICATION.organization_id + attributes['sap.cf.org_name'] = VCAP_APPLICATION.organization_name + attributes['sap.cf.source_type'] = 'APP/PROC/WEB' + attributes['sap.cf.process.id'] = VCAP_APPLICATION.process_id + attributes['sap.cf.process.instance_id'] = VCAP_APPLICATION.instance_id + attributes['sap.cf.process.type'] = VCAP_APPLICATION.process_type + } + + if (cds.env.requires.telemetry?.resource?.attributes) + Object.assign(attributes, cds.env.requires.telemetry.resource.attributes) + + return resourceFromAttributes(attributes) +} + +let dtmetadata +function getDynatraceMetadata() { + if (dtmetadata) return dtmetadata + + dtmetadata = resourceFromAttributes({}) + for (let name of [ + 'dt_metadata_e617c525669e072eebe3d0f08212e8f2.json', + '/var/lib/dynatrace/enrichment/dt_metadata.json' + ]) { + try { + LOG._debug && LOG.debug(`Trying to read dtmetadata source "${name}" ...`) + const content = fs + .readFileSync(name.startsWith('/var') ? name : fs.readFileSync(name).toString('utf-8').trim()) + .toString('utf-8') + LOG._debug && LOG.debug('Successful') + dtmetadata = dtmetadata.merge(resourceFromAttributes(JSON.parse(content))) + break + } catch (err) { + LOG._debug && LOG.debug('Failed with error:', err) + } + } + return dtmetadata +} + +module.exports = { + getResource, + getDynatraceMetadata +} diff --git a/lib/utils/time.js b/lib/utils/time.js new file mode 100644 index 00000000..c9c313f4 --- /dev/null +++ b/lib/utils/time.js @@ -0,0 +1,44 @@ +const { hrTimeToMilliseconds } = require('@opentelemetry/core') + +const now = Date.now() +const hrTimeInMS = Number(`${hrTimeToMilliseconds(process.hrtime())}`.split('.')[0]) +const diff = now - hrTimeInMS +const EPOCH_OFFSET_S = Number(`${diff}`.slice(0, -3)) +const EPOCH_OFFSET_MS = Number(`${diff}`.slice(-3) + '000000') + +// returns [seconds, nanoseconds] since unix epoch +function _hrnow() { + const hrtime = process.hrtime() + let s = hrtime[0] + EPOCH_OFFSET_S + let ns = hrtime[1] + EPOCH_OFFSET_MS + if (ns >= 1000000000) { + s++ + ns -= 1000000000 + } + return [s, ns] +} + +// Parse a queue `timestamp` value to epoch millis, robust to the DB driver's format. +// Direct column reads return an ISO-8601 UTC string ("...Z"), but HANA's min()/max() +// aggregates return a timezone-naive string ("2026-08-13 22:50:41.2270000" — space +// separator, sub-ms digits, no zone). Passing that straight to `new Date()` parses it as +// LOCAL time, so storage-time gauges were off by the machine's UTC offset on HANA (e.g. 7200s +// in CEST). Normalize naive strings to UTC before parsing; ISO/Date/number inputs pass through. +function timestampToEpoch(ts) { + if (ts == null) return null + if (ts instanceof Date) return ts.getTime() + if (typeof ts === 'number') return ts + let s = String(ts).trim() + // Already zoned (ends with Z or ±HH:MM / ±HHMM)? leave as-is; otherwise treat as UTC. + if (!/[zZ]$|[+-]\d\d:?\d\d$/.test(s)) { + // "YYYY-MM-DD HH:MM:SS.fffffff" -> "YYYY-MM-DDTHH:MM:SS.fffZ" (trim sub-ms to 3 digits) + s = s.replace(' ', 'T').replace(/(\.\d{3})\d+$/, '$1') + 'Z' + } + const ms = Date.parse(s) + return Number.isNaN(ms) ? null : ms +} + +module.exports = { + _hrnow, + timestampToEpoch +} diff --git a/lib/zti.js b/lib/zti.js new file mode 100644 index 00000000..f6e0a00d --- /dev/null +++ b/lib/zti.js @@ -0,0 +1,190 @@ +const cds = require('@sap/cds') +const LOG = cds.log('telemetry') +const fs = require('fs') + +const SVID_DIR = '/home/vcap/app/spire-svids' + +let _paths = null +let _cached = null +let _watcher = null +let _bootstrapWatcher = null + +function _getSVIDCertificate() { + if (!_paths) throw new Error('ZTI paths not initialized') + + let stat + try { + stat = fs.statSync(_paths.cert) + } catch (e) { + if (_cached) { + LOG._debug && LOG.debug('Stat failure, serving cached credentials') + return _cached + } + throw e + } + + const mtime = stat.mtimeMs + if (_cached?.mtime === mtime) return _cached + + try { + _cached = { + cert: fs.readFileSync(_paths.cert, 'utf8'), + key: fs.readFileSync(_paths.key, 'utf8'), + bundle: fs.readFileSync(_paths.bundle, 'utf8'), + mtime + } + return _cached + } catch (err) { + if (_cached) { + LOG._warn && LOG.warn('Failed to reload SVID files, serving cached:', err) + return _cached + } + throw err + } +} + +function getZTIConfig() { + if (!process.env.VCAP_SERVICES) return null + + const vcap = JSON.parse(process.env.VCAP_SERVICES) + const zti = vcap['zero-trust-identity'] + if (!zti || zti.length === 0) return null + + const svidName = zti[0].credentials?.parameters?.['svid-store']?.file?.name + if (!svidName) { + LOG._warn && LOG.warn('zero-trust-identity binding missing svid-store.file.name') + return null + } + + const svidDir = process.env.TELEMETRY_ZTI_DIR || SVID_DIR + return { svidDir, svidName } +} + +function initializeZTI() { + const config = getZTIConfig() + if (!config) return false + + _paths = { + cert: `${config.svidDir}/${config.svidName}.svid.pem`, + key: `${config.svidDir}/${config.svidName}.svid.key`, + bundle: `${config.svidDir}/${config.svidName}.bundle.pem` + } + return true +} + +function loadInitialCerts() { + if (!_paths) return false + try { + const creds = _getSVIDCertificate() + if (!cds.env.requires.telemetry) cds.env.requires.telemetry = {} + cds.env.requires.telemetry.x509 = { cert: creds.cert, key: creds.key } + return true + } catch { + return false + } +} + +// One-shot startup provisioning: if a zero-trust-identity service is bound, ZTI is the cert +// source — load the initial SVID into cds.env and start watching for rotations. Returns whether +// ZTI is active, independent of whether certs are present yet, since they may arrive +// asynchronously (SPIRE may write the SVID files shortly after startup). +function setup() { + if (!initializeZTI()) return false + loadInitialCerts() + startSVIDWatcher() + return true +} + +function startSVIDWatcher() { + if (_watcher) return // already watching + if (!_paths) throw new Error('ZTI not initialized') + + const dir = require('path').dirname(_paths.cert) + let debounce = null + + try { + _watcher = fs.watch(dir, (_eventType, filename) => { + if (filename && !filename.endsWith('.svid.pem')) return + if (debounce) clearTimeout(debounce) + debounce = setTimeout(reloadAndEmitSVID, 1000) + }) + } catch (err) { + // The SVID directory may not exist yet during the boot race (SPIRE writes it shortly after + // startup). Watch the parent so we can arm the real watcher once the directory appears — + // otherwise setup() reports async certs with no watcher left to ever deliver them, and + // buffering exporters queue forever. + if (err.code === 'ENOENT') startBootstrapWatcher(dir) + else LOG._warn && LOG.warn('Failed to setup SVID watcher:', err) + } +} + +// Reload the SVID from disk into cds.env and announce it via the 'svid' event so the rotating +// agent hot-swaps and buffering exporters flush. Used for rotations and for the delayed initial +// load once the SVID files first appear. +function reloadAndEmitSVID() { + try { + _cached = null + const creds = _getSVIDCertificate() + if (!cds.env.requires.telemetry) cds.env.requires.telemetry = {} + cds.env.requires.telemetry.x509 = { cert: creds.cert, key: creds.key } + cds.emit('svid', { cert: creds.cert, key: creds.key }) + LOG._debug && LOG.debug('SVID certificate loaded') + } catch (err) { + LOG._error && LOG.error('Failed to reload SVID:', err) + } +} + +// Boot-race fallback: watch the (always-present) parent directory until the SVID directory is +// created, then hand off to the real watcher and pick up any files already written. +function startBootstrapWatcher(svidDir) { + if (_bootstrapWatcher) return + const path = require('path') + const parent = path.dirname(svidDir) + const base = path.basename(svidDir) + let debounce = null + + try { + _bootstrapWatcher = fs.watch(parent, (_eventType, filename) => { + if (filename && filename !== base) return + if (!fs.existsSync(svidDir)) return + if (debounce) clearTimeout(debounce) + debounce = setTimeout(() => { + stopBootstrapWatcher() + startSVIDWatcher() // directory now exists — arm the real watcher + if (fs.existsSync(_paths.cert)) reloadAndEmitSVID() // files may already be present + }, 500) + }) + } catch (err) { + LOG._warn && LOG.warn('Failed to setup SVID bootstrap watcher:', err) + } +} + +function stopSVIDWatcher() { + if (_watcher) { + _watcher.close() + _watcher = null + } + stopBootstrapWatcher() +} + +function stopBootstrapWatcher() { + if (_bootstrapWatcher) { + _bootstrapWatcher.close() + _bootstrapWatcher = null + } +} + +function reset() { + _paths = null + _cached = null + stopSVIDWatcher() +} + +module.exports = { + setup, + loadInitialCerts, + // exported for testing only (used internally within this module in production) + initializeZTI, + getZTIConfig, + reset +} diff --git a/package-lock.json b/package-lock.json index b92d360a..2b172bfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,19 @@ { "name": "@cap-js/telemetry", - "version": "2.0.1", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, - "dev": true, "packages": { "": { "name": "@cap-js/telemetry", - "version": "2.0.1", + "version": "2.1.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/core": "^2.8", - "@opentelemetry/instrumentation": "^0.221", - "@opentelemetry/instrumentation-http": "^0.221", - "@opentelemetry/instrumentation-undici": "^0.31", + "@opentelemetry/instrumentation": "^0.222", + "@opentelemetry/instrumentation-http": "^0.222", + "@opentelemetry/instrumentation-undici": "^0.32", "@opentelemetry/resources": "^2.8", "@opentelemetry/sdk-metrics": "^2.8", "@opentelemetry/sdk-trace-base": "^2.8", @@ -26,5890 +25,3799 @@ "@cap-js/sqlite": "^3", "@cap-js/telemetry": "file:.", "@grpc/grpc-js": "^1.9.14", - "@opentelemetry/exporter-metrics-otlp-grpc": "^0.219", - "@opentelemetry/exporter-metrics-otlp-proto": "^0.219", - "@opentelemetry/exporter-trace-otlp-grpc": "^0.219", - "@opentelemetry/exporter-trace-otlp-proto": "^0.219", - "@opentelemetry/instrumentation-host-metrics": "^0.2.0", - "@opentelemetry/instrumentation-runtime-node": "^0.32.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "^0.222", + "@opentelemetry/exporter-metrics-otlp-proto": "^0.222", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.222", + "@opentelemetry/exporter-trace-otlp-proto": "^0.222", + "@opentelemetry/instrumentation-host-metrics": "^0.5.0", + "@opentelemetry/instrumentation-runtime-node": "^0.35.0", + "@sap-cloud-sdk/http-client": "^4", "@sap/cds-mtxs": "^4", "axios": "^1.6.7", "eslint": "^10", - "jest": "^30.4.2" + "oxfmt": "0.63.0", + "vitest": "^4" }, "peerDependencies": { "@sap/cds": "^10 || ^9" } }, - "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, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "devOptional": 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" + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.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, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "devOptional": true, "license": "MIT", - "engines": { - "node": ">=6.9.0" + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.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==", + "node_modules/@cap-js/cds-test": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@cap-js/cds-test/-/cds-test-1.0.2.tgz", + "integrity": "sha512-autSQg1l7nvHmBwxan0inQ71Cw3cqQdBvON/RFarhbwRJSeC6oC7nQ6d1RiVxCYYkeM0wqkmfn8WGQ9K89dWXQ==", "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" + "license": "Apache-2.0", + "bin": { + "cds-test": "bin/chest.js", + "chest": "bin/chest.js" }, "engines": { - "node": ">=6.9.0" + "node": ">=22" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "peerDependencies": { + "@sap/cds": ">=9.8", + "chai": "^6", + "chai-as-promised": "^8" } }, - "node_modules/@babel/generator": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", - "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "node_modules/@cap-js/db-service": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@cap-js/db-service/-/db-service-3.1.0.tgz", + "integrity": "sha512-CabYQlrT8O2n8aWnvjBhk94KlkpwowUzqnxwJFtokEBiHIVemu1+Kfvoo1djwpX6MB/4TV2j3xQf0DnLiLMUeA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "license": "Apache-2.0", + "peerDependencies": { + "@sap/cds": "^10", + "generic-pool": "^3.9.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependenciesMeta": { + "generic-pool": { + "optional": true + } } }, - "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==", + "node_modules/@cap-js/sqlite": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@cap-js/sqlite/-/sqlite-3.1.0.tgz", + "integrity": "sha512-wcTMQqaGlIq+WkuvQ/p7+I9xoCUGxzYF8TFnrtl78MClBukTrRBWc29xvwznFlifJUVdF3NVhcIabep5+ancHA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "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" + "@cap-js/db-service": "^3.1.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@sap/cds": "^10", + "better-sqlite3": "^12.0.0 || ^13.0.0", + "sql.js": "^1.13.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "sql.js": { + "optional": true + } } }, - "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==", + "node_modules/@cap-js/telemetry": { + "resolved": "", + "link": true + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=0.1.90" } }, - "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==", + "node_modules/@dabh/diagnostics": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.9.tgz", + "integrity": "sha512-R6siwR65Hm+3yfgP7o8DKhNvputQAwfoz9zTc3kyDudnomj2/BcLmD+uGQQPICjuFUp8ounPBU+jmKsocwVVAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.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, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "devOptional": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", - "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" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.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", + "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==", + "devOptional": true, + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "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, + "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==", + "devOptional": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.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", + "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==", + "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/parser": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", - "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", - "dev": true, - "license": "MIT", + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.29.8" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@eslint/core": "^1.2.1" }, "engines": { - "node": ">=6.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "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", + "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==", + "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@types/json-schema": "^7.0.15" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "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, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "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/@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==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "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", + "node_modules/@eslint/plugin-kit": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", + "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", - "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "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": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=12.10.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==", + "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": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" } }, - "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", + "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==", + "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@humanfs/types": "^0.15.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", - "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", - "dev": true, - "license": "MIT", + "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==", + "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.18.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/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.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" + "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==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "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" + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "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==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "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", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" } }, - "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, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "devOptional": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "keyv": "^5.6.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", + "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==", + "devOptional": true, + "license": "MIT" + }, + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.222.0.tgz", + "integrity": "sha512-9mb1If+IF6u0ZVXkHQ6ogEae5HwA6ajIVUgpSDQyRASxft6BSXHvBvPooRle3yFN/fKnCdSOnuu0OC3PLcF6+g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.11.0.tgz", + "integrity": "sha512-Tr79DyWI8itsBdg+jH+opjfrwLzX+erk1/ExkIwhWoAVjVrJIn2y5+cGjTC0Vy8fyNIA/y8wuJPZwr1T3xCZeQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@opentelemetry/api": ">=1.0.0 <1.10.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", + "node_modules/@opentelemetry/core": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.11.0.tgz", + "integrity": "sha512-7YP44XH0tV6+Mb54x2YGf84i7yi+31MBZlE8JwvozkxyTvXbSp10X7cI7YE49ChJ3shMJoBmCJF3+1QFBJctGA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=6.9.0" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@opentelemetry/api": ">=1.0.0 <1.10.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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.222.0.tgz", + "integrity": "sha512-nPU2gpQWJfQttf+A71aLkKEOQoO6n6QGs5QTtPagi/rb3Lc1I55bqRXoLQEZsWqWimc8eas2Vo168NkTaByxQw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@opentelemetry/exporter-metrics-otlp-http": "0.222.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.222.0", + "@opentelemetry/otlp-transformer": "0.222.0" }, "engines": { - "node": ">=6.9.0" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", - "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.222.0.tgz", + "integrity": "sha512-6Ko+0bgNP6V4G/RsmNBSk5lO1B/lo9z6tvLpFY3ykPPevXNzFmOW3C9hEmTt0zKCvHovyrol3TYlg4HVb5gwjg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "@opentelemetry/core": "2.11.0", + "@opentelemetry/otlp-exporter-base": "0.222.0", + "@opentelemetry/otlp-transformer": "0.222.0", + "@opentelemetry/resources": "2.11.0", + "@opentelemetry/sdk-metrics": "2.11.0" }, "engines": { - "node": ">=6.9.0" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@opentelemetry/api": "^1.3.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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.222.0.tgz", + "integrity": "sha512-s1ZPwS8Ig7cpRmL3kCobPaFbemM81dJ03LNPrsuN32h3mCDvNF2NQ1pRHruOju0P0o1TZ24LkZA685rtQal1KA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" + "@opentelemetry/exporter-metrics-otlp-http": "0.222.0", + "@opentelemetry/otlp-exporter-base": "0.222.0", + "@opentelemetry/otlp-transformer": "0.222.0" }, "engines": { - "node": ">=6.9.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@babel/traverse": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", - "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.222.0.tgz", + "integrity": "sha512-uisilePEdOMa3hIfXW82XXU6FofF8gw3iv+PoboIMWEUBC7XbJB3XnIHSdrSDsqCw3njR/If08qD0yzOY1U9Sg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.8", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.8", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.8", - "debug": "^4.3.1" + "@opentelemetry/otlp-exporter-base": "0.222.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.222.0", + "@opentelemetry/otlp-transformer": "0.222.0", + "@opentelemetry/sdk-trace": "2.11.0" }, "engines": { - "node": ">=6.9.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.222.0.tgz", + "integrity": "sha512-imLrLzNhnABJt3tUyT63h6MqfZrG3Gdv3TE7+OUQTvwD088RfCUx3rF01lcgfu2KXBFk13vwi0yKZLvFEalkIA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" + "@opentelemetry/otlp-exporter-base": "0.222.0", + "@opentelemetry/otlp-transformer": "0.222.0", + "@opentelemetry/sdk-trace": "2.11.0" }, "engines": { - "node": ">=6.9.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.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/@cap-js/cds-test": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@cap-js/cds-test/-/cds-test-1.0.1.tgz", - "integrity": "sha512-hOD4ECvHA8mVXCnsAcfWe+CB4XxLHYZskGUiD49BFEblbZVHae32xWZLfPfaawnZzAUPEJ+ugakyltgbxdKdKA==", - "dev": true, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.222.0.tgz", + "integrity": "sha512-fhbRDuAgzPKpq1k5+hX3uS+3QAYNca0on0Ngot/R8dDsQtuFaADeZnG2uSgxABuvcI7S2phAaRhbct9jieeKZw==", "license": "Apache-2.0", - "bin": { - "cds-test": "bin/chest.js", - "chest": "bin/chest.js" + "dependencies": { + "@opentelemetry/api-logs": "0.222.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=22" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@sap/cds": ">=9.8", - "chai": "^6", - "chai-as-promised": "^8" + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@cap-js/db-service": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cap-js/db-service/-/db-service-3.0.1.tgz", - "integrity": "sha512-sWy+EYyfY7YzJspKcGqln4gWNVffcRwd/vm47T+tMa85+LWtOsmJgdfH8i1KVCR3o8zaywmPwSflnnSOKoZJkg==", + "node_modules/@opentelemetry/instrumentation-host-metrics": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-host-metrics/-/instrumentation-host-metrics-0.5.0.tgz", + "integrity": "sha512-rAw5lmY2u3bwUFohRtR4ZYNDnhMTiSeQqq8BB4Bo+aJQ+7vIOOwW/3d4awLPMDQ69VeA5jsh+ICL5oJinrWIwA==", "dev": true, "license": "Apache-2.0", - "peerDependencies": { - "@sap/cds": "^10", - "generic-pool": "^3.9.0" + "dependencies": { + "@opentelemetry/instrumentation": "^0.222.0", + "systeminformation": "^5.31.6" }, - "peerDependenciesMeta": { - "generic-pool": { - "optional": true - } + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@cap-js/sqlite": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@cap-js/sqlite/-/sqlite-3.0.2.tgz", - "integrity": "sha512-C+wiMzRxgmNF919ZcIjXPlAGlgunXUzPAldp5WMZ4fmb+komfADMmdUzYqS1LklnBXBkdxrKlP30giNTutPwpg==", - "dev": true, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.222.0.tgz", + "integrity": "sha512-HhMHW32LyGzIJEmUElxaML4oUe6JiCq0Hvjt/Awxb60SlMsSFetFLFjed45g/0wrPr1n7z+xW+x1B/584WBAkg==", "license": "Apache-2.0", "dependencies": { - "@cap-js/db-service": "^3.0.1" + "@opentelemetry/core": "2.11.0", + "@opentelemetry/instrumentation": "0.222.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" }, - "peerDependencies": { - "@sap/cds": "^10", - "better-sqlite3": "^12.0.0", - "sql.js": "^1.13.0" + "engines": { + "node": "^18.19.0 || >=20.6.0" }, - "peerDependenciesMeta": { - "better-sqlite3": { - "optional": true - }, - "sql.js": { - "optional": true - } + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@cap-js/telemetry": { - "resolved": "", - "link": true - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "node_modules/@opentelemetry/instrumentation-runtime-node": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-runtime-node/-/instrumentation-runtime-node-0.35.0.tgz", + "integrity": "sha512-/cOW4Qlv4nnr4QXDCkv0TEtOBa9R9lXHu//JuD5+nRPYR74g9iLmf5tadcYvn32oz2R8vQnV8FTnMLzjfAGMCQ==", "dev": true, - "license": "MIT", - "optional": true, + "license": "Apache-2.0", "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" + "@opentelemetry/api-logs": "^0.222.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.222.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.32.0.tgz", + "integrity": "sha512-Ms8oPkrCHa6hJOYodcFC8QOt1AFlns3KD1CqXBEihXyTFeLXDO84tIXYyKlXNHr8jMrl1Gm2velDvsSDXAuh9w==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.4.0" + "@opentelemetry/core": "^2.9.0", + "@opentelemetry/instrumentation": "^0.222.0", + "@opentelemetry/semantic-conventions": "^1.24.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.222.0.tgz", + "integrity": "sha512-YbywG3veEm2Fb6TbdxRkuquWob6eVWXuA8/Ba1tXz9jHfUqpdE3keilOHEtPboC4CvS1bjeeVfNkWGOOrLj+lw==", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.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==", - "devOptional": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "@opentelemetry/core": "2.11.0", + "@opentelemetry/otlp-transformer": "0.222.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@opentelemetry/api": "^1.3.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==", - "devOptional": true, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.222.0.tgz", + "integrity": "sha512-Kw5WqDBYYpCgY+edHWZS/Jp3bdguqNkCyEWOsqFhf8RmtcEuWYzPFA9i+L6UsNoJXTs8ngeQB6dwELH1czNYjA==", + "dev": true, "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.11.0", + "@opentelemetry/otlp-exporter-base": "0.222.0", + "@opentelemetry/otlp-transformer": "0.222.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.19.0 || >=20.6.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "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==", - "devOptional": true, - "license": "MIT", + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.222.0.tgz", + "integrity": "sha512-/F3BZ89+CJQnZkMh2tCrtcdB+XT2Dxhj4FFE+WPQ//413hmFL0/RfEX6vgOIWGhiSzrkHWTK3+6SiT7K5/g/jQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.222.0", + "@opentelemetry/core": "2.11.0", + "@opentelemetry/resources": "2.11.0", + "@opentelemetry/sdk-logs": "0.222.0", + "@opentelemetry/sdk-metrics": "2.11.0", + "@opentelemetry/sdk-trace": "2.11.0" + }, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.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==", - "devOptional": true, + "node_modules/@opentelemetry/resources": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.11.0.tgz", + "integrity": "sha512-Ie7+8q8MDF4FAEQCKVMTx3ReUvxiIAgIiiW3c9JdmP8+HMcDy20puT+AHjexnExgnbvBxjQ9fjkFDWrikJ2jQA==", "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^3.0.5", - "debug": "^4.3.1", - "minimatch": "^10.2.4" + "@opentelemetry/core": "2.11.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "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==", - "devOptional": true, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.222.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.222.0.tgz", + "integrity": "sha512-+19YHODIjaUCArxleaJtuufFZVpz/xvvK+VllQqE+W8hHolxdoRwHfK/s667zezwh1hkx6FFF+oYzetYgqK+Bg==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.2.1" + "@opentelemetry/api-logs": "0.222.0", + "@opentelemetry/core": "2.11.0", + "@opentelemetry/resources": "2.11.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "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==", - "devOptional": true, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.11.0.tgz", + "integrity": "sha512-7GXXcObyHyDUUSG+L+kJoquty01bzm7ivE7+SSgXXJcHuPzGviptxwARmI2c+bnnxjexGQbJnyNlN8HxBP/Y7A==", "license": "Apache-2.0", "dependencies": { - "@types/json-schema": "^7.0.15" + "@opentelemetry/core": "2.11.0", + "@opentelemetry/resources": "2.11.0" }, "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==", - "license": "MIT", - "peer": true, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" + "node": "^18.19.0 || >=20.6.0" }, "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==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, - "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==", - "devOptional": true, + "node_modules/@opentelemetry/sdk-trace": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.11.0.tgz", + "integrity": "sha512-fFnTqGm8/G73GQVnxYi7LXa1ZVYEUvgL6XI1LpvV0bPC7WQ/ZGgKxCSl8FnlZBKto9JHHEFTO6s6CUpvvtwFrA==", "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.2.1", - "levn": "^0.4.1" + "@opentelemetry/core": "2.11.0", + "@opentelemetry/resources": "2.11.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "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" + "node": "^18.19.0 || >=20.6.0" }, - "engines": { - "node": ">=12.10.0" + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.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, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.11.0.tgz", + "integrity": "sha512-H19x/TX/LZdqiYOjM7fqtSxwlplC5pgelavqbQdHbhdq0q/AI/TGkM2dfGuuynTXmJPeF2HoZVoPDu+TGoW78A==", "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" + "@opentelemetry/core": "2.11.0", + "@opentelemetry/resources": "2.11.0", + "@opentelemetry/sdk-trace": "2.11.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=6" - } - }, - "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==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/types": "^0.15.0" + "node": "^18.19.0 || >=20.6.0" }, - "engines": { - "node": ">=18.18.0" + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.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==", - "devOptional": true, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.11.0.tgz", + "integrity": "sha512-CuvCMJmZxswhNLlM2LfuLOW3h3fZujA4hsG4B+Sz4dX2zvaXO8Ng74cnDHWD64gLszTlhiG3c0iNUjj4g+0/sA==", "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", - "@humanwhocodes/retry": "^0.4.0" + "@opentelemetry/context-async-hooks": "2.11.0", + "@opentelemetry/core": "2.11.0", + "@opentelemetry/sdk-trace-base": "2.11.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==", - "devOptional": 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==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" + "node": "^18.19.0 || >=20.6.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "devOptional": true, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", "license": "Apache-2.0", "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" + "node": ">=14" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/@oxc-project/types": { + "version": "0.149.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz", + "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/sponsors/oxc-project" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/@oxfmt/binding-android-arm-eabi": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.63.0.tgz", + "integrity": "sha512-YmRth4ZPGgEXcgmkhvANbC9uD67dxmSobW7DQuyt5tOBOKvPnIpk5SVHBj88E+7wMNRI2FhqaDbOhQFBix+b8A==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@oxfmt/binding-android-arm64": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.63.0.tgz", + "integrity": "sha512-icbahX8X2X3sRamOMecvdYeZXWjPDazRDIfvWfy7Ca1nc/ZDT2Y9k5Nt7s46EqFd7NQPdgk+CM3/SgIT5LPCaQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/@oxfmt/binding-darwin-arm64": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.63.0.tgz", + "integrity": "sha512-WV+Ze5v5gI2qoj8jpAovt8KBTW8pjEz/AiMXXjeTQS+Bmf/MmZXTS40S8xNPDszX+W8WDv2Bbk6qKrMTtUGu1A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@oxfmt/binding-darwin-x64": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.63.0.tgz", + "integrity": "sha512-CJGSBdDxXOWIpoFXHpverimCvz084KA7L483rqJ44c3jDtzv6d4qOSoR/V9ywSHfV+Ks1lwIj2P49BFhunLNAA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "node_modules/@oxfmt/binding-freebsd-x64": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.63.0.tgz", + "integrity": "sha512-BDfKY+KhL2078cgswBBFQPAYuxCy93bS/iC5frdSeSbTLcGrR6VC2hsuPTanoJmg84+wSyWl0wWC1eR+uTnkRg==", + "cpu": [ + "x64" + ], "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" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/@oxfmt/binding-linux-arm-gnueabihf": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.63.0.tgz", + "integrity": "sha512-Ov1cQEXT4mj7cojAokWSS1eoxkoyvbDfAbxNsGIKY2o36kvdAaFzPxRN6NxFRk9fD72B8oCoTTX/NuYTUWlpsg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/@oxfmt/binding-linux-arm-musleabihf": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.63.0.tgz", + "integrity": "sha512-0LE7ro3+6L79jcMANycAZfRaC7zxr9YZ2+vEL5uMD9QlEep+rS/r1kSJsnuLl991NXJZD60euh0PC1GHrR20vw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/@oxfmt/binding-linux-arm64-gnu": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.63.0.tgz", + "integrity": "sha512-izPk+2Z4gjuZK32Fqh5qXoMpT/2NXzLh++ob57HiEiVSQZ1iYXu8EKMzb+K5AvWyIEXhdDIt7ADjGGtFhkT9Bw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/@oxfmt/binding-linux-arm64-musl": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.63.0.tgz", + "integrity": "sha512-alPmbOuWXFXiSo+lOtv6X71C7SYMEDW2WVvywOvf9BwKgEhSNGhMTLeFVSjKUMCamcjbbgVdsWF8GN1uy8xshg==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", - "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "node_modules/@oxfmt/binding-linux-ppc64-gnu": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.63.0.tgz", + "integrity": "sha512-BdzCPvolJc4AWZ+YMzgUDJcDzbQWrFjYuqBHoNHNqP1aCaluQRJNs4k3vNU5IG7vTpjf9zeD73D7MFM1TecZpg==", + "cpu": [ + "ppc64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/console": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", - "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", + "node_modules/@oxfmt/binding-linux-riscv64-gnu": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.63.0.tgz", + "integrity": "sha512-7sIgfLzqtNKSkMGsGVyRpHwpjNezRg2XONvUOheFZs95TSZpM0JAuPpA8KrQFsWc4wPU95roX2O69JgH8igOgw==", + "cpu": [ + "riscv64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "slash": "^3.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/core": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", - "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", + "node_modules/@oxfmt/binding-linux-riscv64-musl": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.63.0.tgz", + "integrity": "sha512-9Tcg0y0WcVa6Mm9AgcgFMseDS+VkFJZpKZ8We9SpDY4gg5jewSwln+0sO04QLcTS1BtfDl9MwR+NfID8L7PUTg==", + "cpu": [ + "riscv64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@jest/console": "30.4.1", - "@jest/pattern": "30.4.0", - "@jest/reporters": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.4.1", - "jest-config": "30.4.2", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-resolve-dependencies": "30.4.2", - "jest-runner": "30.4.2", - "jest-runtime": "30.4.2", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "jest-watcher": "30.4.1", - "pretty-format": "30.4.1", - "slash": "^3.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/diff-sequences": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", - "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "node_modules/@oxfmt/binding-linux-s390x-gnu": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.63.0.tgz", + "integrity": "sha512-qWKC1pEOpx1qYhXaugPhHUeXwSfqEOk2wJH2LqVXGPV5iQYfdAZdt+d2XDiX4DTSWA2QDMUcFB+wEORh3Xn/sA==", + "cpu": [ + "s390x" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/environment": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", - "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", + "node_modules/@oxfmt/binding-linux-x64-gnu": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.63.0.tgz", + "integrity": "sha512-S9wXYOiGSqYGS4Fx/TFsY+xDd/7dE5s+rUgbA4TsHiVF9e8J3ZcKmP7dsP/7iqLI9Wz7Ic7TzEr3mdthRCTdrA==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "jest-mock": "30.4.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", + "node_modules/@oxfmt/binding-linux-x64-musl": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.63.0.tgz", + "integrity": "sha512-5eGyTJuMZNwBSHCivXt8Yuta6GeTYksOPXRk2MIhajiyFGQx7bjaHIwY+ZusAoFHhT157A9x6sktLjYo9D5oMQ==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "expect": "30.4.1", - "jest-snapshot": "30.4.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/expect-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", - "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", + "node_modules/@oxfmt/binding-openharmony-arm64": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.63.0.tgz", + "integrity": "sha512-Rz7hx+Dv3DoW/S6pwVAyjfFXp7/trdQ1zg+vNmsdsdDNlUccugp4XNqambSuEAeP0DaG9k72AtNyfDXCEg0AGw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0" - }, + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/fake-timers": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", - "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", + "node_modules/@oxfmt/binding-win32-arm64-msvc": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.63.0.tgz", + "integrity": "sha512-T/IuizKN9mr4Xw6YYnptkXRNdLkyIlUZ7c8zfTOBpoytZyJ1BAsMUvsMDEx0X4YvSMpaivm+DR8112rQfzC25g==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "@sinonjs/fake-timers": "^15.4.0", - "@types/node": "*", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "node_modules/@oxfmt/binding-win32-ia32-msvc": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.63.0.tgz", + "integrity": "sha512-XjrO5FJ5Wl9vsAxtCP1G/eaeT6y1K2s9CICUHGE42cEjou32/J6S+B1KnrOAboj6E7uhJnwPbRSvznWcxNdA0g==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/globals": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", - "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", + "node_modules/@oxfmt/binding-win32-x64-msvc": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.63.0.tgz", + "integrity": "sha512-sgsHCQy432OTQH4Ikk3tZptp3GqwnhwUDuY0loBH41zyHWfMZY9v8Dy78wsnSofHejvFozZGgJgBB1A0LQRwMQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/types": "30.4.1", - "jest-mock": "30.4.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/pattern": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", - "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "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": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "license": "BSD-3-Clause" }, - "node_modules/@jest/reporters": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", - "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", + "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": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } + "license": "BSD-3-Clause" }, - "node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "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": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "license": "BSD-3-Clause" }, - "node_modules/@jest/snapshot-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", - "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", + "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": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "license": "BSD-3-Clause" }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "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": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "@protobufjs/aspromise": "^1.1.1" } }, - "node_modules/@jest/test-result": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", - "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", + "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": "MIT", - "dependencies": { - "@jest/console": "30.4.1", - "@jest/types": "30.4.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "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/@jest/test-sequencer": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", - "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", + "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/@rolldown/binding-android-arm-eabi": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz", + "integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/test-result": "30.4.1", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "slash": "^3.0.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/transform": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", - "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz", + "integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jest/types": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", - "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.8.tgz", + "integrity": "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/pattern": "30.4.0", - "@jest/schemas": "30.4.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^20.19.0 || >=22.12.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==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz", + "integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz", + "integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz", + "integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0.0" + "node": "^20.19.0 || >=22.12.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==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz", + "integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz", + "integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", - "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz", + "integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==", + "cpu": [ + "ppc64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + "node": "^20.19.0 || >=22.12.0" } }, - "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==", - "license": "Apache-2.0", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz", + "integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/api-logs": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.219.0.tgz", - "integrity": "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz", + "integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.10.0.tgz", - "integrity": "sha512-bvyMcgLEkozzSzpEEEo1OMoeQ97bxj6Qs2uN3mPrSdDvObMI1myffD/BPqcLlzZO9//d1SqQA/WPw7Cz2AiqhA==", - "license": "Apache-2.0", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz", + "integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/core": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.10.0.tgz", - "integrity": "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz", + "integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.219.0.tgz", - "integrity": "sha512-6LaaSrPxK5L55bXevWajvOMxGOpNm0n12tG53TeZaUeNzXwLPg6d2KCC1zAlGsojan+xRG71mA4Qqs9K2VVrKQ==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz", + "integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.8.0", - "@opentelemetry/exporter-metrics-otlp-http": "0.219.0", - "@opentelemetry/otlp-exporter-base": "0.219.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.219.0", - "@opentelemetry/otlp-transformer": "0.219.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/sdk-metrics": "2.8.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", - "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz", + "integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/resources": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", - "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } + "license": "MIT" }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", - "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", + "node_modules/@sap-cloud-sdk/connectivity": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@sap-cloud-sdk/connectivity/-/connectivity-4.9.1.tgz", + "integrity": "sha512-0deJBmdUd1LD1O3CX/wqYjTEKmnrnLsxnrSE8CyGtzbyomjjL1ePEXB3g6QUpQKmClVyOgJG/IbpyJFGGXXL5A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" + "@sap-cloud-sdk/resilience": "^4.9.1", + "@sap-cloud-sdk/util": "^4.9.1", + "@sap/xsenv": "^6.2.0", + "@sap/xssec": "^4.13.0", + "async-retry": "^1.3.3", + "axios": "^1.15.0", + "jks-js": "^1.1.6", + "jsonwebtoken": "^9.0.3", + "safe-stable-stringify": "^2.5.0" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.219.0.tgz", - "integrity": "sha512-6CaDRbMVHZSDWzNXwrR8y/H4B/Z1eMNnkHiPQlTx3Ojz2OHY4X/aff/UC4P/3pHUQSuTfi3oh2UsPPZppw+Vrg==", + "node_modules/@sap-cloud-sdk/http-client": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@sap-cloud-sdk/http-client/-/http-client-4.9.1.tgz", + "integrity": "sha512-bsF8T/syitm+GI8eInOB1AfvHsTYJ8Qvg5dfGt5SGXEQjqV2amFEulPp7edNTzCLo0+BBbBa/jhvqLCvXSoV6A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/otlp-exporter-base": "0.219.0", - "@opentelemetry/otlp-transformer": "0.219.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/sdk-metrics": "2.8.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@sap-cloud-sdk/connectivity": "^4.9.1", + "@sap-cloud-sdk/resilience": "^4.9.1", + "@sap-cloud-sdk/util": "^4.9.1", + "axios": "^1.15.0" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", - "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "node_modules/@sap-cloud-sdk/resilience": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@sap-cloud-sdk/resilience/-/resilience-4.9.1.tgz", + "integrity": "sha512-14Hdd2VqEd3BarikR/ChZjquQuelC+CP69HKdMycADmmsuQEwaJkSXURW8cDl19bfx8Re1ldM/4Ig0gbCY4x/A==", "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" + "@sap-cloud-sdk/util": "^4.9.1", + "async-retry": "^1.3.3", + "axios": "^1.15.0", + "opossum": "^10.0.0" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", - "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "node_modules/@sap-cloud-sdk/util": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@sap-cloud-sdk/util/-/util-4.9.1.tgz", + "integrity": "sha512-1tuv+c6o2Y8/1F3UEdqiv3TPWzZDpCjxuDpAFtFBM8C+weum8ixgwc2YXEWvBzWvtSEl7C5L/o8jBqiuhcXF4w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "axios": "^1.15.0", + "logform": "^2.7.0", + "voca": "^1.4.1", + "winston": "^3.19.0", + "winston-transport": "^4.9.0" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", - "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@sap/cds": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@sap/cds/-/cds-10.1.0.tgz", + "integrity": "sha512-Eg8UwRcZ0iJp8JpYad6sFnlI2ByPzmSNWFlexyeo2I2XvsumLFc+NFsPOS45TPRrj8eRfoKgjaI+MyKcRk1AxQ==", + "license": "SEE LICENSE IN LICENSE", + "peer": true, "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0" + "@sap/cds-compiler": "^7", + "@sap/cds-fiori": "^2", + "express": "^4.22.1 || ^5", + "yaml": "^2" + }, + "bin": { + "cds-deploy": "bin/deploy.js", + "cds-serve": "bin/serve.js" }, "engines": { - "node": "^18.19.0 || >=20.6.0" + "node": ">=22" }, "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" + "@eslint/js": "^9 || ^10", + "tar": "^7.5.6" + }, + "peerDependenciesMeta": { + "tar": { + "optional": true + } } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.219.0.tgz", - "integrity": "sha512-DUS7XyIiEnoeccQUvuKy0G2/YqeKhpN8FVIrGbrLNIVMj10yeIFLRzRv0tibCI2kXXvlTTABVexGAk78wHk2ug==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/exporter-metrics-otlp-http": "0.219.0", - "@opentelemetry/otlp-exporter-base": "0.219.0", - "@opentelemetry/otlp-transformer": "0.219.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/sdk-metrics": "2.8.0" + "node_modules/@sap/cds-compiler": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@sap/cds-compiler/-/cds-compiler-7.1.1.tgz", + "integrity": "sha512-ep7rzRJgZtDBs9vVcXyxLVy/q4p+K+a1Q+yq1qn1mb7dYHyH7m88K+Pn761Ofnf7HXXbTo/FKpM1z/kPTbwy3w==", + "license": "SEE LICENSE IN LICENSE", + "peer": true, + "bin": { + "cdsc": "bin/cdsc.js", + "cdshi": "bin/cdshi.js", + "cdsse": "bin/cdsse.js" }, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, + "node": ">=22" + } + }, + "node_modules/@sap/cds-fiori": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sap/cds-fiori/-/cds-fiori-2.3.0.tgz", + "integrity": "sha512-6oWov+DSpFrSTgxXR0dZhak6aZ/IVRZvaHERMi0EgSTzIJdlvZlpw3Kf18ePMcTrRrtEXwD4RIjKt8pbs0g2Hg==", + "license": "SEE LICENSE IN LICENSE", + "peer": true, "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@sap/cds": ">=8" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", - "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "node_modules/@sap/cds-mtxs": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@sap/cds-mtxs/-/cds-mtxs-4.1.0.tgz", + "integrity": "sha512-CWkHjmEiWchyIgaW5jGnrMGNbuJJSLSlC1cYZJaBBQVzm4p4j3AI10BKHm13scTumpYgRNti2KlWSUNE4WcpTA==", "dev": true, - "license": "Apache-2.0", + "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" + "@sap/hdi-deploy": "^5" }, - "engines": { - "node": "^18.19.0 || >=20.6.0" + "bin": { + "cds-mtx": "bin/cds-mtx.js", + "cds-mtx-migrate": "bin/cds-mtx-migrate.js" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "@sap/cds": ">=9" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/resources": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", - "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "node_modules/@sap/hdi": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@sap/hdi/-/hdi-4.8.1.tgz", + "integrity": "sha512-LfRtIbtfvMl82a8CwpqPJRRB9hRSVUQyvxualYBEzyNu/phuw6bFGQA5LHVngHsms+y797Icm4tl4X+TEkTtlg==", "dev": true, - "license": "Apache-2.0", + "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" + "async": "^3.2.3" }, "engines": { - "node": "^18.19.0 || >=20.6.0" + "node": ">=18" }, "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "@sap/hana-client": "^2 >= 2.5", + "hdb": "^2 || ^0" + }, + "peerDependenciesMeta": { + "@sap/hana-client": { + "optional": true + }, + "hdb": { + "optional": true + } } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", - "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", + "node_modules/@sap/hdi-deploy": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@sap/hdi-deploy/-/hdi-deploy-5.7.0.tgz", + "integrity": "sha512-ENBCtCBnJEKBlvC5mQT/G7OAteOENo7ygSeRJgVdz7AHikcHUxegtavXkJtFCJ/PSjpjytFKfGRmG8peUVAmNQ==", "dev": true, - "license": "Apache-2.0", + "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0" + "@sap/hdi": "^4.8.1", + "@sap/xsenv": "^6.0.0", + "async": "^3.2.6", + "dotenv": "^16.4.5", + "handlebars": "^4.7.9", + "micromatch": "^4.0.8" }, "engines": { - "node": "^18.19.0 || >=20.6.0" + "node": ">=18.x" }, "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" + "@sap/hana-client": "^2 >= 2.6", + "hdb": "^2 || ^0" + }, + "peerDependenciesMeta": { + "@sap/hana-client": { + "optional": true + }, + "hdb": { + "optional": true + } } }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.219.0.tgz", - "integrity": "sha512-BkDNv1UD6BscW19MxbAxVmSYSSFuyeqR6buV2/HTYqA7GrR0EbTFzqG6h86T3PtXmpdbsWjMGLDdjG2rikG27Q==", + "node_modules/@sap/xsenv": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@sap/xsenv/-/xsenv-6.2.2.tgz", + "integrity": "sha512-SGn+yaVKeXND/rQaQhqTZC+M6g5ASW1YnqAdQiZqNL74E1T1kUsDHttgOjQNkLObMZ0fsxgQxRPMnuVcAgIODg==", "dev": true, - "license": "Apache-2.0", + "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.8.0", - "@opentelemetry/otlp-exporter-base": "0.219.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.219.0", - "@opentelemetry/otlp-transformer": "0.219.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/sdk-trace-base": "2.8.0" + "debug": "4.4.3", + "node-cache": "^5.1.2", + "verror": "1.10.1" }, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", - "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "node_modules/@sap/xssec": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@sap/xssec/-/xssec-4.15.0.tgz", + "integrity": "sha512-I9TMMaWq4BNDNt9blJy8o2FV9uXjs2RyNTT7KBv9fK83bGoQxcipQ8e4DrxYb296GXg+HhE420kHuqmyXZSOuQ==", "dev": true, - "license": "Apache-2.0", + "license": "SAP DEVELOPER LICENSE AGREEMENT", "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" + "debug": "^4.4.3", + "jwt-decode": "^4" }, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "node": ">=18" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/resources": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", - "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "color": "^5.0.2", + "text-hex": "1.0.x" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz", - "integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==", + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, - "license": "Apache-2.0", + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.219.0.tgz", - "integrity": "sha512-lF/LUBfhOFmxJa+SQsLN7ziV4MHa2pyKgOM6JNehSOfU+npjM4gwm9oIKEJrzrWcexMcqydiyoFy0XCb1Ql3wQ==", + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT" + }, + "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==", + "devOptional": 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==", + "devOptional": true, + "license": "MIT" + }, + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.1.tgz", + "integrity": "sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==", + "dev": true, + "license": "MIT", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/otlp-exporter-base": "0.219.0", - "@opentelemetry/otlp-transformer": "0.219.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/sdk-trace-base": "2.8.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "undici-types": "~8.9.0" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", - "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "dev": true, + "license": "MIT", "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/resources": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", - "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "node_modules/@vitest/mocker": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" + "@vitest/spy": "4.1.11", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" }, - "engines": { - "node": "^18.19.0 || >=20.6.0" + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz", - "integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==", + "node_modules/@vitest/pretty-format": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" + "tinyrainbow": "^3.1.0" }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@opentelemetry/instrumentation": { - "version": "0.221.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.221.0.tgz", - "integrity": "sha512-cCk80Z/iRDf/5gfsKMB4f74LqVA5yKETB/9ojPzVW/6/f70iu89nJvGxsFCxx4XfSohaOofkU19kiYm84AiAlw==", - "license": "Apache-2.0", + "node_modules/@vitest/runner": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", + "dev": true, + "license": "MIT", "dependencies": { - "@opentelemetry/api-logs": "0.221.0", - "import-in-the-middle": "^3.0.0", - "require-in-the-middle": "^8.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" + "@vitest/utils": "4.1.11", + "pathe": "^2.0.3" }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@opentelemetry/instrumentation-host-metrics": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-host-metrics/-/instrumentation-host-metrics-0.2.0.tgz", - "integrity": "sha512-NIttCEOLdg1ebbDiJpCf0Ly1OGIa10isesik+K2dnXy2P99q4muUFjpaLtTnhkENrt9SmR0Zrxzq7B+W/VNWyw==", + "node_modules/@vitest/snapshot": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/instrumentation": "^0.219.0", - "systeminformation": "^5.31.6" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@opentelemetry/instrumentation-host-metrics/node_modules/@opentelemetry/instrumentation": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.219.0.tgz", - "integrity": "sha512-X5t7I8GyIO9rmGHwoedZLREpQqrF1WW2nxzNNym6HOKpFiE+rvqV3ngC0xcZVO2YwIGf3KKmRdWrYwdwz3H9RQ==", + "node_modules/@vitest/spy": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@opentelemetry/api-logs": "0.219.0", - "import-in-the-middle": "^3.0.0", - "require-in-the-middle": "^8.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" + "@vitest/pretty-format": "4.1.11", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.221.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.221.0.tgz", - "integrity": "sha512-oIP91CPIANuYr09tGFElPFKAh6JUar+awJf1kBRYlaeo9b0gDwZHEB2zBfFlvdNFHm0wAVutMZODVi5smKT30g==", - "license": "Apache-2.0", + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "peer": true, "dependencies": { - "@opentelemetry/core": "2.10.0", - "@opentelemetry/instrumentation": "0.221.0", - "@opentelemetry/semantic-conventions": "^1.29.0", - "forwarded-parse": "2.1.2" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">= 0.6" } }, - "node_modules/@opentelemetry/instrumentation-runtime-node": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-runtime-node/-/instrumentation-runtime-node-0.32.0.tgz", - "integrity": "sha512-Jo1jSgrHlah3lPpGNPsIpF0q52D5uSLRJrztWUoPc1/Tli2ZWZ+cArgNtcdmiLuKhW21MwYbbcrNw1fbOPeR3A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "^0.219.0", - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.219.0" + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=0.4.0" } }, - "node_modules/@opentelemetry/instrumentation-runtime-node/node_modules/@opentelemetry/instrumentation": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.219.0.tgz", - "integrity": "sha512-X5t7I8GyIO9rmGHwoedZLREpQqrF1WW2nxzNNym6HOKpFiE+rvqV3ngC0xcZVO2YwIGf3KKmRdWrYwdwz3H9RQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.219.0", - "import-in-the-middle": "^3.0.0", - "require-in-the-middle": "^8.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.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==", + "devOptional": true, + "license": "MIT", "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@opentelemetry/instrumentation-undici": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.31.0.tgz", - "integrity": "sha512-qunCfgSFV+bjRdAYkWIjVX38jIN/Xj80CiERXXdzYAmdigCFvJPB3AY3j43bjJlkhgbJJSCGdbvQUSut8QIiyQ==", - "license": "Apache-2.0", + "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": { - "@opentelemetry/core": "^2.9.0", - "@opentelemetry/instrumentation": "^0.221.0", - "@opentelemetry/semantic-conventions": "^1.24.0" + "debug": "4" }, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.7.0" + "node": ">= 6.0.0" } }, - "node_modules/@opentelemetry/instrumentation/node_modules/@opentelemetry/api-logs": { - "version": "0.221.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.221.0.tgz", - "integrity": "sha512-OlanaW1vv7ufTqQ3/fPLI4arGt5ZoM+P8abOMki6uEYnpRazepSWDwDnnw+la7kE26SHVC18//SMccrDvLKOXQ==", - "license": "Apache-2.0", + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "devOptional": true, + "license": "MIT", "dependencies": { - "@opentelemetry/api": "^1.3.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=8.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.219.0.tgz", - "integrity": "sha512-zvIxQX/AZUVKDU+hCuYx+7UkiP7GRdnk1ZbFQRYzHvYp47cAWR4j3IhoPhV9KaeXEv2xdGq3IA6PnpzDmLcmSA==", + "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": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/otlp-transformer": "0.219.0" - }, + "license": "MIT", "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=8" } }, - "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", - "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.19.0 || >=20.6.0" + "node": ">=8" }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.219.0.tgz", - "integrity": "sha512-iIk/s8QQu39zpTrRRmsW/Eg3SE2+Hg8tLWepr2FLRgmwUpNd0IpCTLJEHJ77hpt4hgIS8MAh44UYI4xQPZwWlw==", + "node_modules/ansi-styles/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.8.0", - "@opentelemetry/otlp-exporter-base": "0.219.0", - "@opentelemetry/otlp-transformer": "0.219.0" + "color-name": "~1.1.4" }, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=7.0.0" } }, - "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", - "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "node_modules/ansi-styles/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "safer-buffer": "~2.1.0" } }, - "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.219.0.tgz", - "integrity": "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ==", + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.219.0", - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/sdk-logs": "0.219.0", - "@opentelemetry/sdk-metrics": "2.8.0", - "@opentelemetry/sdk-trace-base": "2.8.0" - }, + "license": "MIT", "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=0.8" } }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", - "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, + "license": "MIT", "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "node": ">=12" } }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", - "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "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/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "retry": "0.13.1" } }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", - "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, - "license": "Apache-2.0", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "dev": true, + "license": "MIT", "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0" - }, + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "devOptional": true, + "license": "MIT", "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" + "node": "18 || 20 || >=22" } }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz", - "integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "peer": true, "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { - "node": "^18.19.0 || >=20.6.0" + "node": ">=18" }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@opentelemetry/resources": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.10.0.tgz", - "integrity": "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.10.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "peer": true, "engines": { - "node": "^18.19.0 || >=20.6.0" + "node": ">=18" }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@opentelemetry/sdk-logs": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.219.0.tgz", - "integrity": "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "devOptional": true, + "license": "MIT", "dependencies": { - "@opentelemetry/api-logs": "0.219.0", - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" + "node": "20 || >=22" } }, - "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", - "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" + "fill-range": "^7.1.1" }, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "node": ">=8" } }, - "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", - "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "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": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.8.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-metrics": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.10.0.tgz", - "integrity": "sha512-t6r1VSvXNtSDnPXU1FbZeetJb7yyovHmgu0wRSoftxtE0g2rSNhQZQUy69sRUCL+iioJpX8SN/S6wq6ZtvLySQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.10.0", - "@opentelemetry/resources": "2.10.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.10.0.tgz", - "integrity": "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.10.0", - "@opentelemetry/resources": "2.10.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.10.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.10.0.tgz", - "integrity": "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.10.0", - "@opentelemetry/resources": "2.10.0", - "@opentelemetry/sdk-trace": "2.10.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-node": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.10.0.tgz", - "integrity": "sha512-GZK/G6oZyBLGlH1pUgeDch7D91KoHd2uotUGIkWCPi9GI5T9X0p4L7nNAMDR1BQjkRYoDqo+ddfVx9t5Uhys+Q==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/context-async-hooks": "2.10.0", - "@opentelemetry/core": "2.10.0", - "@opentelemetry/sdk-trace-base": "2.10.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.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", - "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", - "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@sap/cds": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@sap/cds/-/cds-10.0.3.tgz", - "integrity": "sha512-S9q8vcJXzIsO4KC49sb9JLIhY/k0MJTZcgEOzmhvoBW/lWLLR79Oci0xmyQqSxJwwzjavR9mW8I4wbMJQQMVAA==", - "license": "SEE LICENSE IN LICENSE", - "peer": true, - "dependencies": { - "@sap/cds-compiler": "^7", - "@sap/cds-fiori": "^2", - "express": "^4.22.1 || ^5", - "yaml": "^2" - }, - "bin": { - "cds-deploy": "bin/deploy.js", - "cds-serve": "bin/serve.js" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "@eslint/js": "^9 || ^10", - "tar": "^7.5.6" - }, - "peerDependenciesMeta": { - "tar": { - "optional": true - } - } - }, - "node_modules/@sap/cds-compiler": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sap/cds-compiler/-/cds-compiler-7.0.1.tgz", - "integrity": "sha512-Qwk9jitwSSwPB9FTB/Q6gYPxFsJxswhfsO9Ux77HTxjIhse8O/Hlq2XzkpVSTyaAVr8Lo7wLfpytHwKQScZTwQ==", - "license": "SEE LICENSE IN LICENSE", - "peer": true, - "bin": { - "cdsc": "bin/cdsc.js", - "cdshi": "bin/cdshi.js", - "cdsse": "bin/cdsse.js" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@sap/cds-fiori": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sap/cds-fiori/-/cds-fiori-2.3.0.tgz", - "integrity": "sha512-6oWov+DSpFrSTgxXR0dZhak6aZ/IVRZvaHERMi0EgSTzIJdlvZlpw3Kf18ePMcTrRrtEXwD4RIjKt8pbs0g2Hg==", - "license": "SEE LICENSE IN LICENSE", - "peer": true, - "peerDependencies": { - "@sap/cds": ">=8" - } - }, - "node_modules/@sap/cds-mtxs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sap/cds-mtxs/-/cds-mtxs-4.0.1.tgz", - "integrity": "sha512-H+UygJY57D3be40X4TrgtLBVZCph9G8LAn+5VWNdL7lEWFMCqZM0bNj1AogmJXEUtlWOUWK98aznlsErY7UVaQ==", - "dev": true, - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@sap/hdi-deploy": "^5" - }, - "bin": { - "cds-mtx": "bin/cds-mtx.js", - "cds-mtx-migrate": "bin/cds-mtx-migrate.js" - }, - "peerDependencies": { - "@sap/cds": ">=9" - } - }, - "node_modules/@sap/hdi": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@sap/hdi/-/hdi-4.8.1.tgz", - "integrity": "sha512-LfRtIbtfvMl82a8CwpqPJRRB9hRSVUQyvxualYBEzyNu/phuw6bFGQA5LHVngHsms+y797Icm4tl4X+TEkTtlg==", - "dev": true, - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "async": "^3.2.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@sap/hana-client": "^2 >= 2.5", - "hdb": "^2 || ^0" - }, - "peerDependenciesMeta": { - "@sap/hana-client": { - "optional": true - }, - "hdb": { - "optional": true - } - } - }, - "node_modules/@sap/hdi-deploy": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@sap/hdi-deploy/-/hdi-deploy-5.7.0.tgz", - "integrity": "sha512-ENBCtCBnJEKBlvC5mQT/G7OAteOENo7ygSeRJgVdz7AHikcHUxegtavXkJtFCJ/PSjpjytFKfGRmG8peUVAmNQ==", - "dev": true, - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@sap/hdi": "^4.8.1", - "@sap/xsenv": "^6.0.0", - "async": "^3.2.6", - "dotenv": "^16.4.5", - "handlebars": "^4.7.9", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=18.x" - }, - "peerDependencies": { - "@sap/hana-client": "^2 >= 2.6", - "hdb": "^2 || ^0" - }, - "peerDependenciesMeta": { - "@sap/hana-client": { - "optional": true - }, - "hdb": { - "optional": true - } - } - }, - "node_modules/@sap/xsenv": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@sap/xsenv/-/xsenv-6.2.1.tgz", - "integrity": "sha512-R1p7VdD3N3jvdkL8av4vLqF+cTQihTz9mCqqF+oa9rVZvgLaCb4ODyZ1dln5/fBgg1OSuch0ESxu3AqZrXVknw==", - "dev": true, - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "debug": "4.4.3", - "node-cache": "^5.1.2", - "verror": "1.10.1" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || ^24.0.0" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.52", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", - "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", - "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "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/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "devOptional": 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==", - "devOptional": 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==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", - "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "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/@ungap/structured-clone": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", - "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", - "dev": true, - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", - "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", - "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", - "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", - "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", - "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", - "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", - "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", - "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", - "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", - "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-loong64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", - "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", - "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", - "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", - "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", - "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", - "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", - "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-openharmony-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", - "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", - "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", - "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", - "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", - "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "peer": true, - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, - "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==", - "devOptional": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "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/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "devOptional": 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==", - "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/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "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/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", - "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/babel-jest": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", - "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "30.4.1", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.4.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", - "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", - "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/babel__core": "^7.20.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", - "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "30.4.0", - "babel-preset-current-node-syntax": "^1.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", - "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "peer": true, - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", - "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.44", - "caniuse-lite": "^1.0.30001806", - "electron-to-chromium": "^1.5.393", - "node-releases": "^2.0.51", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "peer": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001807", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001807.tgz", - "integrity": "sha512-daRXJ9EB/rdRgu7kV+TTl1YUKtlsMWblPl2sLnpg9DZae16QCegol6A1SmCE31Lm9mXC1sRWGt/krouH+/dl7Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/chai-as-promised": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-8.0.2.tgz", - "integrity": "sha512-1GadL+sEJVLzDjcawPM4kjfnL+p/9vrxiEUonowKOAzvVg0PixJUdtuDzdkDeQhK3zfOE76GqGkZIQ7/Adcrqw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "check-error": "^2.1.1" - }, - "peerDependencies": { - "chai": ">= 2.1.2 < 7" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 16" - } - }, - "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" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "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", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "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/dedent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", - "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/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" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT", - "peer": true - }, - "node_modules/electron-to-chromium": { - "version": "1.5.402", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.402.tgz", - "integrity": "sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" + "license": "BSD-3-Clause" }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "peer": true, "engines": { "node": ">= 0.8" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "devOptional": true, "license": "MIT", "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" } }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", + "peer": true, "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT", - "peer": true - }, - "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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/eslint": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", - "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", - "devOptional": true, + "node_modules/chai-as-promised": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-8.0.2.tgz", + "integrity": "sha512-1GadL+sEJVLzDjcawPM4kjfnL+p/9vrxiEUonowKOAzvVg0PixJUdtuDzdkDeQhK3zfOE76GqGkZIQ7/Adcrqw==", + "dev": true, "license": "MIT", - "workspaces": [ - "packages/*" - ], + "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" + "check-error": "^2.1.1" }, "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", - "devOptional": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "devOptional": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "chai": ">= 2.1.2 < 7" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, + "license": "MIT", + "peer": true, "engines": { - "node": ">=4" + "node": ">= 16" } }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "devOptional": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } + "node_modules/cjs-module-lexer": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.1.tgz", + "integrity": "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==", + "license": "MIT" }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "devOptional": true, - "license": "BSD-2-Clause", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", "dependencies": { - "estraverse": "^5.2.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "devOptional": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "node": ">=12" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "devOptional": true, - "license": "BSD-2-Clause", + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=0.8" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "color-name": "^2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">=14.6" } }, - "node_modules/execa/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==", + "node_modules/color-name": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.1.tgz", + "integrity": "sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=12.20" + } }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", "dev": true, "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, "engines": { - "node": ">= 0.8.0" + "node": ">=18" } }, - "node_modules/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" + "delayed-stream": "~1.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.8" } }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", "peer": true, - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, "engines": { - "node": ">= 18" + "node": ">=18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/extsprintf": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", - "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "devOptional": true, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, + "peer": true, "engines": { - "node": ">=16.0.0" + "node": ">= 0.6" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, + "peer": true, "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "peer": true, - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=6.6.0" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "devOptional": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 8" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "devOptional": true, + "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": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "ms": "^2.1.3" }, "engines": { - "node": ">=16" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "devOptional": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], "license": "MIT", "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">=14" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://dotenvx.com" } }, - "node_modules/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", - "dev": true, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" + "node": ">= 0.4" } }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "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": "MIT", + "license": "Apache-2.0", "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" + "safe-buffer": "^5.0.1" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "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", - "peer": true, - "engines": { - "node": ">= 0.6" - } + "peer": true }, - "node_modules/forwarded-parse": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", - "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "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/fresh": { + "node_modules/enabled": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "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", "peer": true, "engines": { "node": ">= 0.8" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" } }, - "node_modules/function-bind": { + "node_modules/es-module-lexer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-3.0.2.tgz", + "integrity": "sha512-BuIB67FngDSyQ/dpQNOZybwdEBDUGJQvOqwWr4ha/ufYiqzuEwPkKO2zLhRAgay28tStRIHUeWmszZAJo3GCOg==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">= 0.4" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=6" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT", + "peer": true + }, + "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==", + "devOptional": true, "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, + "node_modules/eslint": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.10.0.tgz", + "integrity": "sha512-NPXn6r5zl4uET1DAVPaOwzX3rut4c0wcmw3dWJAfOsTM5+TogXo0DDjz8pwm/hL8cyVNpHqeK4JpN0NjnyFFNw==", + "devOptional": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "11.1.5 || >11.1.6 <12", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, "engines": { - "node": ">=8.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "devOptional": true, + "license": "BSD-2-Clause", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "devOptional": true, + "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "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", + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "devOptional": true, + "license": "BSD-2-Clause", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://opencollective.com/eslint" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "devOptional": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "is-glob": "^4.0.3" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "node": ">=0.10" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "devOptional": true, + "license": "BSD-2-Clause", "dependencies": { - "brace-expansion": "^2.0.2" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=4.0" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "devOptional": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/handlebars": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", - "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "@types/estree": "^1.0.0" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "devOptional": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", + "peer": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.6" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, + "license": "Apache-2.0", "engines": { - "node": ">= 0.4" + "node": ">=12.0.0" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "peer": true, "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.8" + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "devOptional": true, "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" + "flat-cache": "^6.1.23" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, "engines": { - "node": ">=10.17.0" + "node": ">=8" } }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "peer": true, "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "devOptional": true, "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-in-the-middle": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.2.0.tgz", - "integrity": "sha512-vR2B6HKIhaBjcZr2bLpFiJ1VbzOlRQ7aby4/gw5WPIzToLjqpfWw3VJ4sk1uDchoOODEirvO2jyrSPtUSL5CrQ==", - "license": "Apache-2.0", - "dependencies": { - "acorn": "^8.15.0", - "acorn-import-attributes": "^1.9.5", - "cjs-module-lexer": "^2.2.0", - "module-details-from-path": "^1.0.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", "devOptional": true, "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "devOptional": true, "license": "ISC" }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "dev": true, "license": "MIT" }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "devOptional": true, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "devOptional": true, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "mime-db": "1.52.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", + "peer": true, "engines": { - "node": ">=0.12.0" + "node": ">= 0.6" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT", - "peer": true + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", + "peer": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "license": "BSD-3-Clause", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, "engines": { - "node": ">=10" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "devOptional": true, + "license": "ISC", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=8" + "node": ">=10.13.0" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", - "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.4.2", - "@jest/types": "30.4.1", - "import-local": "^3.2.0", - "jest-cli": "30.4.2" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" }, "bin": { - "jest": "bin/jest.js" + "handlebars": "bin/handlebars" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.4.7" }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-changed-files": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", - "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.4.1", - "p-limit": "^3.1.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-circus": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", - "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", - "dev": true, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "devOptional": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-runtime": "30.4.2", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "p-limit": "^3.1.0", - "pretty-format": "30.4.1", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "hookified": "^1.15.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=20" } }, - "node_modules/jest-cli": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", - "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", - "dev": true, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { - "@jest/core": "30.4.2", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.4.2", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" + "function-bind": "^1.1.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">= 0.4" } }, - "node_modules/jest-config": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", - "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.4.0", - "@jest/test-sequencer": "30.4.1", - "@jest/types": "30.4.1", - "babel-jest": "30.4.1", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-circus": "30.4.2", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-runner": "30.4.2", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "parse-json": "^5.2.0", - "pretty-format": "30.4.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } - } + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "devOptional": true, + "license": "MIT" }, - "node_modules/jest-diff": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", - "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", - "dev": true, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", + "peer": true, "dependencies": { - "@jest/diff-sequences": "30.4.0", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.4.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/jest-docblock": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", - "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "license": "MIT", "dependencies": { - "detect-newline": "^3.1.0" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 6" } }, - "node_modules/jest-each": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", - "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", - "dev": true, + "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==", "license": "MIT", + "peer": true, "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", - "chalk": "^4.1.2", - "jest-util": "30.4.1", - "pretty-format": "30.4.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/jest-environment-node": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", - "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", - "dev": true, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "devOptional": true, "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-in-the-middle": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.5.1.tgz", + "integrity": "sha512-mPKuL8bPQzecui2KK6Gb+M8JvJoHnhS1FeYGa22QopBmlevF5F0FE6ued/B5EgHDeIoMTONIpDWWFKUPOG0DBQ==", + "license": "Apache-2.0", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "jest-mock": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1" + "cjs-module-lexer": "^2.2.0", + "es-module-lexer": "^3.0.2", + "module-details-from-path": "^1.0.4" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "node_modules/jest-haste-map": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", - "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", - "dev": true, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "devOptional": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", - "picomatch": "^4.0.3", - "walker": "^1.0.8" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" + "node": ">=0.8.19" } }, - "node_modules/jest-haste-map/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", + "peer": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">= 0.10" } }, - "node_modules/jest-leak-detector": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", - "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", - "dev": true, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.4.1" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-matcher-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", - "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.4.1", - "pretty-format": "30.4.1" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-message-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", - "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", - "dev": true, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.4.1", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-util": "30.4.1", - "picomatch": "^4.0.3", - "pretty-format": "30.4.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "is-extglob": "^2.1.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-message-util/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=0.12.0" } }, - "node_modules/jest-mock": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", - "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", - "dev": true, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "jest-util": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "peer": true }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" + "node": ">=8" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-regex-util": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", - "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/jks-js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/jks-js/-/jks-js-1.1.7.tgz", + "integrity": "sha512-BeiDRKsAi1NwEwgx2JB/9/0tar5BNGIv+foGm1G5GgiyR35s/iUnfd/BWqYd16mLDD8qTaAVBrIcOOuVqXJZNQ==", "dev": true, "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "dependencies": { + "node-forge": "^1.4.0", + "node-int64": "^0.4.0", + "node-rsa": "^1.1.1" } }, - "node_modules/jest-resolve": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", - "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "devOptional": true, + "license": "MIT" + }, + "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": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" + "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12", + "npm": ">=6" } }, - "node_modules/jest-resolve-dependencies": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", - "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", + "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": { - "jest-regex-util": "30.4.0", - "jest-snapshot": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "node_modules/jest-runner": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", - "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", + "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": { - "@jest/console": "30.4.1", - "@jest/environment": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-haste-map": "30.4.1", - "jest-leak-detector": "30.4.1", - "jest-message-util": "30.4.1", - "jest-resolve": "30.4.1", - "jest-runtime": "30.4.2", - "jest-util": "30.4.1", - "jest-watcher": "30.4.1", - "jest-worker": "30.4.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "node_modules/jest-runtime": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", - "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/globals": "30.4.1", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "node_modules/jest-snapshot": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", - "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", - "dev": true, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "devOptional": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.4.1", - "graceful-fs": "^4.2.11", - "jest-diff": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "pretty-format": "30.4.1", - "semver": "^7.7.2", - "synckit": "^0.11.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "@keyv/serialize": "^1.1.1" } }, - "node_modules/jest-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", - "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "devOptional": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.3" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.8.0" } }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, "engines": { - "node": ">=12" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/jest-validate": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", - "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.4.1" + "type": "opencollective", + "url": "https://opencollective.com/parcel" }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/jest-watcher": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", - "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.4.1", - "string-length": "^4.0.2" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/jest-worker": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", - "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.4.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", - "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, "node_modules/locate-path": { "version": "6.0.0", @@ -5927,67 +3835,95 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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.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.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "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/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "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": "Apache-2.0" + "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/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "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": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } + "license": "MIT" }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 12.0.0" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "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": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } + "license": "Apache-2.0" }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "tmpl": "1.0.5" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/math-intrinsics": { @@ -6000,13 +3936,17 @@ } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", "peer": true, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/merge-descriptors": { @@ -6022,13 +3962,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -6070,24 +4003,14 @@ "url": "https://opencollective.com/express" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "devOptional": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -6106,16 +4029,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/module-details-from-path": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", @@ -6128,20 +4041,23 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "bin": { - "napi-postinstall": "lib/cli.js" + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/natural-compare": { @@ -6152,13 +4068,34 @@ "license": "MIT" }, "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "license": "MIT", "peer": true, + "dependencies": { + "content-type": "^2.1.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/neo-async": { @@ -6181,6 +4118,16 @@ "node": ">= 8.0.0" } }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6188,37 +4135,14 @@ "dev": true, "license": "MIT" }, - "node_modules/node-releases": { - "version": "2.0.53", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", - "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "asn1": "^0.2.4" } }, "node_modules/object-inspect": { @@ -6234,6 +4158,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -6252,24 +4190,29 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", + "peer": true, "dependencies": { "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" - }, + "fn.name": "1.x.x" + } + }, + "node_modules/opossum": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/opossum/-/opossum-10.0.0.tgz", + "integrity": "sha512-sghtqL8Usj+et06Zui0nyn0R6FFsl7cyuoU+d7MctYU0nbS7Htzjleh+tWohFqk1Rp1srrFwbFa8Vxd0O64w6A==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^26 || ^24 || ^22" } }, "node_modules/optionator": { @@ -6290,6 +4233,58 @@ "node": ">= 0.8.0" } }, + "node_modules/oxfmt": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.63.0.tgz", + "integrity": "sha512-kgdDwv35wvVf6554U2Ab8Jnd0zTM+TsEQWwaB70RAjK3gICFAFGO+2Hd3Be27GMoXj3XRL9IKSNRVl7KBQL6iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinypool": "2.1.0" + }, + "bin": { + "oxfmt": "bin/oxfmt" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxfmt/binding-android-arm-eabi": "0.63.0", + "@oxfmt/binding-android-arm64": "0.63.0", + "@oxfmt/binding-darwin-arm64": "0.63.0", + "@oxfmt/binding-darwin-x64": "0.63.0", + "@oxfmt/binding-freebsd-x64": "0.63.0", + "@oxfmt/binding-linux-arm-gnueabihf": "0.63.0", + "@oxfmt/binding-linux-arm-musleabihf": "0.63.0", + "@oxfmt/binding-linux-arm64-gnu": "0.63.0", + "@oxfmt/binding-linux-arm64-musl": "0.63.0", + "@oxfmt/binding-linux-ppc64-gnu": "0.63.0", + "@oxfmt/binding-linux-riscv64-gnu": "0.63.0", + "@oxfmt/binding-linux-riscv64-musl": "0.63.0", + "@oxfmt/binding-linux-s390x-gnu": "0.63.0", + "@oxfmt/binding-linux-x64-gnu": "0.63.0", + "@oxfmt/binding-linux-x64-musl": "0.63.0", + "@oxfmt/binding-openharmony-arm64": "0.63.0", + "@oxfmt/binding-win32-arm64-msvc": "0.63.0", + "@oxfmt/binding-win32-ia32-msvc": "0.63.0", + "@oxfmt/binding-win32-x64-msvc": "0.63.0" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -6322,42 +4317,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -6378,16 +4337,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -6398,30 +4347,6 @@ "node": ">=8" } }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/path-to-regexp": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", @@ -6433,103 +4358,60 @@ "url": "https://opencollective.com/express" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, "engines": { - "node": ">=6" + "node": ">=8.6" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || >=14" } }, "node_modules/prelude-ls": { @@ -6542,39 +4424,10 @@ "node": ">= 0.8.0" } }, - "node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/protobufjs": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", - "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", "dev": true, "hasInstallScript": true, "license": "BSD-3-Clause", @@ -6629,21 +4482,24 @@ "node": ">=6" } }, - "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "devOptional": true, "license": "MIT" }, "node_modules/qs": { @@ -6693,21 +4549,20 @@ "node": ">= 0.10" } }, - "node_modules/react-is-18": { - "name": "react-is", - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, - "license": "MIT" - }, - "node_modules/react-is-19": { - "name": "react-is", - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", - "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/require-directory": { "version": "2.1.1", @@ -6732,27 +4587,48 @@ "node": ">=9.3.0 || >=8.10.0 <9.0.0" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true, "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 4" } }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/rolldown": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz", + "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==", "dev": true, "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.149.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.8", + "@rolldown/binding-android-arm64": "1.2.8", + "@rolldown/binding-darwin-arm64": "1.2.8", + "@rolldown/binding-darwin-x64": "1.2.8", + "@rolldown/binding-freebsd-x64": "1.2.8", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.8", + "@rolldown/binding-linux-arm64-gnu": "1.2.8", + "@rolldown/binding-linux-arm64-musl": "1.2.8", + "@rolldown/binding-linux-ppc64-gnu": "1.2.8", + "@rolldown/binding-linux-s390x-gnu": "1.2.8", + "@rolldown/binding-linux-x64-gnu": "1.2.8", + "@rolldown/binding-linux-x64-musl": "1.2.8", + "@rolldown/binding-openharmony-arm64": "1.2.8", + "@rolldown/binding-win32-arm64-msvc": "1.2.8", + "@rolldown/binding-win32-x64-msvc": "1.2.8" } }, "node_modules/router": { @@ -6772,21 +4648,54 @@ "node": ">= 18" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -6942,28 +4851,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "ISC" }, "node_modules/source-map": { "version": "0.6.1", @@ -6975,46 +4868,32 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", "dev": true, "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, "engines": { - "node": ">=10" + "node": "*" } }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/statuses": { "version": "2.0.2", @@ -7026,37 +4905,24 @@ "node": ">= 0.8" } }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "safe-buffer": "~5.2.0" } }, - "node_modules/string-width-cjs": { - "name": "string-width", + "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -7084,86 +4950,10 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/synckit": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", - "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.3.6" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, "node_modules/systeminformation": { - "version": "5.31.11", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.11.tgz", - "integrity": "sha512-I6O7iaUj23AXRgCPDDnvi3xHvdOLp4+1YMbF+X194lJwY1NeWojgHJPhslVKcmTtrLTguRk3QJK+xEdTiI3P0w==", + "version": "5.33.10", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.33.10.tgz", + "integrity": "sha512-/NXbMVASt2UbSVgmWto4aBTIAeuYY7zOELpZybmNxqpsrbo5uYHpuEPf5nkyrPng0uG5YOb+Yv6s0FyKY4mDQg==", "dev": true, "license": "MIT", "os": [ @@ -7180,87 +4970,104 @@ "systeminformation": "lib/cli.js" }, "engines": { - "node": ">=8.0.0" + "node": ">=10.0.0" }, "funding": { "type": "Buy me a coffee", "url": "https://www.buymeacoffee.com/systeminfo" } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/test-exclude/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "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" + "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/test-exclude/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "node_modules/tinyglobby/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", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/test-exclude/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", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "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" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/tinypool": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz", + "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": "^20.0.0 || >=22.0.0" } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, - "license": "BSD-3-Clause" + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -7285,13 +5092,15 @@ "node": ">=0.6" } }, - "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/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", "dev": true, - "license": "0BSD", - "optional": true + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } }, "node_modules/type-check": { "version": "0.4.0", @@ -7306,29 +5115,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", @@ -7349,9 +5135,9 @@ } }, "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", "peer": true, "engines": { @@ -7377,9 +5163,9 @@ } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, @@ -7393,75 +5179,6 @@ "node": ">= 0.8" } }, - "node_modules/unrs-resolver": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", - "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.3.4" - }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" - }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.12.2", - "@unrs/resolver-binding-android-arm64": "1.12.2", - "@unrs/resolver-binding-darwin-arm64": "1.12.2", - "@unrs/resolver-binding-darwin-x64": "1.12.2", - "@unrs/resolver-binding-freebsd-x64": "1.12.2", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", - "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", - "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", - "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", - "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-x64-musl": "1.12.2", - "@unrs/resolver-binding-openharmony-arm64": "1.12.2", - "@unrs/resolver-binding-wasm32-wasi": "1.12.2", - "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", - "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", - "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -7472,20 +5189,12 @@ "punycode": "^2.1.0" } }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } + "license": "MIT" }, "node_modules/vary": { "version": "1.1.2", @@ -7512,16 +5221,214 @@ "node": ">=0.6.0" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "node_modules/vite": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", + "dev": true, + "license": "MIT", "dependencies": { - "makeerror": "1.0.12" + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/voca": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/voca/-/voca-1.4.1.tgz", + "integrity": "sha512-NJC/BzESaHT1p4B5k4JykxedeltmNbau4cummStd4RjFojgq/kLew5TzYge9N2geeWyI2w8T30wUET5v+F7ZHA==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7538,6 +5445,61 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -7573,44 +5535,12 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "peer": true }, "node_modules/y18n": { "version": "5.0.8", @@ -7622,17 +5552,10 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", "license": "ISC", "peer": true, "bin": { diff --git a/package.json b/package.json index 08d883a4..29cc0b5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cap-js/telemetry", - "version": "2.0.1", + "version": "2.1.0", "description": "CDS plugin providing observability features, incl. automatic OpenTelemetry instrumentation.", "repository": { "type": "git", @@ -14,15 +14,17 @@ "lib" ], "scripts": { - "lint": "npx eslint . --max-warnings=0", - "test": "npx jest --silent" + "lint": "eslint . --max-warnings=0", + "test": "vitest run --silent", + "format": "oxfmt", + "format:check": "oxfmt --check" }, "dependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/core": "^2.8", - "@opentelemetry/instrumentation": "^0.221", - "@opentelemetry/instrumentation-http": "^0.221", - "@opentelemetry/instrumentation-undici": "^0.31", + "@opentelemetry/instrumentation": "^0.222", + "@opentelemetry/instrumentation-http": "^0.222", + "@opentelemetry/instrumentation-undici": "^0.32", "@opentelemetry/resources": "^2.8", "@opentelemetry/sdk-metrics": "^2.8", "@opentelemetry/sdk-trace-base": "^2.8", @@ -37,16 +39,18 @@ "@cap-js/sqlite": "^3", "@cap-js/telemetry": "file:.", "@grpc/grpc-js": "^1.9.14", - "@opentelemetry/exporter-metrics-otlp-grpc": "^0.219", - "@opentelemetry/exporter-metrics-otlp-proto": "^0.219", - "@opentelemetry/exporter-trace-otlp-grpc": "^0.219", - "@opentelemetry/exporter-trace-otlp-proto": "^0.219", - "@opentelemetry/instrumentation-host-metrics": "^0.2.0", - "@opentelemetry/instrumentation-runtime-node": "^0.32.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "^0.222", + "@opentelemetry/exporter-metrics-otlp-proto": "^0.222", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.222", + "@opentelemetry/exporter-trace-otlp-proto": "^0.222", + "@opentelemetry/instrumentation-host-metrics": "^0.5.0", + "@opentelemetry/instrumentation-runtime-node": "^0.35.0", + "@sap-cloud-sdk/http-client": "^4", "@sap/cds-mtxs": "^4", "axios": "^1.6.7", "eslint": "^10", - "jest": "^30.4.2" + "oxfmt": "0.63.0", + "vitest": "^4" }, "cds": { "requires": { @@ -77,11 +81,17 @@ "W3CTraceContextPropagator", "W3CBaggagePropagator" ], - "[development]": { - "hrtime": true + "processor": { + "kind": "BatchSpanProcessor" }, "_tx": false, - "_hana_prom": true + "_hana_prom": true, + "[development]": { + "hrtime": true, + "processor": { + "kind": "SimpleSpanProcessor" + } + } }, "metrics": { "config": { @@ -107,8 +117,12 @@ }, "telemetry-to-dynatrace": { "vcap": [ - { "label": "dynatrace" }, - { "tag": "dynatrace" } + { + "label": "dynatrace" + }, + { + "tag": "dynatrace" + } ], "tracing": { "exporter": { @@ -126,8 +140,12 @@ }, "telemetry-to-cloud-logging": { "vcap": [ - { "label": "cloud-logging" }, - { "tag": "Cloud Logging" } + { + "label": "cloud-logging" + }, + { + "tag": "Cloud Logging" + } ], "tracing": { "exporter": { @@ -157,6 +175,25 @@ "metrics": { "exporter": "env" } + }, + "telemetry-to-caas": { + "vcap": [ + { + "label": "caas-service" + } + ], + "tracing": { + "exporter": { + "module": "@opentelemetry/exporter-trace-otlp-proto", + "class": "OTLPTraceExporter" + } + }, + "metrics": { + "exporter": { + "module": "@opentelemetry/exporter-metrics-otlp-proto", + "class": "OTLPMetricExporter" + } + } } } } diff --git a/test/bookshop/.cdsrc.json b/test/bookshop/.cdsrc.json index 1dc257b9..c467bbf8 100644 --- a/test/bookshop/.cdsrc.json +++ b/test/bookshop/.cdsrc.json @@ -1,8 +1,19 @@ { + "requires": { + "messaging": { + "kind": "local-messaging", + "_kind": "file-based-messaging", + "file": "../msg-box" + } + }, + "log": { + "cls_custom_fields": ["tenant_id"] + }, "[logging]": { "requires": { "telemetry": { "tracing": { + "exporter": false, "sampler": { "ignoreIncomingPaths": ["/odata/v4/admin/Genres"] } @@ -30,6 +41,10 @@ "metrics": { "config": { "exportIntervalMillis": 100 + }, + "exporter": { + "module": "./lib/MyInMemoryMetricReader.js", + "class": "MyInMemoryMetricReader" } } } @@ -41,13 +56,13 @@ "telemetry": { "metrics": { "config": { - "exportIntervalMillis": 100 + "exportIntervalMillis": 1000 }, "_db_pool": false, "_queue": true, "exporter": { - "module": "@opentelemetry/sdk-metrics", - "class": "ConsoleMetricExporter" + "module": "./lib/MyInMemoryMetricReader.js", + "class": "MyInMemoryMetricReader" } } } @@ -64,24 +79,46 @@ "_db_pool": false, "_queue": false, "exporter": { - "module": "@opentelemetry/sdk-metrics", - "class": "ConsoleMetricExporter" + "module": "./lib/MyInMemoryMetricReader.js", + "class": "MyInMemoryMetricReader" } } } } }, - "[tracing-attributes]": { + "[tracing-in-memory]": { "requires": { "telemetry": { "tracing": { "exporter": { - "module": "@opentelemetry/sdk-trace-node" + "module": "./lib/MyInMemorySpanExporter.js", + "class": "MyInMemorySpanExporter" + } + } + } + } + }, + "[sampler-ignore-authors]": { + "requires": { + "telemetry": { + "tracing": { + "sampler": { + "ignoreIncomingPaths": ["/odata/v4/admin/Authors"] } } } } }, + "[native-fetch]": { + "remote": { + "native_fetch": true + } + }, + "[no-scheduling]": { + "requires": { + "scheduling": false + } + }, "[persistent-outbox]": { "requires": { "messaging": { @@ -90,6 +127,15 @@ } } }, + "[inboxed]": { + "requires": { + "messaging": { + "kind": "file-based-messaging", + "file": "../inboxed", + "inboxed": true + } + } + }, "[without-outbox]": { "requires": { "messaging": { @@ -98,5 +144,16 @@ "outboxed": false } } + }, + "[tracing-batch-processor]": { + "requires": { + "telemetry": { + "tracing": { + "processor": { + "kind": "BatchSpanProcessor" + } + } + } + } } } diff --git a/test/bookshop/lib/MyInMemoryMetricReader.js b/test/bookshop/lib/MyInMemoryMetricReader.js new file mode 100644 index 00000000..5787ee01 --- /dev/null +++ b/test/bookshop/lib/MyInMemoryMetricReader.js @@ -0,0 +1,189 @@ +// In-memory metric reader for tests. Exported metrics are accumulated in a module-level array +// that tests can import directly via `require('./lib/MyInMemoryMetricReader').captured`. +// Wired into the meter provider via .cdsrc.json profile config (no provider-poking from tests): +// the class is exporter-shaped (has `export()`), so lib/metrics/index.js wraps it in a +// PeriodicExportingMetricReader — keeping the configured `exportIntervalMillis` working. +// +// Kept dependency-light on purpose: it does NOT `require('@sap/cds')` at module top (doing so +// broke span capture once for the sibling span exporter). Only @opentelemetry primitives. +// +// TEMPORALITY: mirrors production. lib/metrics/index.js configures the real exporter with +// `temporalityPreference: AggregationTemporality.DELTA`, so the tests must validate what a real +// DELTA export produces — the reader honors that preference rather than forcing CUMULATIVE. +// +// Under DELTA each export reports only the *increment* since the previous collection, and +// `expectEventually` force-flushes repeatedly, so a naive "latest datapoint" read of a counter +// would drop to 0 after the first flush. We therefore split handling by datapoint type: +// * SUM datapoints (the 3 counters: incoming_messages, outgoing_messages, processing_failures) +// are summed into a running total per counter series (metric name + full attribute set) — +// reconstructing the cumulative value the tests assert against (totalInc/totalOut/totalFailed). +// * GAUGE datapoints (cold_entries, remaining_entries, *_storage_time_in_seconds) are absolute +// point-in-time observations; for those we keep the latest exported value, never a sum. + +const { ExportResultCode } = require('@opentelemetry/core') +const { AggregationTemporality, DataPointType } = require('@opentelemetry/sdk-metrics') +const { metrics } = require('@opentelemetry/api') + +// Raw ResourceMetrics objects, one per collection/flush. Drives the GAUGE latest-value lookup. +const captured = [] + +// Running totals for SUM (counter) series. Keyed by the fully-qualified series identity +// (metric name + every attribute on the datapoint) so distinct (queue.name, tenant) series never +// collide; each entry keeps the original attributes so lookups can match by attribute subset the +// same way the gauge path does. Under DELTA the SDK reports the increment since its last +// collection; summing every increment a series receives reconstructs its cumulative value — which +// is what the tests track (totalInc/totalOut/totalFailed grow monotonically, never reset per case). +// +// NOTE: `captured` and `counterSeries` are process-level singletons. Cross-file correctness relies +// on Vitest isolating each test file in its own worker process (vitest.config.mjs: pool:'forks' + +// isolate:true). Two files sharing this module in one process would bleed counter totals together. +const counterSeries = new Map() + +function seriesKey(metricName, attributes) { + const sorted = Object.keys(attributes) + .sort() + .map(k => `${k}=${attributes[k]}`) + .join('&') + return `${metricName} ${sorted}` +} + +// True when `sub` is an attribute subset of `full` (all keys present with equal values). +function attributesMatch(full, sub) { + return Object.entries(sub).every(([key, value]) => full[key] === value) +} + +class MyInMemoryMetricReader { + constructor(config = {}) { + // Honor the temporality the plugin config sets (DELTA in production) so the tests exercise the + // real export shape. Defaults to DELTA to match lib/metrics/index.js when no config is passed. + this._temporality = config.temporalityPreference ?? AggregationTemporality.DELTA + } + + // Invoked by PeriodicExportingMetricReader for each instrument type. + selectAggregationTemporality() { + return this._temporality + } + + export(resourceMetrics, resultCallback) { + captured.push(resourceMetrics) + + // Fold DELTA increments of SUM (counter) datapoints into the running totals. + for (const scopeMetrics of resourceMetrics.scopeMetrics) { + for (const metric of scopeMetrics.metrics) { + if (metric.dataPointType !== DataPointType.SUM) continue + for (const dp of metric.dataPoints) { + const key = seriesKey(metric.descriptor.name, dp.attributes) + const entry = counterSeries.get(key) + if (entry) entry.total += dp.value + else counterSeries.set(key, { name: metric.descriptor.name, attributes: dp.attributes, total: dp.value }) + } + } + } + + resultCallback({ code: ExportResultCode.SUCCESS }) + } + + shutdown() { + return Promise.resolve() + } + + forceFlush() { + return Promise.resolve() + } +} + +// Most recent GAUGE MetricData for `queue.` that carries datapoints, scanning captured +// exports newest-first (mirrors the old `consoleDirLogs.findLast(... && dataPoints?.length)`). +function latestGaugeMetric(metricName) { + const name = `queue.${metricName}` + for (let i = captured.length - 1; i >= 0; i--) { + for (const scopeMetrics of captured[i].scopeMetrics) { + for (const metric of scopeMetrics.metrics) { + if ( + metric.descriptor.name === name && + metric.dataPointType === DataPointType.GAUGE && + metric.dataPoints?.length + ) + return metric + } + } + } + return null +} + +// Accumulated counter total for `queue.` across all series whose attributes match the +// given filter (subset match, like the gauge lookup). Returns null when no counter series exists +// for that name — i.e. the metric was never exported as a counter (queue metrics disabled) or the +// filter matches nothing. +function counterTotal(metricName, attributes) { + const name = `queue.${metricName}` + let found = false + let total = 0 + for (const entry of counterSeries.values()) { + if (entry.name === name && attributesMatch(entry.attributes, attributes)) { + found = true + total += entry.total + } + } + return found ? total : null +} + +// Names of metrics that are SUM (counter) instruments — the three counters the queue plugin +// registers. Dispatches latestDataPointValue explicitly, rather than relying on counterSeries +// happening to be populated (which is empty on the first poll after reset()). +const COUNTER_METRIC_NAMES = new Set([ + 'queue.incoming_messages', + 'queue.outgoing_messages', + 'queue.processing_failures' +]) + +function isCounter(metricName) { + return COUNTER_METRIC_NAMES.has(`queue.${metricName}`) +} + +// Value of `queue.` for the datapoint(s) matching all given attributes +// (e.g. { 'queue.name': ... } and/or { 'sap.tenancy.tenant_id': ... }). For counters this is the +// accumulated running total (cumulative, reconstructed from DELTA increments); for gauges it is +// the latest absolute observation. Returns null when the metric was never exported (queue metrics +// disabled) or no datapoint matches the filter. +function latestDataPointValue(metricName, attributes = {}) { + if (isCounter(metricName)) return counterTotal(metricName, attributes) + + const metric = latestGaugeMetric(metricName) + if (!metric) return null + const dp = metric.dataPoints.find(dp => attributesMatch(dp.attributes, attributes)) + return dp ? dp.value : null +} + +// Force the wired meter provider to collect + export now, so the reader reflects the latest state. +// Fails fast if the provider isn't the real (wired) one — a NoopMeterProvider has no forceFlush, +// which would otherwise silently no-op and let a polling helper busy-spin its whole timeout. +async function forceFlush() { + const provider = metrics.getMeterProvider() + if (typeof provider.forceFlush !== 'function') { + throw new Error( + 'MyInMemoryMetricReader.forceFlush: meter provider is not wired up (no forceFlush) — ' + + 'is the metrics-outbox profile active and the reader configured?' + ) + } + await provider.forceFlush() +} + +// Clears the per-test GAUGE state (captured exports) so a stale point-in-time value from a previous +// case cannot leak. The counter running totals are intentionally NOT cleared: the suites' counter +// assertions (totalInc/totalOut/totalFailed) and the plugin's underlying counters are cumulative +// across the whole file, and the SDK's DELTA baseline likewise persists across flushes — zeroing +// only our side would desync it and under-count. See the module header for the full rationale. +function reset() { + captured.length = 0 +} + +module.exports = { + MyInMemoryMetricReader, + captured, + counterSeries, + latestGaugeMetric, + latestDataPointValue, + forceFlush, + reset +} diff --git a/test/bookshop/lib/MyInMemorySpanExporter.js b/test/bookshop/lib/MyInMemorySpanExporter.js new file mode 100644 index 00000000..9c7e4bdc --- /dev/null +++ b/test/bookshop/lib/MyInMemorySpanExporter.js @@ -0,0 +1,59 @@ +// In-memory span exporter for tests. Spans are accumulated in a module-level array that +// tests can import directly via `require('./lib/MyInMemorySpanExporter').captured`. +// Wired into the tracer provider via .cdsrc.json profile config (no provider-poking from tests). + +const { ExportResultCode } = require('@opentelemetry/core') + +const captured = [] + +class MyInMemorySpanExporter { + export(spans, resultCallback) { + captured.push(...spans) + resultCallback({ code: ExportResultCode.SUCCESS }) + } + + shutdown() { + return Promise.resolve() + } + + forceFlush() { + return Promise.resolve() + } +} + +// Returns the captured spans grouped by traceId, each group is a hierarchy: +// { traceId, root, all, byParent } +// `root` is the span with no parent inside the group (the visible root for the exporter's +// "elapsed times:" primer logic — i.e. spans whose parentSpanId is not present in this group). +function groupedByTrace() { + const byTrace = new Map() + for (const s of captured) { + const tid = s.spanContext().traceId + if (!byTrace.has(tid)) byTrace.set(tid, []) + byTrace.get(tid).push(s) + } + + return [...byTrace.entries()].map(([traceId, all]) => { + const ids = new Set(all.map(s => s.spanContext().spanId)) + const roots = all.filter(s => !s.parentSpanContext?.spanId || !ids.has(s.parentSpanContext.spanId)) + const byParent = new Map() + for (const s of all) { + const pid = s.parentSpanContext?.spanId + if (!byParent.has(pid)) byParent.set(pid, []) + byParent.get(pid).push(s) + } + return { traceId, root: roots[0], roots, all, byParent } + }) +} + +// Returns just the visible "root" spans across all captured traces. These correspond 1:1 to +// "elapsed times:" primers our ConsoleSpanExporter would emit for the same data. +function rootSpans() { + return groupedByTrace().flatMap(g => g.roots) +} + +function reset() { + captured.length = 0 +} + +module.exports = { MyInMemorySpanExporter, captured, groupedByTrace, rootSpans, reset } diff --git a/test/bookshop/package.json b/test/bookshop/package.json index bf574fe9..0c5c0c71 100644 --- a/test/bookshop/package.json +++ b/test/bookshop/package.json @@ -39,11 +39,6 @@ } } }, - "messaging": { - "kind": "local-messaging", - "_kind": "file-based-messaging", - "file": "../msg-box" - }, "queue": { "legacyLocking": false }, @@ -76,11 +71,6 @@ } } }, - "log": { - "cls_custom_fields": [ - "tenant_id" - ] - }, "fiori": { "draft_deletion_timeout": false } diff --git a/test/bookshop/srv/admin-service.cds b/test/bookshop/srv/admin-service.cds index 4ab230d8..3d8838ec 100644 --- a/test/bookshop/srv/admin-service.cds +++ b/test/bookshop/srv/admin-service.cds @@ -7,6 +7,9 @@ service AdminService @(requires: 'admin') { action test_spawn(); action test_emit(); + action test_outboxed_send(); + action test_outboxed_send_batch(); + action test_scheduled(); event foo { bar : String; diff --git a/test/bookshop/srv/admin-service.js b/test/bookshop/srv/admin-service.js index fb1cdf6c..f8f4f3bd 100644 --- a/test/bookshop/srv/admin-service.js +++ b/test/bookshop/srv/admin-service.js @@ -32,6 +32,26 @@ module.exports = class AdminService extends cds.ApplicationService { await messaging.emit('foo', { bar: 'baz' }) }) + // test_outboxed_send: writes a task to the persistent outbox addressed to ExternalServiceOne, + // whose handler the test installs. Exercises the queue-worker path (scan, lock, dispatch). + this.on('test_outboxed_send', async () => { + const externalOne = await cds.connect.to('ExternalServiceOne') + await cds.queued(externalOne).send('call', {}) + }) + + // test_outboxed_send_batch: writes multiple tasks to the persistent outbox to exercise chunkSize > 1 fan-out. + this.on('test_outboxed_send_batch', async () => { + const externalOne = await cds.connect.to('ExternalServiceOne') + const queued = cds.queued(externalOne) + await Promise.all([queued.send('call', {}), queued.send('call', {}), queued.send('call', {})]) + }) + + // test_scheduled: schedules a one-shot task to fire after a short delay. + this.on('test_scheduled', async () => { + const externalOne = await cds.connect.to('ExternalServiceOne') + await cds.queued(externalOne).schedule('call', {}).after(10) + }) + return super.init() } } diff --git a/test/buffer.test.js b/test/buffer.test.js new file mode 100644 index 00000000..24fcf428 --- /dev/null +++ b/test/buffer.test.js @@ -0,0 +1,140 @@ +/** + * Tests for wrapExporterWithBuffer - isolated in separate file for clean process isolation. + * These tests manipulate the cds.env singleton which persists across vi.resetModules() calls. + * They drive buffering via cds.env x509 directly (certsAvailable), no cert source involved. + */ +const cds = require('@sap/cds') + +describe('wrapExporterWithBuffer', () => { + beforeEach(() => { + vi.resetModules() + // Clear cds.env singleton - especially x509 certs from prior tests + cds.env.requires = { telemetry: {} } + }) + + test('buffers items until certs are available', async () => { + const { wrapExporterWithBuffer } = await import('../lib/utils/caas.js') + + const exportedItems = [] + const mockExporter = { + export: vi.fn((items, cb) => { + exportedItems.push(...items) + cb({ code: 0 }) + }), + shutdown: vi.fn(() => Promise.resolve()) + } + + const wrapped = wrapExporterWithBuffer(mockExporter) + + // Export while not ready - should buffer + wrapped.export(['item1'], () => {}) + wrapped.export(['item2'], () => {}) + expect(exportedItems.length).toBe(0) + + // Make certs available + cds.env.requires.telemetry.x509 = { + cert: '-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----', + key: '-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----' + } + + // Next export triggers flush + new export + wrapped.export(['item3'], () => {}) + expect(exportedItems).toEqual(['item1', 'item2', 'item3']) + }) + + test('drops oldest when buffer full', async () => { + const { wrapExporterWithBuffer } = await import('../lib/utils/caas.js') + + const exportedItems = [] + const mockExporter = { + export: vi.fn((items, cb) => { + exportedItems.push(...items) + cb({ code: 0 }) + }) + } + + const wrapped = wrapExporterWithBuffer(mockExporter) + + // Fill buffer beyond max (10) + for (let i = 0; i < 15; i++) { + wrapped.export([`item${i}`], () => {}) + } + + // Make certs available and trigger flush + cds.env.requires.telemetry.x509 = { + cert: '-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----', + key: '-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----' + } + wrapped.export(['final'], () => {}) + + // Buffer was capped at 10, so oldest 5 items were dropped + // Exported: 10 buffered + 1 final = 11 + expect(exportedItems.length).toBe(11) + expect(exportedItems[0]).toBe('item5') // item0-4 were dropped + expect(exportedItems[9]).toBe('item14') + expect(exportedItems[10]).toBe('final') + }) + + test('exports directly when certs already available', async () => { + cds.env.requires = { + telemetry: { + x509: { + cert: '-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----', + key: '-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----' + } + } + } + + const { wrapExporterWithBuffer } = await import('../lib/utils/caas.js') + + const exportedItems = [] + const mockExporter = { + export: vi.fn((items, cb) => { + exportedItems.push(...items) + cb({ code: 0 }) + }) + } + + const wrapped = wrapExporterWithBuffer(mockExporter) + + // Should export directly, no buffering + wrapped.export(['item1'], () => {}) + expect(exportedItems).toEqual(['item1']) + + wrapped.export(['item2'], () => {}) + expect(exportedItems).toEqual(['item1', 'item2']) + }) + + test('after certs available, exports go directly to original (no buffer)', async () => { + const { wrapExporterWithBuffer } = await import('../lib/utils/caas.js') + + const exportCalls = [] + const mockExporter = { + export: (items, cb) => { + exportCalls.push(items) + cb({ code: 0 }) + } + } + + const wrapped = wrapExporterWithBuffer(mockExporter) + + // Buffer while not ready + wrapped.export(['item1'], () => {}) + expect(exportCalls.length).toBe(0) + + // Make certs available + cds.env.requires.telemetry.x509 = { + cert: '-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----', + key: '-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----' + } + + // First export after certs: flushes buffer + exports new item + wrapped.export(['item2'], () => {}) + expect(exportCalls).toEqual([['item1'], ['item2']]) + + // Subsequent exports go directly (no re-checking certsAvailable) + wrapped.export(['item3'], () => {}) + wrapped.export(['item4'], () => {}) + expect(exportCalls).toEqual([['item1'], ['item2'], ['item3'], ['item4']]) + }) +}) diff --git a/test/caas.test.js b/test/caas.test.js new file mode 100644 index 00000000..51795724 --- /dev/null +++ b/test/caas.test.js @@ -0,0 +1,528 @@ +const cds = require('@sap/cds') +const fs = require('fs') +const os = require('os') +const path = require('path') +const { trace } = require('@opentelemetry/api') + +// Mock VCAP_SERVICES for CaaS +const MOCK_CAAS_VCAP = { + 'caas-service': [ + { + name: 'test-caas', + credentials: { + otlp: { + http: 'https://caas.example.com/otlp', + grpc: 'grpc://caas.example.com:4317' + } + } + } + ] +} + +// Mock VCAP_SERVICES with ZTI binding +const MOCK_ZTI_VCAP = { + 'caas-service': [ + { + name: 'test-caas', + credentials: { + otlp: { + http: 'https://caas.example.com/otlp' + } + } + } + ], + 'zero-trust-identity': [ + { + name: 'test-zti', + credentials: { + parameters: { + 'svid-store': { + file: { name: 'test-svid' } + } + } + } + } + ] +} + +// Test certificates +const CERT_V1 = '-----BEGIN CERTIFICATE-----\nCERT_VERSION_1\n-----END CERTIFICATE-----' +const KEY_V1 = '-----BEGIN PRIVATE KEY-----\nKEY_VERSION_1\n-----END PRIVATE KEY-----' +const BUNDLE_V1 = '-----BEGIN CERTIFICATE-----\nBUNDLE_V1\n-----END CERTIFICATE-----' +const CERT_V2 = '-----BEGIN CERTIFICATE-----\nCERT_VERSION_2\n-----END CERTIFICATE-----' +const KEY_V2 = '-----BEGIN PRIVATE KEY-----\nKEY_VERSION_2\n-----END PRIVATE KEY-----' + +// Shared test helper for ZTI setup +function createZTITestContext() { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zti-test-')) + const svidDir = path.join(tmpDir, 'spire-svids') + fs.mkdirSync(svidDir) + + return { + tmpDir, + svidDir, + writeSVIDFiles(cert = CERT_V1, key = KEY_V1, bundle = BUNDLE_V1) { + fs.writeFileSync(path.join(svidDir, 'test-svid.svid.pem'), cert) + fs.writeFileSync(path.join(svidDir, 'test-svid.svid.key'), key) + fs.writeFileSync(path.join(svidDir, 'test-svid.bundle.pem'), bundle) + }, + touchCertFile() { + const now = new Date() + now.setSeconds(now.getSeconds() + 2) + fs.utimesSync(path.join(svidDir, 'test-svid.svid.pem'), now, now) + }, + setupEnv() { + process.env.VCAP_SERVICES = JSON.stringify(MOCK_ZTI_VCAP) + process.env.TELEMETRY_ZTI_DIR = svidDir + cds.env.requires = { telemetry: {} } + }, + clearModuleCache() { + vi.resetModules() + }, + cleanup() { + fs.rmSync(tmpDir, { recursive: true, force: true }) + delete process.env.VCAP_SERVICES + delete process.env.TELEMETRY_ZTI_DIR + vi.resetModules() + } + } +} + +describe('augmentCaaSCreds', () => { + let originalVcap + + beforeAll(() => { + originalVcap = process.env.VCAP_SERVICES + }) + + afterAll(() => { + if (originalVcap) process.env.VCAP_SERVICES = originalVcap + else delete process.env.VCAP_SERVICES + }) + + beforeEach(() => { + vi.resetModules() + cds.env.requires = cds.env.requires || {} + cds.env.requires.telemetry = { + x509: { + cert: Buffer.from('-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----').toString('base64'), + key: Buffer.from('-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----').toString('base64') + } + } + }) + + test('sets baseUrl from otlp.http', async () => { + process.env.VCAP_SERVICES = JSON.stringify(MOCK_CAAS_VCAP) + const { augmentCaaSCreds } = await import('../lib/utils/credentials.js') + + const credentials = { + otlp: { + http: 'https://caas.example.com/otlp', + grpc: 'grpc://caas.example.com:4317' + } + } + + augmentCaaSCreds(credentials) + + expect(credentials.baseUrl).toBe('https://caas.example.com/otlp') + }) + + test('sets httpAgentOptions when mTLS credentials found', async () => { + process.env.VCAP_SERVICES = JSON.stringify(MOCK_CAAS_VCAP) + const { augmentCaaSCreds } = await import('../lib/utils/credentials.js') + + const credentials = { + otlp: { http: 'https://caas.example.com/otlp' } + } + + augmentCaaSCreds(credentials) + + expect(credentials.httpAgentOptions).toBeDefined() + expect(typeof credentials.httpAgentOptions).toBe('function') + }) + + test('throws when no OTLP endpoints', async () => { + process.env.VCAP_SERVICES = JSON.stringify(MOCK_CAAS_VCAP) + const { augmentCaaSCreds } = await import('../lib/utils/credentials.js') + + expect(() => augmentCaaSCreds({})).toThrow('No OTLP HTTP endpoint in CaaS credentials') + }) + + test('does not augment twice', async () => { + process.env.VCAP_SERVICES = JSON.stringify(MOCK_CAAS_VCAP) + const { augmentCaaSCreds } = await import('../lib/utils/credentials.js') + + const credentials = { + otlp: { http: 'https://caas.example.com/otlp' } + } + + augmentCaaSCreds(credentials) + const originalBaseUrl = credentials.baseUrl + + credentials.otlp.http = 'https://different.com' + augmentCaaSCreds(credentials) + + expect(credentials.baseUrl).toBe(originalBaseUrl) + }) + + test('httpAgentOptions always set (agent works once certs available)', async () => { + cds.env.requires.telemetry = {} // No x509 credentials + process.env.VCAP_SERVICES = JSON.stringify(MOCK_CAAS_VCAP) + const { augmentCaaSCreds } = await import('../lib/utils/credentials.js') + + const credentials = { + otlp: { http: 'https://caas.example.com/otlp' } + } + + augmentCaaSCreds(credentials) + + // Agent factory is always set — agent works once certs become available + expect(credentials.httpAgentOptions).toBeDefined() + expect(typeof credentials.httpAgentOptions).toBe('function') + }) +}) + +describe('ZTI', () => { + let ctx + + beforeEach(() => { + ctx = createZTITestContext() + ctx.setupEnv() + }) + + afterEach(() => { + ctx.cleanup() + }) + + describe('getZTIConfig', () => { + test('detects ZTI config from VCAP_SERVICES', async () => { + const { getZTIConfig } = await import('../lib/zti.js') + const config = getZTIConfig() + + expect(config).not.toBeNull() + expect(config.svidName).toBe('test-svid') + expect(config.svidDir).toBe(ctx.svidDir) + }) + + test('returns null when no ZTI binding', async () => { + process.env.VCAP_SERVICES = JSON.stringify(MOCK_CAAS_VCAP) + vi.resetModules() + + const { getZTIConfig } = await import('../lib/zti.js') + expect(getZTIConfig()).toBeNull() + }) + }) + + describe('RotatingCertAgent', () => { + test('can be created without certs', async () => { + // No certs in cds.env + cds.env.requires.telemetry = {} + + const { getRotatingCertAgentClass, reset } = await import('../lib/utils/mtls.js') + + // Should not throw + const RotatingCertAgent = getRotatingCertAgentClass() + const agent = new RotatingCertAgent() + expect(agent).toBeDefined() + expect(agent.options.cert).toBeUndefined() + expect(agent.options.key).toBeUndefined() + + agent._cleanup() + reset() + }) + + test('uses certs when available at construction', async () => { + ctx.writeSVIDFiles() + + const { getRotatingCertAgentClass, reset } = await import('../lib/utils/mtls.js') + const { initializeZTI, loadInitialCerts } = await import('../lib/zti.js') + + initializeZTI() + loadInitialCerts() + + const RotatingCertAgent = getRotatingCertAgentClass() + const agent = new RotatingCertAgent() + expect(agent.options.cert).toBe(CERT_V1) + expect(agent.options.key).toBe(KEY_V1) + + agent._cleanup() + reset() + }) + }) + + describe('certsAvailable', () => { + test('returns false when no certs and no SVID files', async () => { + cds.env.requires.telemetry = {} + const { certsAvailable } = await import('../lib/utils/mtls.js') + expect(certsAvailable()).toBe(false) + }) + + test('returns true once SVID certs are loaded into cds.env (ZTI bound)', async () => { + ctx.writeSVIDFiles() + vi.resetModules() + const { certsAvailable } = await import('../lib/utils/mtls.js') + const { initializeZTI, loadInitialCerts } = await import('../lib/zti.js') + initializeZTI() + loadInitialCerts() + expect(certsAvailable()).toBe(true) + }) + + test('returns true when static x509 certs are in cds.env', async () => { + cds.env.requires = { + telemetry: { + x509: { cert: CERT_V1, key: KEY_V1 } + } + } + vi.resetModules() + const { certsAvailable } = await import('../lib/utils/mtls.js') + expect(certsAvailable()).toBe(true) + }) + }) + + // Certificate rotation works for both interval-based exports (production, metrics) and + // on-demand exports (tracing/logging in development). The test verifies this by checking + // that the factory returns a singleton: after rotation via 'svid' event updates the agent's certs, + // any subsequent factory() call - whether from a scheduled interval or an immediate + // span.end() - returns the same agent instance with the rotated certificates. + describe('certificate rotation', () => { + test('agent rotates certificate when svid event is emitted', async () => { + ctx.writeSVIDFiles() + + const { getRotatingAgentFactory, reset } = await import('../lib/utils/mtls.js') + const { initializeZTI, loadInitialCerts } = await import('../lib/zti.js') + + initializeZTI() + loadInitialCerts() + + const factory = getRotatingAgentFactory() + const agent = factory() + expect(agent.options.cert).toBe(CERT_V1) + expect(agent.options.key).toBe(KEY_V1) + + // Simulate certificate rotation via event (as SVID watcher does in production) + cds.emit('svid', { cert: CERT_V2, key: KEY_V2 }) + + expect(agent.options.cert).toBe(CERT_V2) + expect(agent.options.key).toBe(KEY_V2) + + // Factory returns same singleton — on-demand exports use rotated certs + expect(factory()).toBe(agent) + + reset() + }) + + test('agent rotates by re-reading cds.env when svid event has no payload', async () => { + ctx.writeSVIDFiles() + + const { getRotatingAgentFactory, reset } = await import('../lib/utils/mtls.js') + const { initializeZTI, loadInitialCerts } = await import('../lib/zti.js') + + initializeZTI() + loadInitialCerts() + + const factory = getRotatingAgentFactory() + const agent = factory() + expect(agent.options.cert).toBe(CERT_V1) + + // Simulate manual rotation: update cds.env and emit event without payload + cds.env.requires.telemetry.x509 = { cert: CERT_V2, key: KEY_V2 } + cds.emit('svid') + + expect(agent.options.cert).toBe(CERT_V2) + expect(agent.options.key).toBe(KEY_V2) + + reset() + }) + + test('agent keeps cached cert when rotation event has invalid payload', async () => { + ctx.writeSVIDFiles() + + const { getRotatingAgentFactory, reset } = await import('../lib/utils/mtls.js') + const { initializeZTI, loadInitialCerts } = await import('../lib/zti.js') + + initializeZTI() + loadInitialCerts() + + const factory = getRotatingAgentFactory() + const agent = factory() + expect(agent.options.cert).toBe(CERT_V1) + + // Emit event with invalid payload — agent should keep old certs + cds.emit('svid', { cert: null, key: null }) + expect(agent.options.cert).toBe(CERT_V1) + + // Factory returns same singleton — on-demand exports still work with cached certs + expect(factory()).toBe(agent) + + reset() + }) + }) + + describe('x509 fallback', () => { + test('setup activates ZTI when SVID files are available', async () => { + ctx.writeSVIDFiles() + + const { setup } = await import('../lib/zti.js') + const { certsAvailable, reset } = await import('../lib/utils/mtls.js') + const { augmentCaaSCreds } = await import('../lib/utils/credentials.js') + + expect(setup()).toBe(true) + expect(certsAvailable()).toBe(true) + + const credentials = { otlp: { http: 'https://caas.example.com/otlp' } } + augmentCaaSCreds(credentials) + expect(typeof credentials.httpAgentOptions).toBe('function') + + reset() + }) + + test('setup arms a bootstrap watcher when the SVID dir does not exist yet, loading certs once it appears', async () => { + // Boot race: ZTI is bound but SPIRE has not created the SVID directory yet. setup() must + // still activate and arm a watcher, so certs are picked up when they land — rather than + // reporting async certs with no watcher, which would buffer exports forever. + fs.rmSync(ctx.svidDir, { recursive: true, force: true }) + + const { setup, reset } = await import('../lib/zti.js') + const { certsAvailable } = await import('../lib/utils/mtls.js') + + expect(setup()).toBe(true) + expect(certsAvailable()).toBe(false) // nothing to load yet + + // SPIRE creates the directory and writes the SVID shortly after startup; the bootstrap + // watcher (on the parent dir) then hands off to the real watcher and loads the certs. + fs.mkdirSync(ctx.svidDir) + ctx.writeSVIDFiles() + + await vi.waitFor(() => expect(certsAvailable()).toBe(true), { timeout: 4000, interval: 100 }) + + reset() + }) + + test('setup falls back to static x509 when ZTI is not bound', async () => { + // Manual-cert path as documented in the README: static x509 credentials and no + // zero-trust-identity binding. setup() reports ZTI inactive, and certsAvailable() must + // still recognize the static certs so createCaaSExporter does not reject the setup as + // "missing mTLS". + process.env.VCAP_SERVICES = JSON.stringify(MOCK_CAAS_VCAP) + cds.env.requires.telemetry.x509 = { + cert: Buffer.from('-----BEGIN CERTIFICATE-----\nenvvar-cert\n-----END CERTIFICATE-----').toString('base64'), + key: Buffer.from('-----BEGIN PRIVATE KEY-----\nenvvar-key\n-----END PRIVATE KEY-----').toString('base64') + } + + ctx.clearModuleCache() + const { setup } = await import('../lib/zti.js') + const { setupStaticCerts } = await import('../lib/utils/mtls.js') + const { certsAvailable } = await import('../lib/utils/mtls.js') + + expect(setup()).toBe(false) + expect(setupStaticCerts()).toBe(true) + expect(certsAvailable()).toBe(true) + }) + }) +}) + +describe('CaaS integration', () => { + let ctx + + beforeEach(() => { + ctx = createZTITestContext() + ctx.setupEnv() + process.env.cds_requires_telemetry_kind = 'to-caas' + vi.resetModules() + }) + + afterEach(() => { + ctx.cleanup() + delete process.env.cds_requires_telemetry_kind + delete process.env.cds_requires_telemetry_tracing_exporter + }) + + test('TracerProvider works with ZTI credentials', async () => { + ctx.writeSVIDFiles() + + cds.env.requires = { + telemetry: { + kind: 'telemetry-to-caas', + credentials: { + otlp: { http: 'https://caas.example.com/otlp' } + }, + tracing: { + exporter: { + module: '@opentelemetry/sdk-trace-base', + class: 'InMemorySpanExporter' + }, + sampler: { kind: 'AlwaysOnSampler' }, + propagators: [], + processor: { kind: 'SimpleSpanProcessor' } + }, + instrumentations: {} + } + } + + const setup = await import('../lib/index.js') + setup.default() + + // Verify tracer works (not a NoopTracer) + const tracer = trace.getTracer('test') + const span = tracer.startSpan('test-span') + expect(span.spanContext().traceId).toBeDefined() + expect(span.spanContext().traceId).not.toBe('00000000000000000000000000000000') + span.end() + }) + + test('Metrics use DELTA aggregation temporality with ZTI', async () => { + ctx.writeSVIDFiles() + + cds.env.requires = { + telemetry: { + kind: 'telemetry-to-caas', + credentials: { + otlp: { http: 'https://caas.example.com/otlp' } + }, + metrics: { + exporter: { + module: '@opentelemetry/exporter-metrics-otlp-proto', + class: 'OTLPMetricExporter' + } + }, + instrumentations: {} + } + } + + const { AggregationTemporality, InstrumentType } = await import('@opentelemetry/sdk-metrics') + const { getResource } = await import('../lib/utils/resource.js') + const { setup } = await import('../lib/zti.js') + const metricsSetup = await import('../lib/metrics/index.js') + + // Provision ZTI certs as lib/index.js does at startup (this test drives metrics() directly) + setup() + + // Setup metrics with resource + const resource = getResource() + const meterProvider = metricsSetup.default(resource) + + // Verify metrics provider was created + expect(meterProvider).toBeDefined() + + // Get the metric collectors (contains the reader) + const collectors = meterProvider._sharedState.metricCollectors + expect(collectors).toBeDefined() + expect(collectors.length).toBeGreaterThan(0) + + const collector = collectors[0] + expect(collector.selectAggregationTemporality).toBeDefined() + + // Verify DELTA temporality is used (not CUMULATIVE) for COUNTER + const counterTemporality = collector.selectAggregationTemporality(InstrumentType.COUNTER) + expect(counterTemporality).toBe(AggregationTemporality.DELTA) + expect(counterTemporality).not.toBe(AggregationTemporality.CUMULATIVE) + + // Verify for HISTOGRAM as well + const histogramTemporality = collector.selectAggregationTemporality(InstrumentType.HISTOGRAM) + expect(histogramTemporality).toBe(AggregationTemporality.DELTA) + + // UP_DOWN_COUNTER should be CUMULATIVE by design (this is correct OTel behavior) + const upDownCounterTemporality = collector.selectAggregationTemporality(InstrumentType.UP_DOWN_COUNTER) + expect(upDownCounterTemporality).toBe(AggregationTemporality.CUMULATIVE) + }) +}) diff --git a/test/console-metric-exporter.test.js b/test/console-metric-exporter.test.js new file mode 100644 index 00000000..ab13af1f --- /dev/null +++ b/test/console-metric-exporter.test.js @@ -0,0 +1,255 @@ +// Unit tests for ConsoleMetricExporter — verifies the user-friendly formatting of the three +// output branches (db.pool table, queue table, "other" metrics) plus the aggregated host-metrics +// block, by feeding the exporter crafted ResourceMetrics-shaped fixtures and inspecting the +// formatted strings passed to LOG.info. +// +// This is a pure unit test: no cds.test server, no real OTel SDK, no console spying. + +const cds = require('@sap/cds') + +// Hook LOG.info BEFORE requiring the exporter so the exporter's module-level +// `cds.log('telemetry')` resolves to a logger whose .info we control. +const infoCalls = [] +const telemetryLog = cds.log('telemetry') +const originalInfo = telemetryLog.info +telemetryLog.info = (...args) => infoCalls.push(args) + +const ConsoleMetricExporter = require('../lib/exporter/ConsoleMetricExporter') + +afterAll(() => { + telemetryLog.info = originalInfo +}) + +beforeEach(() => { + infoCalls.length = 0 +}) + +// --- helpers --------------------------------------------------------------- + +// Builds a minimal ScopeMetrics-shaped object. +function scopeMetrics(name, metrics) { + return { scope: { name }, metrics } +} + +// Builds a minimal MetricData-shaped object. `dataPoints` are `{ attributes, value }`. +function metric(name, dataPoints, description = name) { + return { descriptor: { name, description }, dataPoints } +} + +// Drives the exporter and returns the lines logged. Asserts the result callback got SUCCESS. +function exportAndCapture(scopes) { + const exporter = new ConsoleMetricExporter() + let result + exporter.export({ scopeMetrics: scopes }, r => (result = r)) + expect(result).to.deep.equal({ code: 0 /* ExportResultCode.SUCCESS */ }) + return infoCalls.map(args => args[0]) +} + +// --- assertions ------------------------------------------------------------ + +const { expect } = require('@cap-js/cds-test') + +const APP_SCOPE = '@cap-js/telemetry' +const HOST_SCOPE = '@opentelemetry/instrumentation-host-metrics' + +describe('ConsoleMetricExporter', () => { + describe('db.pool table', () => { + it('renders a "db.pool:" header and the size/available/pending table row', () => { + const scopes = [ + scopeMetrics(APP_SCOPE, [ + metric('db.pool.size', [{ attributes: {}, value: 3 }]), + metric('db.pool.max', [{ attributes: {}, value: 10 }]), + metric('db.pool.available', [{ attributes: {}, value: 2 }]), + metric('db.pool.pending', [{ attributes: {}, value: 1 }]) + ]) + ] + + const [line] = exportAndCapture(scopes) + + expect(infoCalls.length).to.equal(1) + expect(line).to.match(/^db\.pool:/) + // Column header + expect(line).to.include('size | available | pending') + // size/max, available/size, pending — padded into the row + expect(line).to.match(/3\/10 \| +2\/3 \| +1/) + }) + + it('labels the table with the tenant id when a datapoint carries sap.tenancy.tenant_id', () => { + const attributes = { 'sap.tenancy.tenant_id': 't1' } + const scopes = [ + scopeMetrics(APP_SCOPE, [ + metric('db.pool.size', [{ attributes, value: 5 }]), + metric('db.pool.max', [{ attributes, value: 8 }]), + metric('db.pool.available', [{ attributes, value: 4 }]), + metric('db.pool.pending', [{ attributes, value: 0 }]) + ]) + ] + + const [line] = exportAndCapture(scopes) + + expect(line).to.match(/^db\.pool of tenant "t1":/) + expect(line).to.match(/5\/8 \| +4\/5 \| +0/) + }) + }) + + describe('queue table', () => { + it('renders a "queue:" header, the wide column header, and lands the values', () => { + const dp = value => [{ attributes: {}, value }] + const scopes = [ + scopeMetrics(APP_SCOPE, [ + metric('queue.cold_entries', dp(1)), + metric('queue.remaining_entries', dp(2)), + metric('queue.min_storage_time_in_seconds', dp(3)), + metric('queue.med_storage_time_in_seconds', dp(4)), + metric('queue.max_storage_time_in_seconds', dp(5)), + metric('queue.incoming_messages', dp(6)), + metric('queue.outgoing_messages', dp(7)), + metric('queue.processing_failures', dp(8)) + ]) + ] + + const [line] = exportAndCapture(scopes) + + expect(infoCalls.length).to.equal(1) + expect(line).to.match(/^queue:/) + // Column header (all eight columns) + expect(line).to.include( + 'cold | remaining | min storage time | med storage time | max storage time | incoming | outgoing | failed' + ) + // The eight values land in the padded row, in column order. + const row = line.split('\n').at(-1) + expect(row.split('|').map(c => c.trim())).to.deep.equal(['1', '2', '3', '4', '5', '6', '7', '8']) + }) + + it('labels the queue table with the tenant id when present', () => { + const attributes = { 'sap.tenancy.tenant_id': 't2' } + const dp = value => [{ attributes, value }] + const scopes = [ + scopeMetrics(APP_SCOPE, [ + metric('queue.cold_entries', dp(0)), + metric('queue.remaining_entries', dp(0)), + metric('queue.min_storage_time_in_seconds', dp(0)), + metric('queue.med_storage_time_in_seconds', dp(0)), + metric('queue.max_storage_time_in_seconds', dp(0)), + metric('queue.incoming_messages', dp(0)), + metric('queue.outgoing_messages', dp(0)), + metric('queue.processing_failures', dp(0)) + ]) + ] + + const [line] = exportAndCapture(scopes) + + expect(line).to.match(/^queue of tenant "t2":/) + }) + }) + + describe('other metrics', () => { + it('logs a single-datapoint metric unwrapped (inspect of the datapoint object)', () => { + const scopes = [ + scopeMetrics(APP_SCOPE, [metric('nodejs.eventloop.utilization', [{ attributes: {}, value: 0.42 }])]) + ] + + const [line] = exportAndCapture(scopes) + + expect(infoCalls.length).to.equal(1) + // Unwrapped: inspect(v[0]) of a single datapoint object → starts with "{" + expect(line).to.match(/^nodejs\.eventloop\.utilization: \{/) + expect(line).to.include('value: 0.42') + expect(line).not.to.match(/^nodejs\.eventloop\.utilization: \[/) + }) + + it('logs a multi-datapoint metric as an array (inspect of the datapoints array)', () => { + const scopes = [ + scopeMetrics(APP_SCOPE, [ + metric('nodejs.eventloop.time', [ + { attributes: { 'nodejs.eventloop.state': 'active' }, value: 100 }, + { attributes: { 'nodejs.eventloop.state': 'idle' }, value: 200 } + ]) + ]) + ] + + const [line] = exportAndCapture(scopes) + + expect(infoCalls.length).to.equal(1) + // Wrapped: inspect(v) of the datapoints array → starts with "[" + expect(line).to.match(/^nodejs\.eventloop\.time: \[/) + expect(line).to.include('value: 100') + expect(line).to.include('value: 200') + }) + + it('labels other metrics with the tenant id when present', () => { + const scopes = [ + scopeMetrics(APP_SCOPE, [metric('some.metric', [{ attributes: { 'sap.tenancy.tenant_id': 't3' }, value: 1 }])]) + ] + + const [line] = exportAndCapture(scopes) + + expect(line).to.match(/^some\.metric of tenant "t3": \{/) + }) + }) + + describe('host metrics', () => { + const original = process.env.HOST_METRICS_LOG_SYSTEM + + afterEach(() => { + if (original === undefined) delete process.env.HOST_METRICS_LOG_SYSTEM + else process.env.HOST_METRICS_LOG_SYSTEM = original + }) + + // process.* metrics are always aggregated; a system.network.* metric is only aggregated when + // HOST_METRICS_LOG_SYSTEM is set. + function hostScope() { + return [ + scopeMetrics(HOST_SCOPE, [ + metric('process.cpu.time', [{ attributes: { 'process.cpu.state': 'user' }, value: 1.5 }], 'process cpu time'), + metric('process.memory.usage', [{ attributes: {}, value: 123456 }], 'process memory usage'), + metric( + 'system.network.io', + [{ attributes: { device: 'eth0', direction: 'receive' }, value: 999 }], + 'system network io' + ) + ]) + ] + } + + it('aggregates only process.* into a "host metrics:" block when HOST_METRICS_LOG_SYSTEM is unset', () => { + delete process.env.HOST_METRICS_LOG_SYSTEM + + const [line] = exportAndCapture(hostScope()) + + expect(infoCalls.length).to.equal(1) + expect(line).to.match(/^host metrics:/) + expect(line).to.include('process cpu time') + expect(line).to.include('process memory usage') + // system.* excluded when the flag is unset + expect(line).not.to.include('system network io') + }) + + it('additionally aggregates system.* when HOST_METRICS_LOG_SYSTEM is set', () => { + process.env.HOST_METRICS_LOG_SYSTEM = 'true' + + const [line] = exportAndCapture(hostScope()) + + expect(infoCalls.length).to.equal(1) + expect(line).to.match(/^host metrics:/) + expect(line).to.include('process cpu time') + expect(line).to.include('process memory usage') + expect(line).to.include('system network io') + }) + }) + + describe('shutdown', () => { + it('returns FAILED via setImmediate when the exporter is shutting down', () => { + const exporter = new ConsoleMetricExporter() + exporter._shutdown = true + + return new Promise(resolve => { + exporter.export({ scopeMetrics: [] }, result => { + expect(result).to.deep.equal({ code: 1 /* ExportResultCode.FAILED */ }) + expect(infoCalls.length).to.equal(0) + resolve() + }) + }) + }) + }) +}) diff --git a/test/console-span-exporter.test.js b/test/console-span-exporter.test.js new file mode 100644 index 00000000..3fbee4fc --- /dev/null +++ b/test/console-span-exporter.test.js @@ -0,0 +1,299 @@ +// Unit tests for ConsoleSpanExporter — verifies the user-friendly hierarchy formatting +// (the "elapsed times:" primer + indented child lines) by feeding the exporter crafted +// ReadableSpan-shaped fixtures and inspecting the formatted string passed to LOG.info. +// +// This is a pure unit test: no cds.test server, no real OTel SDK, no console spying. + +const cds = require('@sap/cds') + +// Hook LOG.info BEFORE requiring the exporter so the exporter's module-level +// `cds.log('telemetry')` resolves to a logger whose .info we control. +const infoCalls = [] +const telemetryLog = cds.log('telemetry') +const originalInfo = telemetryLog.info +telemetryLog.info = (...args) => infoCalls.push(args) + +const ConsoleSpanExporter = require('../lib/exporter/ConsoleSpanExporter') + +afterAll(() => { + telemetryLog.info = originalInfo +}) + +beforeEach(() => { + infoCalls.length = 0 +}) + +// --- helpers --------------------------------------------------------------- + +// Builds a minimal ReadableSpan-shaped object. Times are in OTel HrTime = [seconds, nanos]. +function span({ name, traceId, spanId, parentSpanId, startMs = 0, durationMs = 0, attributes = {} }) { + const startHr = msToHr(startMs) + const durationHr = msToHr(durationMs) + const endHr = msToHr(startMs + durationMs) + return { + name, + kind: 0, + spanContext: () => ({ traceId, spanId }), + parentSpanContext: parentSpanId ? { traceId, spanId: parentSpanId } : undefined, + startTime: startHr, + endTime: endHr, + duration: durationHr, + status: { code: 0 }, + attributes, + links: [], + events: [], + ended: true, + resource: { attributes: {} }, + instrumentationScope: { name: 'test' }, + droppedAttributesCount: 0, + droppedEventsCount: 0, + droppedLinksCount: 0 + } +} + +function msToHr(ms) { + const seconds = Math.floor(ms / 1000) + const nanos = Math.round((ms - seconds * 1000) * 1e6) + return [seconds, nanos] +} + +// Drives the exporter and returns the lines logged across all root primers. +function exportAndCapture(spans) { + const exporter = new ConsoleSpanExporter() + let result + exporter.export(spans, r => (result = r)) + expect(result).to.deep.equal({ code: 0 /* ExportResultCode.SUCCESS */ }) + return infoCalls.map(args => args[0]) +} + +// --- assertions ------------------------------------------------------------ + +const { expect } = require('@cap-js/cds-test') + +describe('ConsoleSpanExporter', () => { + describe('hierarchy formatting', () => { + it('emits a single "elapsed times:" primer per root and nests children by depth', () => { + // Tree shape: + // root (0 → 10 ms) + // childA (1 → 4 ms) + // grandchild (2 → 3 ms) + // childB (5 → 9 ms) + const TRACE = 'a'.repeat(32) + const root = span({ name: 'root', traceId: TRACE, spanId: 'r0', startMs: 0, durationMs: 10 }) + const childA = span({ + name: 'childA', + traceId: TRACE, + spanId: 'cA', + parentSpanId: 'r0', + startMs: 1, + durationMs: 3 + }) + const grand = span({ + name: 'grandchild', + traceId: TRACE, + spanId: 'g0', + parentSpanId: 'cA', + startMs: 2, + durationMs: 1 + }) + const childB = span({ + name: 'childB', + traceId: TRACE, + spanId: 'cB', + parentSpanId: 'r0', + startMs: 5, + durationMs: 4 + }) + + // Order matters: children must arrive BEFORE the root for the exporter's + // temporaryStorage flush logic to merge them under the same primer. + const [primer] = exportAndCapture([childA, grand, childB, root]) + + // Single primer + expect(infoCalls.length).to.equal(1) + expect(primer).to.match(/^elapsed times:/) + + // Root line: 0.00 → 10.00 = 10.00 ms root (no indent on the root data line) + expect(primer).to.match(/\n +0\.00 → +10\.00 = +10\.00 ms {2}root/) + + // First-level children indented by 2 spaces beyond root + expect(primer).to.match(/\n.+ ms {4}childA/) + expect(primer).to.match(/\n.+ ms {4}childB/) + + // Grandchild indented by 4 spaces beyond root + expect(primer).to.match(/\n.+ ms {6}grandchild/) + + // Ordering: childA appears before grandchild appears before childB + expect(primer.indexOf('childA')).to.be.lessThan(primer.indexOf('grandchild')) + expect(primer.indexOf('grandchild')).to.be.lessThan(primer.indexOf('childB')) + }) + + it('relativizes child start/end to the root start time', () => { + // Root starts at 100 ms wallclock; child at 105 ms. Child should display as 5.00 → ... + const TRACE = 'b'.repeat(32) + const root = span({ name: 'root', traceId: TRACE, spanId: 'r0', startMs: 100, durationMs: 20 }) + const child = span({ + name: 'child', + traceId: TRACE, + spanId: 'c0', + parentSpanId: 'r0', + startMs: 105, + durationMs: 10 + }) + + const [primer] = exportAndCapture([child, root]) + + expect(primer).to.match(/0\.00 → +20\.00 = +20\.00 ms {2}root/) + expect(primer).to.match(/5\.00 → +15\.00 = +10\.00 ms {4}child/) + }) + + it('sorts sibling spans by start time, ties broken by later end-time first', () => { + const TRACE = 'c'.repeat(32) + const root = span({ name: 'root', traceId: TRACE, spanId: 'r0', startMs: 0, durationMs: 50 }) + const late = span({ name: 'late', traceId: TRACE, spanId: 's3', parentSpanId: 'r0', startMs: 10, durationMs: 1 }) + const earlyLong = span({ + name: 'earlyLong', + traceId: TRACE, + spanId: 's1', + parentSpanId: 'r0', + startMs: 0, + durationMs: 30 + }) + const earlyShort = span({ + name: 'earlyShort', + traceId: TRACE, + spanId: 's2', + parentSpanId: 'r0', + startMs: 0, + durationMs: 5 + }) + + const [primer] = exportAndCapture([late, earlyShort, earlyLong, root]) + + // Equal start time → longer span first; otherwise by start time ascending + const order = ['earlyLong', 'earlyShort', 'late'].map(n => primer.indexOf(n)) + expect(order[0]).to.be.lessThan(order[1]) + expect(order[1]).to.be.lessThan(order[2]) + }) + + it('emits a separate primer per trace (multi-root)', () => { + const T1 = 'd'.repeat(32), + T2 = 'e'.repeat(32) + const r1 = span({ name: 'root1', traceId: T1, spanId: 'r1', startMs: 0, durationMs: 5 }) + const c1 = span({ name: 'c1', traceId: T1, spanId: 'c1', parentSpanId: 'r1', startMs: 1, durationMs: 2 }) + const r2 = span({ name: 'root2', traceId: T2, spanId: 'r2', startMs: 0, durationMs: 7 }) + const c2 = span({ name: 'c2', traceId: T2, spanId: 'c2', parentSpanId: 'r2', startMs: 1, durationMs: 3 }) + + exportAndCapture([c1, c2, r1, r2]) + + expect(infoCalls.length).to.equal(2) + const all = infoCalls.map(c => c[0]) + expect(all[0]).to.include('root1').and.to.include('c1').and.not.to.include('root2') + expect(all[1]).to.include('root2').and.to.include('c2').and.not.to.include('root1') + }) + + it('skips short "METHOD /word" spans (e.g. unadjusted http instrumentation roots)', () => { + const TRACE = 'f'.repeat(32) + const root = span({ name: 'root', traceId: TRACE, spanId: 'r0', startMs: 0, durationMs: 10 }) + // The skip regex is /^[A-Z]+ \/\${0,1}\w+$/ — single path segment, no slashes after the first. + const noisy = span({ + name: 'GET /catalog', + traceId: TRACE, + spanId: 'h0', + parentSpanId: 'r0', + startMs: 1, + durationMs: 5 + }) + + const [primer] = exportAndCapture([noisy, root]) + + expect(primer).to.include('root') + expect(primer).not.to.include('GET /catalog') + }) + + it('handles deep nesting with increasing indentation', () => { + const TRACE = '1'.repeat(32) + const root = span({ name: 'L0', traceId: TRACE, spanId: 'L0', startMs: 0, durationMs: 10 }) + const l1 = span({ name: 'L1', traceId: TRACE, spanId: 'L1', parentSpanId: 'L0', startMs: 1, durationMs: 8 }) + const l2 = span({ name: 'L2', traceId: TRACE, spanId: 'L2', parentSpanId: 'L1', startMs: 2, durationMs: 6 }) + const l3 = span({ name: 'L3', traceId: TRACE, spanId: 'L3', parentSpanId: 'L2', startMs: 3, durationMs: 4 }) + + const [primer] = exportAndCapture([l1, l2, l3, root]) + + // Each deeper level adds 2 spaces of indentation + const indents = ['L0', 'L1', 'L2', 'L3'].map(n => { + const m = primer.match(new RegExp(`\\n( +)\\d.*ms( +)${n}(?!\\d)`)) + return m ? m[2].length - 1 : null // exclude the single space separator after "ms " + }) + // L0: 1 leading space before the name; each child adds 2. So we expect 1, 3, 5, 7. + expect(indents).to.deep.equal([1, 3, 5, 7]) + }) + }) + + describe('time formatting', () => { + it('formats sub-millisecond durations with two decimals', () => { + const TRACE = '2'.repeat(32) + const root = span({ name: 'root', traceId: TRACE, spanId: 'r0', startMs: 0, durationMs: 0.5 }) + const [primer] = exportAndCapture([root]) + expect(primer).to.match(/0\.00 → +0\.50 = +0\.50 ms/) + }) + + it('right-aligns integer portion to 3 chars', () => { + const TRACE = '3'.repeat(32) + const root = span({ name: 'root', traceId: TRACE, spanId: 'r0', startMs: 0, durationMs: 123 }) + const [primer] = exportAndCapture([root]) + // "123.00" → matches as-is, fits in the 3-char integer slot + expect(primer).to.match(/0\.00 → +123\.00 = +123\.00 ms/) + }) + }) + + describe('span name handling', () => { + it('truncates names longer than 80 chars with an ellipsis', () => { + const TRACE = '4'.repeat(32) + const longName = 'X'.repeat(100) + const root = span({ name: longName, traceId: TRACE, spanId: 'r0', startMs: 0, durationMs: 1 }) + + const [primer] = exportAndCapture([root]) + + expect(primer).to.include('X'.repeat(79) + '…') + expect(primer).not.to.include('X'.repeat(80)) + }) + }) + + describe('robustness', () => { + it('does not throw when a child arrives without its parent (orphan trace)', () => { + // No root provided for this trace — the exporter should buffer the child and not flush. + const TRACE = '5'.repeat(32) + const orphan = span({ + name: 'orphan', + traceId: TRACE, + spanId: 'o0', + parentSpanId: 'r-missing', + startMs: 0, + durationMs: 1 + }) + + expect(() => exportAndCapture([orphan])).not.to.throw() + expect(infoCalls.length).to.equal(0) + }) + + it('treats any span without a parent as a root and emits a primer', () => { + const TRACE = '6'.repeat(32) + const lonely = span({ name: 'lonely', traceId: TRACE, spanId: 'r0', startMs: 0, durationMs: 2 }) + + const [primer] = exportAndCapture([lonely]) + + expect(primer).to.match(/^elapsed times:/) + expect(primer).to.include('lonely') + }) + + it('shutdown flushes pending buffered children without throwing', () => { + const exporter = new ConsoleSpanExporter() + // No-op: just verify the shutdown contract. + return exporter.shutdown().then(() => { + // No exception, no logged primers (nothing was buffered). + expect(infoCalls.length).to.equal(0) + }) + }) + }) +}) diff --git a/test/logging-mt.test.js b/test/logging-mt.test.js new file mode 100644 index 00000000..24c910e3 --- /dev/null +++ b/test/logging-mt.test.js @@ -0,0 +1,48 @@ +/* eslint-disable no-console */ + +const cds = require('@sap/cds') +// prettier-ignore +const { expect, GET } = cds.test('serve', '--in-memory', '--project', __dirname + '/bookshop', '--profile', 'logging,multitenancy') + +describe('logging with multitenancy', () => { + const TENANT1 = 'tenant_1' + const TENANT2 = 'tenant_2' + const user1 = { auth: { username: `user_${TENANT1}` } } + const user2 = { auth: { username: `user_${TENANT2}` } } + + const { dir } = console + beforeAll(async () => { + const mts = await cds.connect.to('cds.xt.DeploymentService') + await mts.subscribe(TENANT1) + await mts.subscribe(TENANT2) + }) + beforeEach(() => { + console.dir = vi.fn() + }) + afterAll(() => { + console.dir = dir + }) + + test('log records carry the tenant id for tenant_1', async () => { + const { status } = await GET('/odata/v4/admin/Genres', user1) + expect(status).to.equal(200) + const logs = console.dir.mock.calls.map(([log]) => log) + // the request-scoped logs from the AdminService READ Genres handler + const scoped = logs.filter(l => l?.attributes?.['sap.tenancy.tenant_id']) + expect(scoped.length).to.be.greaterThan(0) + for (const log of scoped) { + expect(log.attributes['sap.tenancy.tenant_id']).to.equal(TENANT1) + } + }) + + test('log records carry the tenant id for tenant_2', async () => { + const { status } = await GET('/odata/v4/admin/Genres', user2) + expect(status).to.equal(200) + const logs = console.dir.mock.calls.map(([log]) => log) + const scoped = logs.filter(l => l?.attributes?.['sap.tenancy.tenant_id']) + expect(scoped.length).to.be.greaterThan(0) + for (const log of scoped) { + expect(log.attributes['sap.tenancy.tenant_id']).to.equal(TENANT2) + } + }) +}) diff --git a/test/logging.test.js b/test/logging.test.js index 614b83dc..c75b5000 100644 --- a/test/logging.test.js +++ b/test/logging.test.js @@ -1,8 +1,14 @@ /* eslint-disable no-console */ -// REVISIT: even with profile "logging", cls_custom_fields from package.json wins -process.env.cds_log = JSON.stringify({ cls_custom_fields: ['foo'] }) - +// Config lives in the `[logging]` profile of test/bookshop/.cdsrc.json: +// - log.cls_custom_fields: ['foo'] — the profile's own override (the base default is +// ['tenant_id']). #486 moved the base `cds.log`/`messaging` defaults out of package.json into +// .cdsrc.json base so profiles can win: package.json config loads AFTER .cdsrc.json and would +// otherwise override any profile. +// - requires.telemetry.tracing.exporter: false to disable the tracing signal (no exporter → +// lib/tracing/index.js bails out early) so the queue SchedulingService's outbox-scan "elapsed +// times:" trace primer is never produced and can't land in the console.dir spy window. Without +// this, on HANA the outbox poll fires later than any fixed drain and the primer flakes the count. const cds = require('@sap/cds') const { expect, GET } = cds.test(__dirname + '/bookshop', '--profile', 'logging') @@ -11,7 +17,7 @@ describe('logging', () => { const { dir } = console beforeEach(() => { - console.dir = jest.fn() + console.dir = vi.fn() }) afterAll(() => { console.dir = dir diff --git a/test/metrics-outbox-disabled.test.js b/test/metrics-outbox-disabled.test.js index 6864cc64..6b6e33a8 100644 --- a/test/metrics-outbox-disabled.test.js +++ b/test/metrics-outbox-disabled.test.js @@ -1,22 +1,13 @@ -// Mock console.dir to capture logs ConsoleMetricExporter writes -const consoleDirLogs = [] -jest.spyOn(console, 'dir').mockImplementation((...args) => { - consoleDirLogs.push(args) -}) - const cds = require('@sap/cds') -const { setTimeout: wait } = require('node:timers/promises') + +// With queue metrics disabled (_queue: false in the metrics-outbox-disabled profile) the +// in-memory reader should never capture any `queue.*` datapoints. +const { latestDataPointValue, forceFlush, reset } = require('./bookshop/lib/MyInMemoryMetricReader') const { expect, GET } = cds.test(__dirname + '/bookshop', '--with-mocks', '--profile', 'metrics-outbox-disabled') function metricValue(metric) { - const mostRecentMetricLog = consoleDirLogs.findLast( - metricLog => metricLog[0].descriptor.name === `queue.${metric}` - )?.[0] - - if (!mostRecentMetricLog) return null - - return mostRecentMetricLog.dataPoints[0].value + return latestDataPointValue(metric) } describe('queue metrics is disabled', () => { @@ -34,12 +25,15 @@ describe('queue metrics is disabled', () => { externalServiceOne.before('*', () => {}) }) - beforeEach(() => (consoleDirLogs.length = 0)) + beforeEach(() => reset()) test('metrics are not collected', async () => { await GET('/odata/v4/proxy/proxyCallToExternalServiceOne', admin) - await wait(150) // Wait for metrics to be collected + // Assert absence: with _queue disabled no queue.* instrument is ever registered, so nothing can + // be exported. Force a few export cycles (rather than a fixed sleep) to give the app every chance + // to emit a queue metric — none must appear. + for (let i = 0; i < 5; i++) await forceFlush() expect(metricValue('cold_entries')).to.eq(null) expect(metricValue('remaining_entries')).to.eq(null) diff --git a/test/metrics-outbox-multitenant.test.js b/test/metrics-outbox-multitenant.test.js index 41b46ee0..77a4310b 100644 --- a/test/metrics-outbox-multitenant.test.js +++ b/test/metrics-outbox-multitenant.test.js @@ -1,12 +1,11 @@ -// Mock console.dir to capture logs ConsoleMetricExporter writes -const consoleDirLogs = [] -jest.spyOn(console, 'dir').mockImplementation((...args) => { - consoleDirLogs.push(args) -}) - const cds = require('@sap/cds') const { setTimeout: wait } = require('node:timers/promises') +// Exported metric data is captured in-memory by MyInMemoryMetricReader (wired via the +// metrics-outbox profile in .cdsrc.json) instead of scraping ConsoleMetricExporter's console.dir. +const { latestDataPointValue, forceFlush, reset } = require('./bookshop/lib/MyInMemoryMetricReader') +const { makeExpectEventually } = require('./utils') + const { expect, GET, axios } = cds.test( __dirname + '/bookshop', '--with-mocks', @@ -16,18 +15,18 @@ const { expect, GET, axios } = cds.test( axios.defaults.validateStatus = () => true function metricValue(tenant, metric) { - const mostRecentMetricLog = consoleDirLogs.findLast( - metricLog => metricLog[0].descriptor.name === `queue.${metric}` - )?.[0] - - if (!mostRecentMetricLog) return null - - const mostRecentTenantDataPoint = mostRecentMetricLog.dataPoints.find( - dp => dp.attributes['sap.tenancy.tenant_id'] === tenant - ) - return mostRecentTenantDataPoint ? mostRecentTenantDataPoint.value : null + return latestDataPointValue(metric, { 'sap.tenancy.tenant_id': tenant }) } +// State-based wait for metric assertions: force the wired meter provider (forceFlush) to collect + +// export, then re-run the assertion block. Replaces all fixed-time `wait(…)` sleeps — the loop +// completes the instant the in-memory per-tenant queue statistics (kept fresh by the existing +// cds.spawn poller) reflect the asserted state. forceFlush() throws fast if the provider isn't +// wired, so a misconfigured profile fails loudly instead of busy-spinning the full timeout. +const expectEventually = makeExpectEventually(forceFlush, { timeout: 10000, interval: 25 }) + +// Multitenancy runs on sqlite only; excluded from the HANA job (needs a bound Service Manager). +// See TESTING.md → Sanctioned skips. describe('queue metrics for multi tenant service', () => { const T1 = 'tenant_1' const T2 = 'tenant_2' @@ -66,7 +65,7 @@ describe('queue metrics for multi tenant service', () => { beforeEach(async () => { await cds.tx({ tenant: T1 }, () => DELETE.from('cds.outbox.Messages')) await cds.tx({ tenant: T2 }, () => DELETE.from('cds.outbox.Messages')) - consoleDirLogs.length = 0 + reset() }) describe('given the target service succeeds immediately', () => { @@ -76,34 +75,39 @@ describe('queue metrics for multi tenant service', () => { GET('/odata/v4/proxy/proxyCallToExternalServiceOne', user[T2]) ]) - await wait(150) // Wait for metrics to be collected - - expect(metricValue(T1, 'cold_entries')).to.eq(0) - expect(metricValue(T1, 'incoming_messages')).to.eq(totalInc[T1]) - expect(metricValue(T1, 'outgoing_messages')).to.eq(totalOut[T1]) - expect(metricValue(T1, 'remaining_entries')).to.eq(0) - expect(metricValue(T1, 'min_storage_time_in_seconds')).to.eq(0) - expect(metricValue(T1, 'med_storage_time_in_seconds')).to.eq(0) - expect(metricValue(T1, 'max_storage_time_in_seconds')).to.eq(0) - - expect(metricValue(T2, 'cold_entries')).to.eq(0) - expect(metricValue(T2, 'incoming_messages')).to.eq(totalInc[T2]) - expect(metricValue(T2, 'outgoing_messages')).to.eq(totalOut[T2]) - expect(metricValue(T2, 'remaining_entries')).to.eq(0) - expect(metricValue(T2, 'min_storage_time_in_seconds')).to.eq(0) - expect(metricValue(T2, 'med_storage_time_in_seconds')).to.eq(0) - expect(metricValue(T2, 'max_storage_time_in_seconds')).to.eq(0) + await expectEventually(() => { + expect(metricValue(T1, 'cold_entries')).to.eq(0) + expect(metricValue(T1, 'incoming_messages')).to.eq(totalInc[T1]) + expect(metricValue(T1, 'outgoing_messages')).to.eq(totalOut[T1]) + expect(metricValue(T1, 'remaining_entries')).to.eq(0) + expect(metricValue(T1, 'min_storage_time_in_seconds')).to.eq(0) + expect(metricValue(T1, 'med_storage_time_in_seconds')).to.eq(0) + expect(metricValue(T1, 'max_storage_time_in_seconds')).to.eq(0) + + expect(metricValue(T2, 'cold_entries')).to.eq(0) + expect(metricValue(T2, 'incoming_messages')).to.eq(totalInc[T2]) + expect(metricValue(T2, 'outgoing_messages')).to.eq(totalOut[T2]) + expect(metricValue(T2, 'remaining_entries')).to.eq(0) + expect(metricValue(T2, 'min_storage_time_in_seconds')).to.eq(0) + expect(metricValue(T2, 'med_storage_time_in_seconds')).to.eq(0) + expect(metricValue(T2, 'max_storage_time_in_seconds')).to.eq(0) + }) }) }) describe('given a target service that requires retries', () => { let currentRetryCount, unboxedService + // Fail the first 3 attempts so the 4th delivers — the same widened window #445 introduced for + // the single-tenant suite: it opens a comfortable gap between "message has aged >=1s in the + // queue" and "message is delivered and removed", which is what made the wall-clock test flaky. + const ATTEMPTS_TO_FAIL = 3 + beforeAll(async () => { unboxedService = await cds.connect.to('ExternalServiceOne') unboxedService.before('call', req => { - if ((currentRetryCount[cds.context.tenant] += 1) <= 2) { + if ((currentRetryCount[cds.context.tenant] += 1) <= ATTEMPTS_TO_FAIL) { totalFailed[cds.context.tenant] += 1 return req.reject({ status: 503 }) } @@ -119,76 +123,75 @@ describe('queue metrics for multi tenant service', () => { }) test('storage time increases before message can be delivered', async () => { + // Reference time taken BEFORE the GETs so the queuing round-trip counts toward the wall-clock debounce below. const timeOfInitialCall = Date.now() await Promise.all([ GET('/odata/v4/proxy/proxyCallToExternalServiceOne', user[T1]), GET('/odata/v4/proxy/proxyCallToExternalServiceOne', user[T2]) ]) - // Wait for the first retry to be processed - while (currentRetryCount[T1] < 2) await wait(10) - while (currentRetryCount[T2] < 2) await wait(10) - - // Wait until at least 1 second has passed since the initial call - const timeAfterFirstRetry = Date.now() - if (timeAfterFirstRetry - timeOfInitialCall < 1000) { - await wait(1000 - (timeAfterFirstRetry - timeOfInitialCall)) - } - await wait(150) // ... for metrics to be collected - - expect(metricValue(T1, 'cold_entries')).to.eq(0) - expect(metricValue(T1, 'incoming_messages')).to.eq(totalInc[T1]) - expect(metricValue(T1, 'outgoing_messages')).to.eq(totalOut[T1]) - expect(metricValue(T1, 'processing_failures')).to.eq(totalFailed[T1]) - expect(metricValue(T1, 'remaining_entries')).to.eq(1) - expect(metricValue(T1, 'min_storage_time_in_seconds')).to.be.gte(1) - expect(metricValue(T1, 'med_storage_time_in_seconds')).to.be.gte(1) - expect(metricValue(T1, 'max_storage_time_in_seconds')).to.be.gte(1) - - expect(metricValue(T2, 'cold_entries')).to.eq(0) - expect(metricValue(T2, 'incoming_messages')).to.eq(totalInc[T2]) - expect(metricValue(T2, 'outgoing_messages')).to.eq(totalOut[T2]) - expect(metricValue(T2, 'processing_failures')).to.eq(totalFailed[T2]) - expect(metricValue(T2, 'remaining_entries')).to.eq(1) - expect(metricValue(T2, 'min_storage_time_in_seconds')).to.be.gte(1) - expect(metricValue(T2, 'med_storage_time_in_seconds')).to.be.gte(1) - expect(metricValue(T2, 'max_storage_time_in_seconds')).to.be.gte(1) - - // Wait for the second retry to be processd - while (currentRetryCount[T1] < 3) await wait(10) - while (currentRetryCount[T2] < 3) await wait(10) - await wait(600) // ... for metrics to be collected - - expect(metricValue(T1, 'cold_entries')).to.eq(0) - expect(metricValue(T1, 'incoming_messages')).to.eq(totalInc[T1]) - expect(metricValue(T1, 'outgoing_messages')).to.eq(totalOut[T1]) - expect(metricValue(T1, 'processing_failures')).to.eq(totalFailed[T1]) - expect(metricValue(T1, 'remaining_entries')).to.eq(0) - expect(metricValue(T1, 'min_storage_time_in_seconds')).to.eq(0) - expect(metricValue(T1, 'med_storage_time_in_seconds')).to.eq(0) - expect(metricValue(T1, 'max_storage_time_in_seconds')).to.eq(0) - - expect(metricValue(T2, 'cold_entries')).to.eq(0) - expect(metricValue(T2, 'incoming_messages')).to.eq(totalInc[T2]) - expect(metricValue(T2, 'outgoing_messages')).to.eq(totalOut[T2]) - expect(metricValue(T2, 'processing_failures')).to.eq(totalFailed[T2]) - expect(metricValue(T2, 'remaining_entries')).to.eq(0) - expect(metricValue(T2, 'min_storage_time_in_seconds')).to.eq(0) - expect(metricValue(T2, 'med_storage_time_in_seconds')).to.eq(0) - expect(metricValue(T2, 'max_storage_time_in_seconds')).to.eq(0) + // The storage_time gauges need a real second to elapse since the messages were enqueued — + // this is the one place the test fundamentally depends on wall-clock time. + const elapsed = Date.now() - timeOfInitialCall + if (elapsed < 1500) await wait(1500 - elapsed) + + await expectEventually(() => { + // Message is still being retried (>=1s aged) for both tenants. + expect(currentRetryCount[T1]).to.be.gte(2) + expect(currentRetryCount[T2]).to.be.gte(2) + + expect(metricValue(T1, 'cold_entries')).to.eq(0) + expect(metricValue(T1, 'incoming_messages')).to.eq(totalInc[T1]) + expect(metricValue(T1, 'outgoing_messages')).to.eq(totalOut[T1]) + expect(metricValue(T1, 'processing_failures')).to.eq(totalFailed[T1]) + expect(metricValue(T1, 'remaining_entries')).to.eq(1) + expect(metricValue(T1, 'min_storage_time_in_seconds')).to.be.gte(1) + expect(metricValue(T1, 'med_storage_time_in_seconds')).to.be.gte(1) + expect(metricValue(T1, 'max_storage_time_in_seconds')).to.be.gte(1) + + expect(metricValue(T2, 'cold_entries')).to.eq(0) + expect(metricValue(T2, 'incoming_messages')).to.eq(totalInc[T2]) + expect(metricValue(T2, 'outgoing_messages')).to.eq(totalOut[T2]) + expect(metricValue(T2, 'processing_failures')).to.eq(totalFailed[T2]) + expect(metricValue(T2, 'remaining_entries')).to.eq(1) + expect(metricValue(T2, 'min_storage_time_in_seconds')).to.be.gte(1) + expect(metricValue(T2, 'med_storage_time_in_seconds')).to.be.gte(1) + expect(metricValue(T2, 'max_storage_time_in_seconds')).to.be.gte(1) + }) + + // Final attempt — the message is delivered and removed from the outbox for both tenants. + await expectEventually(() => { + expect(currentRetryCount[T1]).to.be.gte(ATTEMPTS_TO_FAIL + 1) + expect(currentRetryCount[T2]).to.be.gte(ATTEMPTS_TO_FAIL + 1) + + expect(metricValue(T1, 'cold_entries')).to.eq(0) + expect(metricValue(T1, 'incoming_messages')).to.eq(totalInc[T1]) + expect(metricValue(T1, 'outgoing_messages')).to.eq(totalOut[T1]) + expect(metricValue(T1, 'processing_failures')).to.eq(totalFailed[T1]) + expect(metricValue(T1, 'remaining_entries')).to.eq(0) + expect(metricValue(T1, 'min_storage_time_in_seconds')).to.eq(0) + expect(metricValue(T1, 'med_storage_time_in_seconds')).to.eq(0) + expect(metricValue(T1, 'max_storage_time_in_seconds')).to.eq(0) + + expect(metricValue(T2, 'cold_entries')).to.eq(0) + expect(metricValue(T2, 'incoming_messages')).to.eq(totalInc[T2]) + expect(metricValue(T2, 'outgoing_messages')).to.eq(totalOut[T2]) + expect(metricValue(T2, 'processing_failures')).to.eq(totalFailed[T2]) + expect(metricValue(T2, 'remaining_entries')).to.eq(0) + expect(metricValue(T2, 'min_storage_time_in_seconds')).to.eq(0) + expect(metricValue(T2, 'med_storage_time_in_seconds')).to.eq(0) + expect(metricValue(T2, 'max_storage_time_in_seconds')).to.eq(0) + }) }) }) describe('given a taget service that fails unrecoverably', () => { let unboxedService - const didProcess = { [T1]: false, [T2]: false } - beforeAll(async () => { unboxedService = await cds.connect.to('ExternalServiceOne') unboxedService.before('call', req => { - didProcess[cds.context.tenant] = true totalFailed[cds.context.tenant] += 1 return req.reject({ status: 418, unrecoverable: true }) }) @@ -204,21 +207,19 @@ describe('queue metrics for multi tenant service', () => { GET('/odata/v4/proxy/proxyCallToExternalServiceOne', user[T2]) ]) - while (!didProcess[T1]) await wait(10) - while (!didProcess[T2]) await wait(10) - await wait(500) // ... for metrics to be collected - - expect(metricValue(T1, 'cold_entries')).to.eq(1) - expect(metricValue(T1, 'incoming_messages')).to.eq(totalInc[T1]) - expect(metricValue(T1, 'outgoing_messages')).to.eq(totalOut[T1]) - expect(metricValue(T1, 'processing_failures')).to.eq(totalFailed[T1]) - expect(metricValue(T1, 'remaining_entries')).to.eq(0) - - expect(metricValue(T2, 'cold_entries')).to.eq(1) - expect(metricValue(T2, 'incoming_messages')).to.eq(totalInc[T2]) - expect(metricValue(T2, 'outgoing_messages')).to.eq(totalOut[T2]) - expect(metricValue(T2, 'processing_failures')).to.eq(totalFailed[T2]) - expect(metricValue(T2, 'remaining_entries')).to.eq(0) + await expectEventually(() => { + expect(metricValue(T1, 'cold_entries')).to.eq(1) + expect(metricValue(T1, 'incoming_messages')).to.eq(totalInc[T1]) + expect(metricValue(T1, 'outgoing_messages')).to.eq(totalOut[T1]) + expect(metricValue(T1, 'processing_failures')).to.eq(totalFailed[T1]) + expect(metricValue(T1, 'remaining_entries')).to.eq(0) + + expect(metricValue(T2, 'cold_entries')).to.eq(1) + expect(metricValue(T2, 'incoming_messages')).to.eq(totalInc[T2]) + expect(metricValue(T2, 'outgoing_messages')).to.eq(totalOut[T2]) + expect(metricValue(T2, 'processing_failures')).to.eq(totalFailed[T2]) + expect(metricValue(T2, 'remaining_entries')).to.eq(0) + }) }) }) }) diff --git a/test/metrics-outbox.test.js b/test/metrics-outbox.test.js index f87c3c67..ce1847f2 100644 --- a/test/metrics-outbox.test.js +++ b/test/metrics-outbox.test.js @@ -1,8 +1,4 @@ -// Mock console.dir to capture logs ConsoleMetricExporter writes -const consoleDirLogs = [] -jest.spyOn(console, 'dir').mockImplementation((...args) => { - consoleDirLogs.push(args) -}) +import { vi } from 'vitest' const E1 = 'ExternalServiceOne' const E2 = 'ExternalServiceTwo' @@ -10,24 +6,35 @@ const E2 = 'ExternalServiceTwo' const cds = require('@sap/cds') const { setTimeout: wait } = require('node:timers/promises') +// Exported metric data is captured in-memory by MyInMemoryMetricReader (wired via the +// metrics-outbox profile in .cdsrc.json) instead of scraping ConsoleMetricExporter's console.dir. +const { latestDataPointValue, forceFlush, reset } = require('./bookshop/lib/MyInMemoryMetricReader') +const { clearOutbox, makeExpectEventually } = require('./utils') + const { expect, GET, axios } = cds.test(__dirname + '/bookshop', '--with-mocks', '--profile', 'metrics-outbox') axios.defaults.validateStatus = () => true function metricValue(metric, queuedServiceName) { - const mostRecentMetricLog = consoleDirLogs.findLast( - metricLog => metricLog[0].descriptor.name === `queue.${metric}` && metricLog[0].dataPoints?.length - )?.[0] - - const mestRecentQueueMetricData = mostRecentMetricLog?.dataPoints.find( - dataPoint => dataPoint.attributes['queue.name'] === queuedServiceName - ) - - if (!mestRecentQueueMetricData) return null - - return mestRecentQueueMetricData.value + return latestDataPointValue(metric, { 'queue.name': queuedServiceName }) } -const debugLog = (cds.log('telemetry').debug = jest.fn(() => {})) +// State-based wait for metric assertions: force the wired meter provider (forceFlush) to collect + +// export, then re-run the assertion block. Replaces all fixed-time `wait(150)` sleeps — the loop +// completes the instant the in-memory queue statistics (kept fresh by the queue-stats cds.spawn +// poller) reflect the asserted state. forceFlush() throws fast if the provider isn't wired, so a +// misconfigured profile fails loudly instead of busy-spinning the full timeout. +// +// interval is 500ms (NOT a few ms): each forceFlush() triggers a metric collection that runs the +// queue-stats poller's SELECTs against the DB. On the SHARED HANA HDI container a tight poll loop +// (plus the profile's background export) starves the queue worker of connections, so its retries +// stall and delivery never completes — which manifested as `expected N to be at least M` flakes +// and, via ensuing hook hangs + pool exhaustion, ECONNREFUSED cascades into later files' servers. +// Polling at 500ms (with the profile's exportIntervalMillis raised to 1000ms) leaves the worker +// enough DB headroom to make all its attempts. The loop still returns the instant the state holds, +// so sqlite (per-file in-memory DB) still satisfies in well under a second. +const expectEventually = makeExpectEventually(forceFlush, { timeout: 30000, interval: 500 }) + +const debugLog = (cds.log('telemetry').debug = vi.fn(() => {})) describe('queue metrics for single tenant service', () => { let totalInc = { [E1]: 0, [E2]: 0 } @@ -72,46 +79,75 @@ describe('queue metrics for single tenant service', () => { }) beforeEach(async () => { - await DELETE.from('cds.outbox.Messages') - consoleDirLogs.length = 0 + await clearOutbox() + reset() debugLog.mockClear() }) + // Leave the shared DB clean for the next test file and let background queue workers settle. + // On HANA all files share one HDI container, so (a) the undeliverable `unknown-service` row + // inserted by the last case below would otherwise linger and skew another file's queue metrics, + // and (b) an in-flight worker retrying a message could fire this file's `before('call')` handler + // during teardown. Clear, wait a beat for any in-flight worker iteration to finish, clear again. + // Every clear is timeout-bounded (clearOutbox) so a draining pool can't hang the hook. HANA-only: + // sqlite gets a fresh in-memory DB per file, so the settle is pointless there. + afterAll(async () => { + await clearOutbox() + if (process.env.TELEMETRY_TEST_HANA) { + await wait(5000) + await clearOutbox() + } + }) + describe('given the target service succeeds immediately', () => { test('metrics are collected', async () => { await GET('/odata/v4/proxy/proxyCallToExternalServiceOne', admin) - await wait(150) // Wait for metrics to be collected - - expect(metricValue('cold_entries', E1)).to.eq(0) - expect(metricValue('remaining_entries', E1)).to.eq(0) - expect(metricValue('incoming_messages', E1)).to.eq(totalInc[E1]) - expect(metricValue('outgoing_messages', E1)).to.eq(totalOut[E1]) - expect(metricValue('processing_failures', E1)).to.eq(totalFailed[E1]) - expect(metricValue('min_storage_time_in_seconds', E1)).to.eq(0) - expect(metricValue('med_storage_time_in_seconds', E1)).to.eq(0) - expect(metricValue('max_storage_time_in_seconds', E1)).to.eq(0) + await expectEventually(() => { + expect(metricValue('cold_entries', E1)).to.eq(0) + expect(metricValue('remaining_entries', E1)).to.eq(0) + expect(metricValue('incoming_messages', E1)).to.eq(totalInc[E1]) + expect(metricValue('outgoing_messages', E1)).to.eq(totalOut[E1]) + expect(metricValue('processing_failures', E1)).to.eq(totalFailed[E1]) + expect(metricValue('min_storage_time_in_seconds', E1)).to.eq(0) + expect(metricValue('med_storage_time_in_seconds', E1)).to.eq(0) + expect(metricValue('max_storage_time_in_seconds', E1)).to.eq(0) + }) await GET('/odata/v4/proxy/proxyCallToExternalServiceTwo', admin) - await wait(150) // Wait for metrics to be collected - - expect(metricValue('cold_entries', E2)).to.eq(0) - expect(metricValue('remaining_entries', E2)).to.eq(0) - expect(metricValue('incoming_messages', E2)).to.eq(totalInc[E2]) - expect(metricValue('outgoing_messages', E2)).to.eq(totalOut[E2]) - expect(metricValue('processing_failures', E2)).to.eq(totalFailed[E2]) - expect(metricValue('min_storage_time_in_seconds', E2)).to.eq(0) - expect(metricValue('med_storage_time_in_seconds', E2)).to.eq(0) - expect(metricValue('max_storage_time_in_seconds', E2)).to.eq(0) + await expectEventually(() => { + expect(metricValue('cold_entries', E2)).to.eq(0) + expect(metricValue('remaining_entries', E2)).to.eq(0) + expect(metricValue('incoming_messages', E2)).to.eq(totalInc[E2]) + expect(metricValue('outgoing_messages', E2)).to.eq(totalOut[E2]) + expect(metricValue('processing_failures', E2)).to.eq(totalFailed[E2]) + expect(metricValue('min_storage_time_in_seconds', E2)).to.eq(0) + expect(metricValue('med_storage_time_in_seconds', E2)).to.eq(0) + expect(metricValue('max_storage_time_in_seconds', E2)).to.eq(0) + }) }) }) describe('given a target service that requires retries', () => { - let currentRetryCount, customizedHandler + // Initialized at declaration (not left undefined): the `before('call')` handler registered in + // beforeAll stays live for the whole describe, so a background queue-worker retry can fire it + // OUTSIDE any test's window (between tests, or during teardown). If currentRetryCount were + // undefined then, `currentRetryCount[E]` throws — the queue logs "Programming error detected" + // and the delivery the test expects never completes. On HANA the slower retry cadence + pool + // drain at teardown reliably hits that gap; sqlite's timing never exposed it. beforeEach still + // re-zeroes it per test. + let currentRetryCount = { [E1]: 0, [E2]: 0 } + let customizedHandler + // Fail the first 3 attempts so the 4th delivers. With the queue's exp-backoff schedule + // (0.5s, 1.25s, 2.375s, ...), this places the 4th attempt at ~t=4.1s after enqueue — + // giving a comfortable ~3s window between "message has aged 1s in the queue" and + // "message is finally delivered and removed". Tightening that window is what made the + // original wall-clock-based test flaky. + const ATTEMPTS_TO_FAIL = 3 const customizedHandlerFor = E => req => { - if ((currentRetryCount[E] += 1) <= 2) { + if ((currentRetryCount[E] += 1) <= ATTEMPTS_TO_FAIL) { totalFailed[E] += 1 return req.reject({ status: 503 }) } @@ -141,88 +177,83 @@ describe('queue metrics for single tenant service', () => { test('storage time increases before message can be delivered', async () => { await GET('/odata/v4/proxy/proxyCallToExternalServiceOne', admin) await GET('/odata/v4/proxy/proxyCallToExternalServiceTwo', admin) - + // Reference time taken after GETs return — i.e. after both messages are persisted in the outbox. const timeOfInitialCall = Date.now() - await wait(150) // ... for metrics to be collected - expect(currentRetryCount[E1]).to.eq(1) - expect(currentRetryCount[E2]).to.eq(1) - - expect(metricValue('cold_entries', E1)).to.eq(0) - expect(metricValue('remaining_entries', E1)).to.eq(1) - expect(metricValue('incoming_messages', E1)).to.eq(totalInc[E1]) - expect(metricValue('outgoing_messages', E1)).to.eq(totalOut[E1]) - expect(metricValue('processing_failures', E1)).to.eq(totalFailed[E1]) - expect(metricValue('min_storage_time_in_seconds', E1)).to.eq(0) - expect(metricValue('med_storage_time_in_seconds', E1)).to.eq(0) - expect(metricValue('max_storage_time_in_seconds', E1)).to.eq(0) - - expect(metricValue('cold_entries', E2)).to.eq(0) - expect(metricValue('remaining_entries', E2)).to.eq(1) - expect(metricValue('incoming_messages', E2)).to.eq(totalInc[E2]) - expect(metricValue('outgoing_messages', E2)).to.eq(totalOut[E2]) - expect(metricValue('processing_failures', E2)).to.eq(totalFailed[E2]) - expect(metricValue('min_storage_time_in_seconds', E2)).to.eq(0) - expect(metricValue('med_storage_time_in_seconds', E2)).to.eq(0) - expect(metricValue('max_storage_time_in_seconds', E2)).to.eq(0) - - // Wait for the first retry to be initiated - while (currentRetryCount[E1] < 2) await wait(10) - while (currentRetryCount[E2] < 2) await wait(10) - await wait(150) // ... for the retry to be processed and metrics to be collected - expect(currentRetryCount[E1]).to.eq(2) - expect(currentRetryCount[E2]).to.eq(2) - - // Wait until at least 1 second has passed since the initial call - const timeAfterFirstRetry = Date.now() - if (timeAfterFirstRetry - timeOfInitialCall < 1000) { - await wait(1000 - (timeAfterFirstRetry - timeOfInitialCall)) - } + // Freshly-enqueued state: each message is present (remaining == 1) and not cold. We assert + // each service in its OWN poll (E1 and E2 stagger; coupling them risks one aging out before + // the other aligns). Storage_time is asserted as a small upper bound rather than exactly 0: + // the "just enqueued, ~0s old" state is a sub-second transient and on HANA the queue-stats + // poller's first observation already lands with storage_time >= 1 (poll interval + query + // latency), so `== 0` is not reliably observable. The `< 60` bound still guards the timezone + // regression this suite covers (a naive-timestamp misparse reported storage_time as ~7200s); + // storage-time GROWTH is asserted in the next block, delivery/removal in the one after. + const assertFreshlyEnqueued = E => + expectEventually(() => { + expect(metricValue('cold_entries', E)).to.eq(0) + expect(metricValue('remaining_entries', E)).to.eq(1) + expect(metricValue('incoming_messages', E)).to.eq(totalInc[E]) + expect(metricValue('outgoing_messages', E)).to.eq(totalOut[E]) + expect(metricValue('processing_failures', E)).to.eq(totalFailed[E]) + expect(metricValue('min_storage_time_in_seconds', E)).to.be.lessThan(60) + expect(metricValue('med_storage_time_in_seconds', E)).to.be.lessThan(60) + expect(metricValue('max_storage_time_in_seconds', E)).to.be.lessThan(60) + }) + await Promise.all([assertFreshlyEnqueued(E1), assertFreshlyEnqueued(E2)]) + + // The storage_time gauges need a real second to elapse since the messages were enqueued — + // this is the one place the test fundamentally depends on wall-clock time. + const elapsed = Date.now() - timeOfInitialCall + if (elapsed < 1500) await wait(1500 - elapsed) + + await expectEventually(() => { + // Either still on attempt 2 (waiting to retry) or on attempt 3 (delivered) — both are fine + // for these assertions, the message has been in the queue >=1s either way. + expect(currentRetryCount[E1]).to.be.gte(2) + expect(currentRetryCount[E2]).to.be.gte(2) + + expect(metricValue('cold_entries', E1)).to.eq(0) + expect(metricValue('remaining_entries', E1)).to.eq(1) + expect(metricValue('incoming_messages', E1)).to.eq(totalInc[E1]) + expect(metricValue('outgoing_messages', E1)).to.eq(totalOut[E1]) + expect(metricValue('processing_failures', E1)).to.eq(totalFailed[E1]) + expect(metricValue('min_storage_time_in_seconds', E1)).to.be.gte(1) + expect(metricValue('med_storage_time_in_seconds', E1)).to.be.gte(1) + expect(metricValue('max_storage_time_in_seconds', E1)).to.be.gte(1) + + expect(metricValue('cold_entries', E2)).to.eq(0) + expect(metricValue('remaining_entries', E2)).to.eq(1) + expect(metricValue('incoming_messages', E2)).to.eq(totalInc[E2]) + expect(metricValue('outgoing_messages', E2)).to.eq(totalOut[E2]) + expect(metricValue('processing_failures', E2)).to.eq(totalFailed[E2]) + expect(metricValue('min_storage_time_in_seconds', E2)).to.be.gte(1) + expect(metricValue('med_storage_time_in_seconds', E2)).to.be.gte(1) + expect(metricValue('max_storage_time_in_seconds', E2)).to.be.gte(1) + }) - await wait(150) // ... for metrics to be collected again - - expect(metricValue('cold_entries', E1)).to.eq(0) - expect(metricValue('remaining_entries', E1)).to.eq(1) - expect(metricValue('incoming_messages', E1)).to.eq(totalInc[E1]) - expect(metricValue('outgoing_messages', E1)).to.eq(totalOut[E1]) - expect(metricValue('processing_failures', E1)).to.eq(totalFailed[E1]) - expect(metricValue('min_storage_time_in_seconds', E1)).to.be.gte(1) - expect(metricValue('med_storage_time_in_seconds', E1)).to.be.gte(1) - expect(metricValue('max_storage_time_in_seconds', E1)).to.be.gte(1) - - expect(metricValue('cold_entries', E2)).to.eq(0) - expect(metricValue('remaining_entries', E2)).to.eq(1) - expect(metricValue('incoming_messages', E2)).to.eq(totalInc[E2]) - expect(metricValue('outgoing_messages', E2)).to.eq(totalOut[E2]) - expect(metricValue('processing_failures', E2)).to.eq(totalFailed[E2]) - expect(metricValue('min_storage_time_in_seconds', E2)).to.be.gte(1) - expect(metricValue('med_storage_time_in_seconds', E2)).to.be.gte(1) - expect(metricValue('max_storage_time_in_seconds', E2)).to.be.gte(1) - - // Wait for the second retry to be initiated - while (currentRetryCount[E1] < 3) await wait(10) - while (currentRetryCount[E2] < 3) await wait(10) - await wait(150) // ... for the retry to be processed and metrics to be collected - expect(currentRetryCount[E1]).to.eq(3) - expect(currentRetryCount[E2]).to.eq(3) - - expect(metricValue('cold_entries', E1)).to.eq(0) - expect(metricValue('remaining_entries', E1)).to.eq(0) - expect(metricValue('incoming_messages', E1)).to.eq(totalInc[E1]) - expect(metricValue('outgoing_messages', E1)).to.eq(totalOut[E1]) - expect(metricValue('processing_failures', E1)).to.eq(totalFailed[E1]) - expect(metricValue('min_storage_time_in_seconds', E1)).to.eq(0) - expect(metricValue('med_storage_time_in_seconds', E1)).to.eq(0) - expect(metricValue('max_storage_time_in_seconds', E1)).to.eq(0) - - expect(metricValue('cold_entries', E2)).to.eq(0) - expect(metricValue('remaining_entries', E2)).to.eq(0) - expect(metricValue('incoming_messages', E2)).to.eq(totalInc[E2]) - expect(metricValue('outgoing_messages', E2)).to.eq(totalOut[E2]) - expect(metricValue('processing_failures', E2)).to.eq(totalFailed[E2]) - expect(metricValue('min_storage_time_in_seconds', E2)).to.eq(0) - expect(metricValue('med_storage_time_in_seconds', E2)).to.eq(0) - expect(metricValue('max_storage_time_in_seconds', E2)).to.eq(0) + // Final attempt — the message is delivered and removed from the outbox. + await expectEventually(() => { + expect(currentRetryCount[E1]).to.be.gte(ATTEMPTS_TO_FAIL + 1) + expect(currentRetryCount[E2]).to.be.gte(ATTEMPTS_TO_FAIL + 1) + + expect(metricValue('cold_entries', E1)).to.eq(0) + expect(metricValue('remaining_entries', E1)).to.eq(0) + expect(metricValue('incoming_messages', E1)).to.eq(totalInc[E1]) + expect(metricValue('outgoing_messages', E1)).to.eq(totalOut[E1]) + expect(metricValue('processing_failures', E1)).to.eq(totalFailed[E1]) + expect(metricValue('min_storage_time_in_seconds', E1)).to.eq(0) + expect(metricValue('med_storage_time_in_seconds', E1)).to.eq(0) + expect(metricValue('max_storage_time_in_seconds', E1)).to.eq(0) + + expect(metricValue('cold_entries', E2)).to.eq(0) + expect(metricValue('remaining_entries', E2)).to.eq(0) + expect(metricValue('incoming_messages', E2)).to.eq(totalInc[E2]) + expect(metricValue('outgoing_messages', E2)).to.eq(totalOut[E2]) + expect(metricValue('processing_failures', E2)).to.eq(totalFailed[E2]) + expect(metricValue('min_storage_time_in_seconds', E2)).to.eq(0) + expect(metricValue('med_storage_time_in_seconds', E2)).to.eq(0) + expect(metricValue('max_storage_time_in_seconds', E2)).to.eq(0) + }) }) }) @@ -255,25 +286,25 @@ describe('queue metrics for single tenant service', () => { await GET('/odata/v4/proxy/proxyCallToExternalServiceOne', admin) await GET('/odata/v4/proxy/proxyCallToExternalServiceTwo', admin) - await wait(150) // ... for metrics to be collected - - expect(metricValue('cold_entries', E1)).to.eq(1) - expect(metricValue('remaining_entries', E1)).to.eq(0) - expect(metricValue('incoming_messages', E1)).to.eq(totalInc[E1]) - expect(metricValue('outgoing_messages', E1)).to.eq(totalOut[E1]) - expect(metricValue('processing_failures', E1)).to.eq(totalFailed[E1]) - expect(metricValue('min_storage_time_in_seconds', E1)).to.eq(0) - expect(metricValue('med_storage_time_in_seconds', E1)).to.eq(0) - expect(metricValue('max_storage_time_in_seconds', E1)).to.eq(0) - - expect(metricValue('cold_entries', E2)).to.eq(1) - expect(metricValue('remaining_entries', E2)).to.eq(0) - expect(metricValue('incoming_messages', E2)).to.eq(totalInc[E2]) - expect(metricValue('outgoing_messages', E2)).to.eq(totalOut[E2]) - expect(metricValue('processing_failures', E2)).to.eq(totalFailed[E2]) - expect(metricValue('min_storage_time_in_seconds', E2)).to.eq(0) - expect(metricValue('med_storage_time_in_seconds', E2)).to.eq(0) - expect(metricValue('max_storage_time_in_seconds', E2)).to.eq(0) + await expectEventually(() => { + expect(metricValue('cold_entries', E1)).to.eq(1) + expect(metricValue('remaining_entries', E1)).to.eq(0) + expect(metricValue('incoming_messages', E1)).to.eq(totalInc[E1]) + expect(metricValue('outgoing_messages', E1)).to.eq(totalOut[E1]) + expect(metricValue('processing_failures', E1)).to.eq(totalFailed[E1]) + expect(metricValue('min_storage_time_in_seconds', E1)).to.eq(0) + expect(metricValue('med_storage_time_in_seconds', E1)).to.eq(0) + expect(metricValue('max_storage_time_in_seconds', E1)).to.eq(0) + + expect(metricValue('cold_entries', E2)).to.eq(1) + expect(metricValue('remaining_entries', E2)).to.eq(0) + expect(metricValue('incoming_messages', E2)).to.eq(totalInc[E2]) + expect(metricValue('outgoing_messages', E2)).to.eq(totalOut[E2]) + expect(metricValue('processing_failures', E2)).to.eq(totalFailed[E2]) + expect(metricValue('min_storage_time_in_seconds', E2)).to.eq(0) + expect(metricValue('med_storage_time_in_seconds', E2)).to.eq(0) + expect(metricValue('max_storage_time_in_seconds', E2)).to.eq(0) + }) }) }) diff --git a/test/metrics.test.js b/test/metrics.test.js index 78765d24..84d68f1d 100644 --- a/test/metrics.test.js +++ b/test/metrics.test.js @@ -1,36 +1,76 @@ -// process.env.HOST_METRICS_RETAIN_SYSTEM = 'true' //> with this the test would fail -process.env.HOST_METRICS_LOG_SYSTEM = 'true' +// Integration tests for metrics collection — asserts on what is actually COLLECTED (which +// instruments produce datapoints, and how many), captured in-memory by MyInMemoryMetricReader +// (wired via the `metrics` profile in .cdsrc.json) instead of scraping ConsoleMetricExporter's +// log output. The formatting of those metrics is unit-tested in console-metric-exporter.test.js. const cds = require('@sap/cds') + +const { captured, forceFlush, reset } = require('./bookshop/lib/MyInMemoryMetricReader') +const { makeExpectEventually } = require('./utils') + const { expect, GET } = cds.test(__dirname + '/bookshop', '--profile', 'metrics') -const log = cds.test.log() -const wait = require('node:timers/promises').setTimeout +// State-based wait for metric assertions: force the wired meter provider (forceFlush) to collect + +// export, then re-run the assertion block. Replaces fixed-time sleeps — the loop completes the +// instant the captured datapoints reflect the asserted state. forceFlush() throws fast if the +// provider isn't wired, so a misconfigured profile fails loudly instead of busy-spinning the timeout. +const expectEventually = makeExpectEventually(forceFlush, { timeout: 10000, interval: 25 }) + +// All metric descriptor names present across every captured export. +function capturedMetricNames() { + const names = new Set() + for (const rm of captured) { + for (const scopeMetrics of rm.scopeMetrics) { + for (const metric of scopeMetrics.metrics) names.add(metric.descriptor.name) + } + } + return names +} + +// Most recent captured MetricData for the given descriptor name (newest export first). +function latestMetric(name) { + for (let i = captured.length - 1; i >= 0; i--) { + for (const scopeMetrics of captured[i].scopeMetrics) { + for (const metric of scopeMetrics.metrics) { + if (metric.descriptor.name === name && metric.dataPoints?.length) return metric + } + } + } + return null +} describe('metrics', () => { const admin = { auth: { username: 'alice' } } - beforeEach(log.clear) + beforeEach(reset) test('system metrics are not collected by default', async () => { const { status } = await GET('/odata/v4/admin/Books', admin) expect(status).to.equal(200) - await wait(100) - - expect(log.output).to.match(/process/i) - expect(log.output).not.to.match(/network/i) + await expectEventually(() => { + const names = capturedMetricNames() + // process.* host metrics ARE collected out of the box ... + expect([...names].some(n => n.startsWith('process.'))).to.be.true + // ... but system.* (network/cpu/memory) collection is NOT enabled by default. + expect([...names].some(n => n.startsWith('system.'))).to.be.false + expect([...names].some(n => n.includes('network'))).to.be.false + }) }) - test('other metrics with multiple datapoints are logged as array', async () => { + test('other metrics can carry multiple datapoints', async () => { const { status } = await GET('/odata/v4/admin/Books', admin) expect(status).to.equal(200) - await wait(200) - - // nodejs.eventloop.time has multiple datapoints (active + idle) → logged as array - expect(log.output).to.match(/nodejs\.eventloop\.time: \[/) - // nodejs.eventloop.utilization has single datapoint → logged unwrapped (not as array) - expect(log.output).to.match(/nodejs\.eventloop\.utilization: \{/) + await expectEventually(() => { + // nodejs.eventloop.time is collected with multiple datapoints (active + idle) ... + const time = latestMetric('nodejs.eventloop.time') + expect(time).to.exist + expect(time.dataPoints.length).to.be.greaterThan(1) + // ... whereas nodejs.eventloop.utilization is a single datapoint. + const utilization = latestMetric('nodejs.eventloop.utilization') + expect(utilization).to.exist + expect(utilization.dataPoints.length).to.equal(1) + }) }) }) diff --git a/test/passport.test.js b/test/passport.test.js index 1f9de9b0..0b58693e 100644 --- a/test/passport.test.js +++ b/test/passport.test.js @@ -2,11 +2,11 @@ process.env.SAP_PASSPORT = 'true' // CDS v10 enables scheduling by default; its periodic outbox reads run in // their own transactions and cause spurious SAP_PASSPORT set/reset pairs on -// the connection, breaking the deterministic _count assertions below. -process.env.cds_requires_scheduling = 'false' - +// the connection, breaking the deterministic _count assertions below. The +// `no-scheduling` profile (requires.scheduling: false) in test/bookshop/.cdsrc.json +// disables it. const cds = require('@sap/cds') -const { expect, GET } = cds.test().in(__dirname + '/bookshop') +const { expect, GET } = cds.test(__dirname + '/bookshop', '--profile', 'no-scheduling') describe('SAP Passport', () => { if (cds.env.requires.db.kind === 'sqlite') return test.skip('n/a for SQLite', () => {}) diff --git a/test/tracing-attributes.test.js b/test/tracing-attributes.test.js index 3b15627c..535cf863 100644 --- a/test/tracing-attributes.test.js +++ b/test/tracing-attributes.test.js @@ -1,33 +1,44 @@ -// REVISIT: use native fetch in cds oq -process.env.cds_remote_native__fetch = 'true' - +// Use native fetch in CDS OQ so @opentelemetry/instrumentation-undici can see outbound calls +// (cds.env.remote.native_fetch = true), configured via the `native-fetch` profile in +// test/bookshop/.cdsrc.json, composed with `tracing-in-memory`. const cds = require('@sap/cds') -const { expect, data } = cds.test(__dirname + '/bookshop', '--profile', 'tracing-attributes') +const { expect, data } = cds.test(__dirname + '/bookshop', '--profile', 'tracing-in-memory, native-fetch') const http = require('http') -describe('tracing attributes', () => { - beforeEach(data.reset) +// The tracing-in-memory profile (see test/bookshop/.cdsrc.json) configures +// MyInMemorySpanExporter as the trace exporter. We read the captured ReadableSpan +// objects directly out of its shared buffer — no console spy, no provider-poking. +const { captured } = require('./bookshop/lib/MyInMemorySpanExporter') + +beforeEach(async () => { + // data.reset is itself heavily traced (it runs DELETEs + INSERTs for the seed data) — + // run it first, THEN clear the buffer so the test only sees its own spans. + await data.reset() + captured.length = 0 +}) - const log = jest.spyOn(console, 'dir') - beforeEach(log.mockClear) +// Returns all finished spans, optionally filtered by a predicate. +const spans = filter => (filter ? captured.filter(filter) : captured.slice()) +describe('tracing attributes', () => { describe('remote', () => { let server, port - beforeAll(done => { - server = http.createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end(JSON.stringify({ value: [] })) - }) - server.listen(0, () => { - port = server.address().port - done() - }) - }) + beforeAll( + () => + new Promise(resolve => { + server = http.createServer((req, res) => { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ value: [] })) + }) + server.listen(0, () => { + port = server.address().port + resolve() + }) + }) + ) - afterAll(done => { - server.close(done) - }) + afterAll(() => new Promise(resolve => server.close(resolve))) test('HTTP client attributes are set on remote service span', async () => { // configure destination URL directly on credentials @@ -37,54 +48,70 @@ describe('tracing attributes', () => { // no mock handler - let it make the actual HTTP call await remote.send({ method: 'GET', path: '/test' }) - const output = JSON.stringify(log.mock.calls) - expect(output).to.match(/"http\.request\.method":"GET"/) - expect(output).to.match(/"http\.response\.status_code":200/) - expect(output).to.match(new RegExp(`"url\\.full":"http://localhost:${port}/test"`)) - expect(output).to.match(/"server\.address":"localhost"/) - expect(output).to.match(new RegExp(`"server\\.port":${port}`)) + // Find the HTTP client span (instrumented by OTel's http instrumentation) + const httpSpan = spans(s => s.attributes['http.request.method'] === 'GET' && s.attributes['url.full']) + expect(httpSpan.length).to.be.gte(1, 'expected an HTTP client span') + const attrs = httpSpan[0].attributes + expect(attrs).to.include({ + 'http.request.method': 'GET', + 'http.response.status_code': 200, + 'url.full': `http://localhost:${port}/test`, + 'server.address': 'localhost', + 'server.port': port + }) }) }) describe('db', () => { const _db_spans = require('./_db_spans') - // prettier-ignore - const _get_db_spans = o => JSON.parse(o).map(o => o[0]).filter(s => !s.name.startsWith('db')) - const _match_db_spans = (output, kind) => { - const db_spans = _get_db_spans(output) - for (const each of _db_spans[kind]) expect(db_spans).to.containSubset([each]) + + // Filter out the high-level "db - …" CAP wrapper spans, keep only the @cap-js/ ones + // that carry the actual DB attributes. + const dbSpans = () => spans(s => !s.name.startsWith('db')) + + const _match_db_spans = kind => { + const got = dbSpans().map(s => ({ name: s.name, attributes: { ...s.attributes } })) + for (const each of _db_spans[kind]) expect(got).to.containSubset([each]) } test('SELECT', async () => { await SELECT.from('sap.capire.bookshop.Books').where('title !=', 'DUMMY') - const output = JSON.stringify(log.mock.calls) - expect(output).to.match(/db\.client\.response.returned_rows":5/) - _match_db_spans(output, 'SELECT') + const rowCounts = dbSpans() + .map(s => s.attributes['db.client.response.returned_rows']) + .filter(v => v != null) + expect(rowCounts).to.include(5) + _match_db_spans('SELECT') }) test('INSERT', async () => { await INSERT.into('sap.capire.bookshop.Books').entries([{ ID: 1 }, { ID: 2 }]) - const output = JSON.stringify(log.mock.calls) - expect(output).to.match(/db\.client\.response.returned_rows":2/) + const rowCounts = dbSpans() + .map(s => s.attributes['db.client.response.returned_rows']) + .filter(v => v != null) + expect(rowCounts).to.include(2) // TODO - // _match_db_spans(output, 'INSERT') + // _match_db_spans('INSERT') }) test('UPDATE', async () => { await UPDATE('sap.capire.bookshop.Books').set({ stock: 42 }).where('ID > 250') - const output = JSON.stringify(log.mock.calls) - expect(output).to.match(/db\.client\.response.returned_rows":3/) + const rowCounts = dbSpans() + .map(s => s.attributes['db.client.response.returned_rows']) + .filter(v => v != null) + expect(rowCounts).to.include(3) // TODO - // _match_db_spans(output, 'UPDATE') + // _match_db_spans('UPDATE') }) test('DELETE', async () => { await DELETE.from('sap.capire.bookshop.Books') - const output = JSON.stringify(log.mock.calls) - expect(output).to.match(/db\.client\.response.returned_rows":0/) //> texts - expect(output).to.match(/db\.client\.response.returned_rows":5/) + const rowCounts = dbSpans() + .map(s => s.attributes['db.client.response.returned_rows']) + .filter(v => v != null) + expect(rowCounts).to.include(0) // texts + expect(rowCounts).to.include(5) // TODO - // _match_db_spans(output, 'DELETE') + // _match_db_spans('DELETE') }) }) }) diff --git a/test/tracing-messaging-inboxed.test.js b/test/tracing-messaging-inboxed.test.js new file mode 100644 index 00000000..be906ae5 --- /dev/null +++ b/test/tracing-messaging-inboxed.test.js @@ -0,0 +1,68 @@ +const CASE = 'inboxed' + +const cds = require('@sap/cds') +const otel = require('@opentelemetry/api') + +// `inboxed: true` combined with the default outboxed messaging behavior means TWO queue +// workers get involved per emit — one on the producer side (drains outbox to broker) and +// one on the consumer side (drains inbox to subscribers). Each worker runs two +// transactions (tx 1: lock; tx 2: handle + delete). +// +// Each worker iteration is wrapped by `cds.spawn`, so both txs collapse under a single +// `cds.spawn - run task` root. With incoming HTTP instrumentation on, the producer trace is +// rooted at the incoming SERVER span for the emit-triggering POST (AdminService - tx nests +// under it). 4 meaningful roots: +// +// 1. POST (incoming SERVER span) (producer: AdminService - tx, handle test_emit, UPSERT outbox) +// 2. cds.spawn - run task (outbox worker: dispatches to file) +// ├─ db - tx (tx 1: lock) +// └─ messaging - tx (tx 2: handle foo — writes to file — + DELETE) +// 3. messaging - tx (file-based CONSUMER: writes inbox row) +// └─ ...enqueue into inbox... +// 4. cds.spawn - run task (inbox worker: runs subscriber) +// ├─ db - tx (tx 1: lock) +// └─ messaging - tx (tx 2: handle foo — SELECT Books — + DELETE) +// +// Tolerated: allow one extra root for the scheduling-service bookkeeping startup scan. + +// Messaging config (kind/file/inboxed) comes from the `inboxed` profile in +// test/bookshop/.cdsrc.json, composed with `tracing-in-memory` by the shared harness. +const CHECK = ({ expect, rootSpans, groupedByTrace }) => { + // Producer trace + const producer = groupedByTrace.find(g => g.all.some(s => s.name === 'AdminService - handle test_emit')) + expect(producer, 'expected a producer trace').to.exist + expect(producer.root.kind, 'producer trace rooted at the incoming SERVER span').to.equal(otel.SpanKind.SERVER) + expect(producer.all.some(s => s.name === 'AdminService - tx')).to.be.true + expect(producer.all.some(s => s.name === 'db - UPSERT cds.outbox.Messages')).to.be.true + + // The inbox worker must have run the application handler (SELECT Books). + const allSpans = groupedByTrace.flatMap(g => g.all) + expect(allSpans.some(s => s.name.match(/READ sap\.capire\.bookshop\.Books/))).to.be.true + expect(allSpans.some(s => s.name === 'db - DELETE cds.outbox.Messages')).to.be.true + + // Exactly two `cds.spawn - run task` roots (outbox worker + inbox worker). + const workerRoots = rootSpans.filter(s => s.name === 'cds.spawn - run task') + expect(workerRoots, 'expected two queue-worker spawn roots (outbox + inbox)').to.have.lengthOf(2) + + // One of the spawn roots (the inbox worker) ran the app handler. + const inboxWorker = groupedByTrace.find( + g => g.root.name === 'cds.spawn - run task' && g.all.some(s => s.name.match(/READ sap\.capire\.bookshop\.Books/)) + ) + expect(inboxWorker, 'expected an inbox-worker trace that ran the application handler').to.exist + + // 4 meaningful roots (+1 tolerated bookkeeping scan). + expect(rootSpans.length).to.be.gte(4) + expect(rootSpans.length).to.be.lte(5) +} + +describe(`tracing messaging - ${CASE}`, () => { + // Queue-worker tracing needs the cds.spawn fix (@sap/cds >= 10.1) on sqlite; HANA always runs. Tracked in #477 §1. + // Dormant on CI until the transitive @sap/cds reaches 10.1 (the cds-10 leg resolves 10.0.x today); cds-9 always skips. + // See TESTING.md → Sanctioned skips (and HANA signalling: why we branch on TELEMETRY_TEST_HANA, not cds.env). + const [maj, min] = cds.version.split('.').map(Number) + if (!process.env.TELEMETRY_TEST_HANA && (maj < 10 || (maj === 10 && min < 1))) { + test.skip('queue-worker tracing needs @sap/cds >= 10.1 (cds.spawn fix) on sqlite', () => {}) + return + } + require('./tracing-messaging')(CASE, CHECK) +}) diff --git a/test/tracing-messaging-persistent-outbox.test.js b/test/tracing-messaging-persistent-outbox.test.js index 1d959595..e46e5a1f 100644 --- a/test/tracing-messaging-persistent-outbox.test.js +++ b/test/tracing-messaging-persistent-outbox.test.js @@ -1,20 +1,112 @@ const CASE = 'persistent-outbox' -// REVISIT: even with profile "persistent-outbox", messaging kind and file from package.json wins -process.env.cds_requires_messaging = JSON.stringify({ - kind: 'file-based-messaging', - file: `../${CASE}` -}) +const cds = require('@sap/cds') +const otel = require('@opentelemetry/api') + +// Messaging config (kind/file) comes from the `persistent-outbox` profile in +// test/bookshop/.cdsrc.json, composed with `tracing-in-memory` by the shared harness. + +// --- Span hierarchy for the persistent-outbox case --------------------------------------- +// +// With persistent outbox enabled, the queue worker runs two coherent transactions: +// - tx 1: read out of queue + set status='processing' (libx/queue/processing.js:189) +// - tx 2: handle the event + delete the row (libx/queue/processing.js:319) +// +// `@cap-js/telemetry` wraps `cds.tx(fn)` to emit a ` - tx` span per callback, so +// each of these transactions is captured as a root/child span. Both sqlite and HANA now +// produce the same unified shape: the worker uses `cds.spawn`, which the telemetry plugin +// wraps to emit a single `cds.spawn - run task` CONSUMER root that both worker tx spans +// nest under. +// +// With incoming HTTP instrumentation on, the producer trace is rooted at the incoming SERVER +// span for the emit-triggering POST (AdminService - tx nests under it). +// +// Expected shape (3 meaningful roots, same for sqlite and HANA): +// +// 1. POST (incoming SERVER span) (producer trace) +// └─ AdminService - tx +// └─ AdminService - handle test_emit +// └─ messaging - emit outgoing foo +// └─ db - UPSERT cds.outbox.Messages +// └─ cds.spawn - schedule task +// +// 2. cds.spawn - run task (queue worker root) +// ├─ db - tx (tx 1) +// │ ├─ db - READ cds.outbox.Messages +// │ └─ db - UPDATE cds.outbox.Messages +// └─ messaging - tx (tx 2) +// ├─ messaging - handle foo +// └─ db - DELETE cds.outbox.Messages +// +// 3. messaging - tx (file-based CONSUMER) +// └─ ...handler work (READ Books, READ Authors)... +// +// Plus the scheduling service may emit a bookkeeping `db - tx` (startup scan finding no +// tasks) — tolerated as a 4th root, not required. + +const CHECK = ({ expect, rootSpans, groupedByTrace }) => { + // Producer trace + const producer = groupedByTrace.find(g => g.all.some(s => s.name === 'AdminService - handle test_emit')) + expect(producer, 'expected a producer trace').to.exist + expect(producer.root.kind, 'producer trace rooted at the incoming SERVER span').to.equal(otel.SpanKind.SERVER) + expect(producer.all.some(s => s.name === 'AdminService - tx')).to.be.true + expect(producer.all.some(s => s.name === 'messaging - emit outgoing foo')).to.be.true + expect(producer.all.some(s => s.name === 'db - UPSERT cds.outbox.Messages')).to.be.true + expect(producer.all.some(s => s.name === 'cds.spawn - schedule task')).to.be.true + + // Queue worker trace: rooted at `cds.spawn - run task`, containing both tx spans as children. + const workerTrace = groupedByTrace.find(g => g.root.name === 'cds.spawn - run task') + expect(workerTrace, 'expected a queue-worker spawn-root trace').to.exist + + // tx 1: db - tx with READ + UPDATE of the outbox + const workerDbTx = workerTrace.all.find(s => s.name === 'db - tx') + expect(workerDbTx, 'expected a db - tx child in the worker trace (tx 1)').to.exist + expect(workerTrace.all.some(s => s.name === 'db - READ cds.outbox.Messages')).to.be.true + expect(workerTrace.all.some(s => s.name === 'db - UPDATE cds.outbox.Messages')).to.be.true + + // tx 2: messaging - tx with handle foo + DELETE of the outbox row + const workerMessagingTx = workerTrace.all.find(s => s.name === 'messaging - tx') + expect(workerMessagingTx, 'expected a messaging - tx child in the worker trace (tx 2)').to.exist + expect(workerTrace.all.some(s => s.name === 'messaging - handle foo')).to.be.true + expect(workerTrace.all.some(s => s.name === 'db - DELETE cds.outbox.Messages')).to.be.true + + // File-based CONSUMER trace (the file-messaging consumer, *not* the queue-worker path). + // Identified by containing the full `foo` handler work (SELECT Books + READ Authors). + const consumer = groupedByTrace.find( + g => + g !== producer && + g !== workerTrace && + g.root.name === 'messaging - tx' && + g.all.some(s => s.name.match(/READ sap\.capire\.bookshop\.Books/)) && + g.all.some(s => s.name === 'AdminService - READ AdminService.Authors') + ) + expect(consumer, 'expected a CONSUMER trace').to.exist + expect(consumer.all.some(s => s.name === 'messaging - emit outgoing foo')).to.be.true + expect(consumer.all.some(s => s.name === 'messaging - handle foo')).to.be.true + + // 3 meaningful roots; tolerate one extra for the scheduling-service bookkeeping scan + // (a `db - tx` root with just a READ, no UPDATE). + expect(rootSpans.length).to.be.gte(3) + expect(rootSpans.length).to.be.lte(4) -// REVISIT: check json exports -const CHECK = (log, expect) => { - // 3: outbox -> consumers get new root context - // REVISIT: for some reason, span "cds.spawn run task" has no parent when running in jest - expect(log.output.match(/\[telemetry\] - elapsed times:/g).length).to.equal(4) //> actually 3 - expect(log.output.match(/cds.spawn - schedule task/g).length).to.equal(1) + // Sanity: every non-root span has a parent inside the captured set. + const allSpans = groupedByTrace.flatMap(g => g.all) + for (const s of allSpans) { + const pid = s.parentSpanContext?.spanId + if (!pid) continue + const parent = allSpans.find(p => p.spanContext().spanId === pid) + expect(parent, `expected parent span for ${s.name}`).to.exist + } } -// REVISIT: re-enable with switch to vitest -describe.skip(`tracing messaging - ${CASE}`, () => { +describe(`tracing messaging - ${CASE}`, () => { + // Queue-worker tracing needs the cds.spawn fix (@sap/cds >= 10.1) on sqlite; HANA always runs. Tracked in #477 §1. + // Dormant on CI until the transitive @sap/cds reaches 10.1 (the cds-10 leg resolves 10.0.x today); cds-9 always skips. + // See TESTING.md → Sanctioned skips (and HANA signalling: why we branch on TELEMETRY_TEST_HANA, not cds.env). + const [maj, min] = cds.version.split('.').map(Number) + if (!process.env.TELEMETRY_TEST_HANA && (maj < 10 || (maj === 10 && min < 1))) { + test.skip('queue-worker tracing needs @sap/cds >= 10.1 (cds.spawn fix) on sqlite', () => {}) + return + } require('./tracing-messaging')(CASE, CHECK) }) diff --git a/test/tracing-messaging-without-outbox.test.js b/test/tracing-messaging-without-outbox.test.js index f93664c1..15b9b647 100644 --- a/test/tracing-messaging-without-outbox.test.js +++ b/test/tracing-messaging-without-outbox.test.js @@ -1,16 +1,50 @@ const CASE = 'without-outbox' -// REVISIT: even with profile "without-outbox", messaging kind and file from package.json wins -process.env.cds_requires_messaging = JSON.stringify({ - kind: 'file-based-messaging', - file: `../${CASE}`, - outboxed: false -}) +const otel = require('@opentelemetry/api') + +// Messaging config (kind/file/outboxed) comes from the `without-outbox` profile in +// test/bookshop/.cdsrc.json, composed with `tracing-in-memory` by the shared harness. + +// Without outbox, file-based messaging writes directly to the file from the producer's +// transaction (no queue worker). The file watcher delivers asynchronously as a new +// SpanKind.CONSUMER root. +// +// With incoming HTTP instrumentation on, the producer trace is now rooted at the incoming +// SERVER span (SpanKind.SERVER) for the emit-triggering POST; `AdminService - tx` nests under it. +// +// Expected roots: +// 1. POST (incoming SERVER span) (producer) +// └─ AdminService - tx +// └─ AdminService - handle test_emit +// └─ messaging - emit outgoing foo +// └─ messaging - handle foo (writes to file, in-process) +// +// 2. messaging - tx (file-based CONSUMER) +// └─ messaging - emit outgoing foo +// └─ messaging - handle foo +// └─ ...handler work... +// +// The scheduling service may also emit a bookkeeping scan trace (`db - tx → db - READ +// cds.outbox.Messages` finding nothing) — we allow it but don't require it. + +const CHECK = ({ expect, rootSpans, groupedByTrace }) => { + // Producer trace + const producer = groupedByTrace.find(g => g.all.some(s => s.name === 'AdminService - handle test_emit')) + expect(producer, 'expected a producer trace').to.exist + expect(producer.root.kind, 'producer trace rooted at the incoming SERVER span').to.equal(otel.SpanKind.SERVER) + expect(producer.all.some(s => s.name === 'AdminService - tx')).to.be.true + expect(producer.all.some(s => s.name.match(/messaging - emit outgoing/))).to.be.true + + // File-based CONSUMER trace + const consumer = groupedByTrace.find( + g => g !== producer && g.root.name === 'messaging - tx' && g.all.some(s => s.name === 'messaging - handle foo') + ) + expect(consumer, 'expected a CONSUMER trace').to.exist + expect(consumer.all.some(s => s.name.match(/READ sap\.capire\.bookshop\.Books/))).to.be.true -// REVISIT: check json exports -const CHECK = (log, expect) => { - // 2: no outbox -> consumer gets new root context - expect(log.output.match(/\[telemetry\] - elapsed times:/g).length).to.equal(2) + // 2 meaningful roots; allow up to 3 to tolerate the scheduling service's bookkeeping scan. + expect(rootSpans.length).to.be.gte(2) + expect(rootSpans.length).to.be.lte(3) } describe(`tracing messaging - ${CASE}`, () => { diff --git a/test/tracing-messaging.js b/test/tracing-messaging.js index 35508745..91a9fecc 100644 --- a/test/tracing-messaging.js +++ b/test/tracing-messaging.js @@ -1,7 +1,8 @@ module.exports = (CASE, CHECK) => { const cds = require('@sap/cds') - const { expect, POST } = cds.test(__dirname + '/bookshop', '--profile', CASE) - const log = cds.test.log() + const { expect, POST } = cds.test(__dirname + '/bookshop', '--profile', `${CASE},tracing-in-memory`) + const { reset, groupedByTrace, captured } = require('./bookshop/lib/MyInMemorySpanExporter') + const { asExternalClient, clearOutbox, eventually, meaningful } = require('./utils') const wait = require('node:timers/promises').setTimeout @@ -21,16 +22,34 @@ module.exports = (CASE, CHECK) => { }) afterAll(async () => { - await wait(100) + // HANA-only outbox settle so a draining worker can't bleed into the next file; no-op on sqlite. + // See TESTING.md → sqlite vs HANA (outbox bleed on the shared HANA container). + if (process.env.TELEMETRY_TEST_HANA) { + await clearOutbox() + await wait(5000) + await clearOutbox() + } rm() }) - beforeEach(log.clear) + beforeEach(async () => { + // Clear the shared outbox before resetting the span buffer; no-op on sqlite. + // See TESTING.md → sqlite vs HANA (outbox bleed on the shared HANA container). + await clearOutbox() + reset() + }) test('emit is traced', async () => { - await POST('/odata/v4/admin/test_emit', {}, admin) - await wait(1000) - // execute case specific check - CHECK(log, expect) + await asExternalClient(() => POST('/odata/v4/admin/test_emit', {}, admin)) + // Poll (flush + re-check) until both queue workers have run and exported their spans; + // on HANA the worker latency exceeds any reasonable fixed sleep. Pass the meaningful + // (non-outbox-scan) traces so the CHECK's exact root-count assertions aren't thrown off by + // the scheduler's bookkeeping scans on the shared HANA container. + await eventually(() => { + const groups = meaningful(groupedByTrace()) + const roots = groups.flatMap(g => g.roots) + // CHECK is called with span-level data: { expect, rootSpans, groupedByTrace, captured, cds } + CHECK({ expect, rootSpans: roots, groupedByTrace: groups, captured: [...captured], cds }) + }) }) } diff --git a/test/tracing-mt.test.js b/test/tracing-mt.test.js index f6f3c36a..4eb412c1 100644 --- a/test/tracing-mt.test.js +++ b/test/tracing-mt.test.js @@ -1,8 +1,11 @@ const cds = require('@sap/cds') // prettier-ignore -const { expect, GET } = cds.test('serve', '--in-memory', '--project', __dirname + '/bookshop', '--profile', 'multitenancy') -const log = cds.test.log() +const { expect, GET } = cds.test('serve', '--in-memory', '--project', __dirname + '/bookshop', '--profile', 'multitenancy,tracing-in-memory') +const { reset, captured } = require('./bookshop/lib/MyInMemorySpanExporter') + +// Multitenancy runs on sqlite only; excluded from the HANA job (needs a bound Service Manager). +// See TESTING.md → Sanctioned skips. describe('tracing with multitenancy', () => { const TENANT1 = 'tenant_1' const TENANT2 = 'tenant_2' @@ -17,22 +20,23 @@ describe('tracing with multitenancy', () => { await mts.subscribe(TENANT2) }) - beforeEach(log.clear) + beforeEach(reset) test('GET with user1 is traced', async () => { const { status } = await GET('/odata/v4/admin/Books', user1) expect(status).to.equal(200) - // primitive check that console has trace logs - expect(log.output).to.match(/\[telemetry|tenant_1\] - elapsed times:/) - expect(log.output).to.match(/\s+\d+\.\d+ → \s*\d+\.\d+ = \s*\d+\.\d+ ms \s* AdminService - READ AdminService.Books/) + // AdminService READ ran exactly once and was tagged with the right tenant. + const spans = captured.filter(s => s.name === 'AdminService - READ AdminService.Books') + expect(spans.length, 'expected exactly one AdminService READ span').to.equal(1) + expect(spans[0].attributes['sap.tenancy.tenant_id']).to.equal(TENANT1) }) test('GET with user2 is traced', async () => { const { status } = await GET('/odata/v4/admin/Books', user2) expect(status).to.equal(200) - // primitive check that console has trace logs - expect(log.output).to.match(/\[telemetry|tenant_2\] - elapsed times:/) - expect(log.output).to.match(/\s+\d+\.\d+ → \s*\d+\.\d+ = \s*\d+\.\d+ ms \s* AdminService - READ AdminService.Books/) + const spans = captured.filter(s => s.name === 'AdminService - READ AdminService.Books') + expect(spans.length, 'expected exactly one AdminService READ span').to.equal(1) + expect(spans[0].attributes['sap.tenancy.tenant_id']).to.equal(TENANT2) }) // --- TODO --- diff --git a/test/tracing-one-agent.test.js b/test/tracing-one-agent.test.js new file mode 100644 index 00000000..a84bd08f --- /dev/null +++ b/test/tracing-one-agent.test.js @@ -0,0 +1,90 @@ +// When Dynatrace OneAgent is active (`via_one_agent`), OneAgent captures OpenTelemetry spans +// in-process — so instead of exporting traces ourselves (which would duplicate them), the tracing +// factory registers a real, recording tracer provider with an EMPTY span-processor list. That +// provider records spans (so the global tracer used by lib/tracing/trace.js is no longer a no-op) +// but adds no export path of our own; OneAgent's preload hooks observe the recorded spans. +// +// Two properties must hold and are asserted here without a live Dynatrace tenant: +// 1. crash-safety — an EMPTY processor list `[]` must not crash on the first span, unlike the +// `[undefined]` a naive "no exporter -> no processor" path would have produced (which crashed +// in MultiSpanProcessor.onStart); +// 2. the provider is real and RECORDING — a provider that doesn't record would leave CDS spans +// non-existent, which is the regression this path must avoid. +// +// We drive the factory directly rather than through a full boot: exercising `via_one_agent` needs +// kind `*-to-dynatrace` (whose exporters would otherwise demand real Dynatrace credentials at boot). +// Booting once with the in-memory tracing profile populates `cds.env` and caches lib/tracing; the +// tests then flip `cds.env.requires.telemetry.kind` + the env flag and call the factory in isolation. +const cds = require('@sap/cds') +const { expect } = cds.test(__dirname + '/bookshop', '--profile', 'tracing-in-memory') + +const { resourceFromAttributes } = require('@opentelemetry/resources') +const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node') + +const { hasDependency } = require('../lib/utils/common') +const setupTracing = require('../lib/tracing') + +describe('tracing setup with Dynatrace OneAgent', () => { + const OTLP_PROTO = '@opentelemetry/exporter-trace-otlp-proto' + + // Guard on the illustrative failure mode: a provider whose only span processor is `undefined` + // builds fine but crashes on the first span in onStart. It is the reason the OneAgent path passes + // an empty list `[]` rather than `[processor]` with a missing processor. + test('a provider with an undefined span processor crashes on the first span', () => { + const provider = new NodeTracerProvider({ + resource: resourceFromAttributes({}), + spanProcessors: [undefined] + }) + expect(() => provider.getTracer('probe').startSpan('boom')).toThrow(/onStart/) + }) + + // Precondition for `via_one_agent`: the otlp-proto exporter must NOT be a (production) dependency + // — it is only a devDependency here, and hasDependency() checks `dependencies` only. With it + // present, `via_one_agent` would be false and the normal OTLP export path taken instead. + test('otlp-proto exporter is not a production dependency (so via_one_agent can be true)', () => { + expect(hasDependency(OTLP_PROTO)).toBe(false) + }) + + describe('OneAgent path', () => { + let savedKind, savedEnv + + beforeEach(() => { + savedKind = cds.env.requires.telemetry.kind + savedEnv = process.env.DT_NODE_PRELOAD_OPTIONS + process.env.DT_NODE_PRELOAD_OPTIONS = '{}' + cds.env.requires.telemetry.kind = 'telemetry-to-dynatrace' + }) + + afterEach(() => { + cds.env.requires.telemetry.kind = savedKind + if (savedEnv === undefined) delete process.env.DT_NODE_PRELOAD_OPTIONS + else process.env.DT_NODE_PRELOAD_OPTIONS = savedEnv + }) + + test('standalone: registers a real, recording provider with no export path, and does not crash', () => { + // A truthy resource is the standalone path (a falsy resource is the CALM path). + let provider + expect(() => { + provider = setupTracing(resourceFromAttributes({})) + }).not.toThrow() + + // A real provider is returned (not undefined as on the pre-fix regression) ... + expect(provider).toBeInstanceOf(NodeTracerProvider) + + // ... and it records: a span created the way lib/tracing/trace.js creates them is a real, + // recording span — not a NonRecordingSpan — so CDS spans actually come into existence for + // OneAgent to capture. The empty processor list means creating and ending it never crashes. + const span = provider.getTracer('@cap-js/telemetry').startSpan('cds-span') + expect(span.constructor.name).not.toBe('NonRecordingSpan') + expect(span.isRecording()).toBe(true) + expect(() => span.end()).not.toThrow() + }) + + test('CALM + OneAgent: rejected as an unsupported combination', () => { + // A falsy resource is the CALM path (@sap/xotel-agent-ext-js owns the provider). Combined with + // OneAgent, two agents would own tracing at once — a contradictory, unverified setup — so the + // factory rejects it rather than silently doing nothing. + expect(() => setupTracing(undefined)).toThrow(/OneAgent with @sap\/xotel-agent-ext-js .* not supported/) + }) + }) +}) diff --git a/test/tracing-outboxed-batch.test.js b/test/tracing-outboxed-batch.test.js new file mode 100644 index 00000000..d47ba678 --- /dev/null +++ b/test/tracing-outboxed-batch.test.js @@ -0,0 +1,85 @@ +// Tests that when the queue worker picks up multiple ready tasks in one iteration +// (chunkSize > 1), each is dispatched in its own tx span under the SAME worker root. +// This validates the parallel-fan-out shape described in the design notes. + +const cds = require('@sap/cds') +const { expect, POST } = cds.test(__dirname + '/bookshop', '--with-mocks', '--profile', 'tracing-in-memory') +const { reset, captured, groupedByTrace } = require('./bookshop/lib/MyInMemorySpanExporter') +const { hrTimeToNanoseconds } = require('@opentelemetry/core') +const { eventually } = require('./utils') + +describe('tracing for outboxed batch (chunk-size fan-out)', () => { + // Queue-worker tracing needs the cds.spawn fix (@sap/cds >= 10.1) on sqlite; HANA always runs. Tracked in #477 §1. + // Dormant on CI until the transitive @sap/cds reaches 10.1 (the cds-10 leg resolves 10.0.x today); cds-9 always skips. + // See TESTING.md → Sanctioned skips (and HANA signalling: why we branch on TELEMETRY_TEST_HANA, not cds.env). + const [maj, min] = cds.version.split('.').map(Number) + if (!process.env.TELEMETRY_TEST_HANA && (maj < 10 || (maj === 10 && min < 1))) { + test.skip('queue-worker tracing needs @sap/cds >= 10.1 (cds.spawn fix) on sqlite', () => {}) + return + } + + beforeAll(async () => { + const externalOne = await cds.connect.to('ExternalServiceOne') + externalOne.on('call', () => 'ok') + }) + + beforeEach(async () => { + // Clear the shared outbox before resetting the span buffer; no-op on sqlite. + // See TESTING.md → sqlite vs HANA (outbox bleed on the shared HANA container). + await DELETE.from('cds.outbox.Messages') + reset() + }) + + test('three queued sends produce parallel dispatch spans under one worker root', async () => { + await POST('/odata/v4/admin/test_outboxed_send_batch', {}, { auth: { username: 'alice' } }) + + await eventually(() => { + // Producer wrote three rows to the outbox. + const upserts = captured.filter(s => s.name === 'db - UPSERT cds.outbox.Messages') + expect(upserts.length, 'expected three producer outbox UPSERTs').to.be.gte(3) + + // Look for a queue worker root containing multiple dispatch tx spans. + const workerTrace = groupedByTrace().find( + g => + g.root.name === 'cds.spawn - run task' && g.all.filter(s => s.name === 'ExternalServiceOne - tx').length >= 2 + ) + expect(workerTrace, 'expected a worker trace with multiple ExternalServiceOne - tx children').to.exist + + // The worker root must have exactly one lock tx (db - tx with READ + UPDATE)… + const lockTxs = workerTrace.all.filter( + s => + s.name === 'db - tx' && + workerTrace.all.some( + c => c.parentSpanContext?.spanId === s.spanContext().spanId && c.name === 'db - READ cds.outbox.Messages' + ) + ) + expect(lockTxs, 'expected one lock tx (db - tx with READ + UPDATE)').to.have.lengthOf(1) + + // …and multiple dispatch txs, each containing an ExternalServiceOne handle span + DELETE. + const dispatchTxs = workerTrace.all.filter(s => s.name === 'ExternalServiceOne - tx') + expect(dispatchTxs.length, 'expected multiple dispatch txs (chunk-size fan-out)').to.be.gte(2) + for (const tx of dispatchTxs) { + const kids = workerTrace.all.filter(k => k.parentSpanContext?.spanId === tx.spanContext().spanId) + expect( + kids.some(k => k.name.match(/ExternalServiceOne - handle/)), + 'dispatch tx should contain handle call' + ).to.be.true + expect( + kids.some(k => k.name === 'db - DELETE cds.outbox.Messages'), + 'dispatch tx should contain DELETE' + ).to.be.true + } + + // The dispatch txs should overlap in time (parallel), not be strictly sequential. + if (dispatchTxs.length >= 2) { + const sorted = [...dispatchTxs].sort( + (a, b) => hrTimeToNanoseconds(a.startTime) - hrTimeToNanoseconds(b.startTime) + ) + const firstEndNs = hrTimeToNanoseconds(sorted[0].endTime) + const secondStartNs = hrTimeToNanoseconds(sorted[1].startTime) + // Parallel: second starts before first ends (allow a tiny slack). + expect(secondStartNs, 'expected parallel dispatch: task2 starts before task1 ends').to.be.lessThan(firstEndNs) + } + }) + }) +}) diff --git a/test/tracing-processor-batch.test.js b/test/tracing-processor-batch.test.js new file mode 100644 index 00000000..7fe4d779 --- /dev/null +++ b/test/tracing-processor-batch.test.js @@ -0,0 +1,31 @@ +// Tests that an explicit processor override activates BatchSpanProcessor. +// +// The `[tracing-batch-processor]` profile in test/bookshop/.cdsrc.json explicitly +// sets `telemetry.tracing.processor.kind = "BatchSpanProcessor"`, overriding the +// `[development]` profile default of SimpleSpanProcessor. This proves the explicit- +// override path: a single config knob flips the active processor in a dev-profile run. +// +// NOTE: a "true production profile" boot (NODE_ENV=production or --profile production) +// is not feasible in the test bookshop — the production profile requires @cap-js/hana +// which is not installed. The production default (BatchSpanProcessor, defined in +// package.json's base tracing config) is therefore covered via this explicit override +// rather than a genuine production boot. +const cds = require('@sap/cds') +const { expect } = cds.test(__dirname + '/bookshop', '--profile', 'tracing-in-memory, tracing-batch-processor') +const otel = require('@opentelemetry/api') + +// Same accessor as in tracing-processor-simple.test.js — see that file for rationale. +function activeProcessorName() { + const provider = otel.trace.getTracerProvider() + const delegate = provider.getDelegate?.() ?? provider + return delegate._activeSpanProcessor?._spanProcessors?.[0]?.constructor?.name +} + +describe('span processor selection — explicit BatchSpanProcessor override', () => { + test('tracing-batch-processor profile activates BatchSpanProcessor', () => { + // The [tracing-batch-processor] profile overrides the [development] default + // (SimpleSpanProcessor) to BatchSpanProcessor — the same processor production uses, + // proven here via explicit override in a dev-profile run. + expect(activeProcessorName()).toBe('BatchSpanProcessor') + }) +}) diff --git a/test/tracing-processor-simple.test.js b/test/tracing-processor-simple.test.js new file mode 100644 index 00000000..1b523828 --- /dev/null +++ b/test/tracing-processor-simple.test.js @@ -0,0 +1,34 @@ +// Tests that the development profile selects SimpleSpanProcessor. +// +// Per package.json `cds.requires.kinds.telemetry.tracing`, the package-level default +// (production) processor is BatchSpanProcessor; the `[development]` profile in +// package.json overrides it to SimpleSpanProcessor. In the test environment +// (NODE_ENV != production) the `[development]` profile applies automatically, so a +// plain boot should yield SimpleSpanProcessor without any additional override. +const cds = require('@sap/cds') +const { expect } = cds.test(__dirname + '/bookshop', '--profile', 'tracing-in-memory') +const otel = require('@opentelemetry/api') + +// Reach the active span processor installed by the tracing factory. +// +// The global OTel provider is a ProxyTracerProvider (returned by trace.getTracerProvider()); +// getDelegate() unwraps it to the real NodeTracerProvider built by lib/tracing/index.js. +// NodeTracerProvider wraps all registered processors in a private MultiSpanProcessor +// accessible as _activeSpanProcessor. We drill one level deeper to _spanProcessors[0] +// (the single processor the factory installed) to read its constructor name. +// _spanProcessors is a private `_`-prefixed field, but it is the only stable path to the +// live processor instance; `flushSpans()` in test/utils.js follows the same +// getDelegate() + forceFlush() pattern for the same reason. +function activeProcessorName() { + const provider = otel.trace.getTracerProvider() + const delegate = provider.getDelegate?.() ?? provider + return delegate._activeSpanProcessor?._spanProcessors?.[0]?.constructor?.name +} + +describe('span processor selection — development profile', () => { + test('development profile activates SimpleSpanProcessor', () => { + // [development] is auto-applied in non-production (NODE_ENV=test) environments. + // The plugin should have wired a SimpleSpanProcessor (not the batch default). + expect(activeProcessorName()).toBe('SimpleSpanProcessor') + }) +}) diff --git a/test/tracing-processor-unknown.test.js b/test/tracing-processor-unknown.test.js new file mode 100644 index 00000000..5033a712 --- /dev/null +++ b/test/tracing-processor-unknown.test.js @@ -0,0 +1,25 @@ +// Tests that an unknown processor kind causes the tracing factory to throw. +// +// Boot the bookshop server so cds.env is fully configured with the tracing-in-memory +// profile, then call the real tracing factory (lib/tracing/index.js) with a bogus +// processor kind. The factory throws before creating the tracerProvider or calling +// .register(), so passing {} as the resource arg is safe and causes no double-register. +// require('./cds')() and require('./cloud_sdk')() run first, but both use the wrap() +// utility which guards against double-wrapping via the __wrapped flag — idempotent. +const cds = require('@sap/cds') +const { expect } = cds.test(__dirname + '/bookshop', '--profile', 'tracing-in-memory') + +const tracing = require('../lib/tracing') + +describe('span processor selection — unknown kind', () => { + test('unknown processor kind throws with a descriptive error', () => { + const cfg = cds.env.requires.telemetry.tracing + const orig = cfg.processor + try { + cfg.processor = { kind: 'NotARealProcessor' } + expect(() => tracing({})).toThrow(/Unknown span processor NotARealProcessor/) + } finally { + cfg.processor = orig + } + }) +}) diff --git a/test/tracing-remote-cloudsdk.test.js b/test/tracing-remote-cloudsdk.test.js new file mode 100644 index 00000000..16b5b79e --- /dev/null +++ b/test/tracing-remote-cloudsdk.test.js @@ -0,0 +1,65 @@ +const cds = require('@sap/cds') +const { expect } = cds.test(__dirname + '/bookshop', '--profile', 'tracing-in-memory') +const http = require('http') + +// The tracing-in-memory profile (see test/bookshop/.cdsrc.json) configures +// MyInMemorySpanExporter as the trace exporter. We read the captured ReadableSpan +// objects directly out of its shared buffer — no console spy. +const { captured, reset } = require('./bookshop/lib/MyInMemorySpanExporter') + +// Cloud SDK path: with @sap-cloud-sdk/http-client installed (as in the bookshop) and +// cds.env.remote.native_fetch NOT set, CAP routes outbound remote calls through +// getCloudSdk().executeHttpRequestWithOrigin(...). lib/tracing/cloud_sdk.js wraps that +// export so the outbound call produces a @cap-js/telemetry CLIENT span carrying +// the sap.btp.destination attribute. +describe('tracing remote via cloud sdk', () => { + beforeEach(reset) + + const getSpans = () => captured + const getCapSpans = () => getSpans().filter(s => s.instrumentationScope?.name === '@cap-js/telemetry') + + let server, port + + beforeAll( + () => + new Promise(resolve => { + server = http.createServer((req, res) => { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ value: [] })) + }) + server.listen(0, () => { + port = server.address().port + resolve() + }) + }) + ) + + afterAll(() => new Promise(resolve => server.close(resolve))) + + test('outbound call is traced by the cloud_sdk wrapper with sap.btp.destination', async () => { + // a named destination object -> destination.name flows into the CLIENT span attribute + cds.env.requires.TestRemote = { + kind: 'odata', + credentials: { destination: { name: 'my-destination', url: `http://localhost:${port}` } } + } + const remote = await cds.connect.to('TestRemote') + + // no mock handler - let it make the actual HTTP call via the cloud sdk + await remote.send({ method: 'GET', path: '/test' }) + + // the cloud sdk path must not go through native fetch / undici + expect(cds.env.remote?.native_fetch).not.to.equal(true) + + // the outbound span comes from our tracer (not from undici) ... + const clientSpan = getCapSpans().find(s => s.attributes?.['code.function.name'] === 'executeHttpRequestWithOrigin') + expect(clientSpan, 'cloud_sdk wrapper did not produce a CLIENT span').to.exist + // ... is a CLIENT span (kind 2) ... + expect(clientSpan.kind).to.equal(2) + // ... and carries the destination name + expect(clientSpan.attributes['sap.btp.destination']).to.equal('my-destination') + + // no undici span for this call (cloud sdk path is used, not native fetch) + const undiciSpans = getSpans().filter(s => s.instrumentationScope?.name === '@opentelemetry/instrumentation-undici') + expect(undiciSpans.length).to.equal(0) + }) +}) diff --git a/test/tracing-remote-native.test.js b/test/tracing-remote-native.test.js new file mode 100644 index 00000000..9d6a4954 --- /dev/null +++ b/test/tracing-remote-native.test.js @@ -0,0 +1,58 @@ +// Force CAP to use native fetch for outbound remote calls (instead of the cloud sdk) via the +// `native-fetch` profile (cds.env.remote.native_fetch = true) in test/bookshop/.cdsrc.json, +// composed with `tracing-in-memory`. +const cds = require('@sap/cds') +const { expect } = cds.test(__dirname + '/bookshop', '--profile', 'tracing-in-memory, native-fetch') +const http = require('http') + +// The tracing-in-memory profile (see test/bookshop/.cdsrc.json) configures +// MyInMemorySpanExporter as the trace exporter. We read the captured ReadableSpan +// objects directly out of its shared buffer — no console spy. +const { captured, reset } = require('./bookshop/lib/MyInMemorySpanExporter') + +// Native fetch path: when cds.env.remote.native_fetch === true (or no cloud sdk is +// installed), CAP routes outbound remote calls through native fetch, which is +// instrumented by @opentelemetry/instrumentation-undici. The outbound span therefore +// comes from that instrumentation scope (NOT @opentelemetry/instrumentation-http, and +// NOT our cloud_sdk wrapper) and carries the standard http.* / url.* / server.* attributes. +describe('tracing remote via native fetch', () => { + beforeEach(reset) + + const getSpans = () => captured + + let server, port + + beforeAll( + () => + new Promise(resolve => { + server = http.createServer((req, res) => { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ value: [] })) + }) + server.listen(0, () => { + port = server.address().port + resolve() + }) + }) + ) + + afterAll(() => new Promise(resolve => server.close(resolve))) + + test('outbound call is traced by @opentelemetry/instrumentation-undici', async () => { + expect(cds.env.remote?.native_fetch).to.equal(true) + + cds.env.requires.TestRemote = { kind: 'odata', credentials: { url: `http://localhost:${port}` } } + const remote = await cds.connect.to('TestRemote') + + // no mock handler - let it make the actual HTTP call via native fetch + await remote.send({ method: 'GET', path: '/test' }) + + const undiciSpan = getSpans().find(s => s.instrumentationScope?.name === '@opentelemetry/instrumentation-undici') + expect(undiciSpan, 'no span from @opentelemetry/instrumentation-undici').to.exist + expect(undiciSpan.attributes['http.request.method']).to.equal('GET') + expect(undiciSpan.attributes['http.response.status_code']).to.equal(200) + expect(undiciSpan.attributes['url.full']).to.equal(`http://localhost:${port}/test`) + expect(undiciSpan.attributes['server.address']).to.equal('localhost') + expect(undiciSpan.attributes['server.port']).to.equal(port) + }) +}) diff --git a/test/tracing-scheduled.test.js b/test/tracing-scheduled.test.js new file mode 100644 index 00000000..14772b69 --- /dev/null +++ b/test/tracing-scheduled.test.js @@ -0,0 +1,80 @@ +// Tests tracing of scheduled tasks. +// +// `cds.queued(svc).schedule('event', ...).after(N)` writes a task row to the persistent +// outbox with a timestamp N ms in the future. The queue scheduler picks it up at that +// time and dispatches to the target service's handler. +// +// Expected meaningful roots (unified across sqlite and HANA): +// +// 1. POST (incoming SERVER span) (producer trace) +// └─ AdminService - tx +// └─ AdminService - handle test_scheduled +// └─ db - UPSERT cds.outbox.Messages +// └─ cds.spawn - schedule task +// +// 2. cds.spawn - run task (queue worker root) +// ├─ db - tx (tx 1: lock) +// └─ ExternalServiceOne - tx (tx 2: dispatch) +// +// Plus optionally one bookkeeping startup-scan trace (tolerated, not required). +// Total meaningful roots: between 2 and 3. + +const cds = require('@sap/cds') +const { expect, POST } = cds.test(__dirname + '/bookshop', '--with-mocks', '--profile', 'tracing-in-memory') +const { reset, captured, groupedByTrace, rootSpans } = require('./bookshop/lib/MyInMemorySpanExporter') +const otel = require('@opentelemetry/api') +const { asExternalClient, eventually } = require('./utils') + +describe('tracing for scheduled tasks', () => { + // Queue-worker tracing needs the cds.spawn fix (@sap/cds >= 10.1) on sqlite; HANA always runs. Tracked in #477 §1. + // Dormant on CI until the transitive @sap/cds reaches 10.1 (the cds-10 leg resolves 10.0.x today); cds-9 always skips. + // See TESTING.md → Sanctioned skips (and HANA signalling: why we branch on TELEMETRY_TEST_HANA, not cds.env). + const [maj, min] = cds.version.split('.').map(Number) + if (!process.env.TELEMETRY_TEST_HANA && (maj < 10 || (maj === 10 && min < 1))) { + test.skip('queue-worker tracing needs @sap/cds >= 10.1 (cds.spawn fix) on sqlite', () => {}) + return + } + + beforeAll(async () => { + const externalOne = await cds.connect.to('ExternalServiceOne') + externalOne.on('call', () => 'ok') + }) + + beforeEach(async () => { + // Clear the shared outbox before resetting the span buffer; no-op on sqlite. + // See TESTING.md → sqlite vs HANA (outbox bleed on the shared HANA container). + await DELETE.from('cds.outbox.Messages') + reset() + }) + + test('schedule .after() is fully traced through the queue worker', async () => { + await asExternalClient(() => POST('/odata/v4/admin/test_scheduled', {}, { auth: { username: 'alice' } })) + + // Poll (flush + re-check) until the scheduled task has fired and all spans have been + // exported; on HANA the worker latency exceeds any reasonable fixed sleep. + await eventually(() => { + // Producer trace: writes the task row inside the HTTP request tx. + const producer = groupedByTrace().find(g => g.all.some(s => s.name === 'AdminService - handle test_scheduled')) + expect(producer, 'expected a producer trace').to.exist + // With incoming HTTP instrumentation on, the producer trace roots at the incoming SERVER + // span for the POST; `AdminService - tx` now nests under it. + expect(producer.root.kind, 'producer trace rooted at the incoming SERVER span').to.equal(otel.SpanKind.SERVER) + expect(producer.all.some(s => s.name === 'AdminService - tx')).to.be.true + expect(producer.all.some(s => s.name === 'db - UPSERT cds.outbox.Messages')).to.be.true + expect(producer.all.some(s => s.name === 'cds.spawn - schedule task')).to.be.true + + // Queue worker trace: rooted at cds.spawn - run task, contains both tx spans. + const workerTrace = groupedByTrace().find(g => g.root.name === 'cds.spawn - run task') + expect(workerTrace, 'expected a queue-worker spawn-root trace').to.exist + expect(workerTrace.all.some(s => s.name === 'db - tx')).to.be.true + expect(workerTrace.all.some(s => s.name === 'ExternalServiceOne - tx')).to.be.true + + // The ExternalServiceOne handler was invoked. + expect(captured.some(s => s.name.match(/ExternalServiceOne - handle/))).to.be.true + + // Total meaningful roots: producer + worker (+ optional bookkeeping scan). + expect(rootSpans().length).to.be.gte(2) + expect(rootSpans().length).to.be.lte(3) + }) + }) +}) diff --git a/test/tracing-span-names.test.js b/test/tracing-span-names.test.js index 027579f3..73a30903 100644 --- a/test/tracing-span-names.test.js +++ b/test/tracing-span-names.test.js @@ -1,14 +1,21 @@ const cds = require('@sap/cds') -const { expect, data } = cds.test(__dirname + '/bookshop', '--profile', 'tracing-attributes') +const { expect, data } = cds.test(__dirname + '/bookshop', '--profile', 'tracing-in-memory') const http = require('http') -describe('span names', () => { - beforeEach(data.reset) +// The tracing-in-memory profile (see test/bookshop/.cdsrc.json) configures +// MyInMemorySpanExporter as the trace exporter. We read the captured ReadableSpan +// objects directly out of its shared buffer — no console spy. +const { captured, reset } = require('./bookshop/lib/MyInMemorySpanExporter') - const log = jest.spyOn(console, 'dir') - beforeEach(log.mockClear) +describe('span names', () => { + beforeEach(async () => { + // data.reset is itself heavily traced (it runs DELETEs + INSERTs for the seed data) — + // run it first, THEN clear the buffer so the test only sees its own spans. + await data.reset() + reset() + }) - const getSpans = () => log.mock.calls.map(c => c[0]).filter(Boolean) + const getSpans = () => captured // Spans from our tracer only (excludes HTTP instrumentation spans) const getCapSpans = () => getSpans().filter(s => s.instrumentationScope?.name === '@cap-js/telemetry') @@ -63,16 +70,19 @@ describe('span names', () => { describe('cloud sdk', () => { let server, port - beforeAll(done => { - server = http.createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end(JSON.stringify({ value: [] })) - }) - server.listen(0, () => { - port = server.address().port - done() - }) - }) + beforeAll( + () => + new Promise(resolve => { + server = http.createServer((req, res) => { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ value: [] })) + }) + server.listen(0, () => { + port = server.address().port + resolve() + }) + }) + ) afterAll(() => new Promise(resolve => server.close(resolve))) diff --git a/test/tracing.test.js b/test/tracing.test.js index f3d0ce05..3f1122a0 100644 --- a/test/tracing.test.js +++ b/test/tracing.test.js @@ -1,121 +1,166 @@ -// REVISIT: jest breaks otel's patching of incoming request handling -> we can't ignore via ignoreIncomingRequestHook -process.env.cds_requires_telemetry_tracing_sampler = JSON.stringify({ - ignoreIncomingPaths: ['/odata/v4/admin/Authors'] -}) - +// The sampler's ignoreIncomingPaths (/odata/v4/admin/Authors) is configured via the +// `sampler-ignore-authors` profile in test/bookshop/.cdsrc.json, composed with `tracing-in-memory`. const cds = require('@sap/cds') -const { expect, GET, POST } = cds.test(__dirname + '/bookshop') -const log = cds.test.log() +const { expect, GET, POST } = cds.test( + __dirname + '/bookshop', + '--profile', + 'tracing-in-memory, sampler-ignore-authors' +) + +// Assert against the structured ReadableSpan objects captured by MyInMemorySpanExporter +// (configured via the tracing-in-memory profile in test/bookshop/.cdsrc.json) — no +// console spying, no string-regex matching of formatted output. +const { reset, rootSpans, groupedByTrace, captured } = require('./bookshop/lib/MyInMemorySpanExporter') +const otel = require('@opentelemetry/api') +const { asExternalClient, eventually, meaningful } = require('./utils') -const wait = require('node:timers/promises').setTimeout +const meaningfulRoots = () => meaningful(groupedByTrace()).flatMap(g => g.roots) describe('tracing', () => { const admin = { auth: { username: 'alice' } } - beforeEach(log.clear) + beforeEach(reset) test('GET is traced', async () => { const { status } = await GET('/odata/v4/admin/Books', admin) expect(status).to.equal(200) - // primitive check that console has trace logs - expect(log.output).to.match(/\[telemetry\] - elapsed times:/) - expect(log.output).to.match(/\s+\d+\.\d+ → \s*\d+\.\d+ = \s*\d+\.\d+ ms \s* AdminService - READ AdminService.Books/) + // The AdminService READ for Books was traced + expect(captured.some(s => s.name === 'AdminService - READ AdminService.Books')).to.be.true + // ...and at least one trace was rooted (i.e. our exporter would emit "elapsed times:") + expect(rootSpans().length).to.be.gte(1) }) - // REVISIT: jest breaks otel's patching of incoming request handling -> no span for 'GET' -> behavior to test not reproducible - xtest('GET with traceparent is traced', async () => { - const config = { ...admin, headers: { traceparent: '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01' } } - const { status } = await GET('/odata/v4/admin/Books', config) + // With incoming HTTP instrumentation on, the SERVER span adopts the W3C trace context from the + // request's `traceparent` header: the whole request trace continues the given trace id and the + // SERVER span is a child of the given (external) span id. + test('GET with traceparent is traced', async () => { + const traceId = '0af7651916cd43dd8448eb211c80319c' + const parentSpanId = 'b7ad6b7169203331' + const config = { ...admin, headers: { traceparent: `00-${traceId}-${parentSpanId}-01` } } + const { status } = await asExternalClient(() => GET('/odata/v4/admin/Books', config)) expect(status).to.equal(200) - // primitive check that console has trace logs - expect(log.output).to.match(/\[telemetry\] - elapsed times:/) - expect(log.output).to.match(/\s+\d+\.\d+ → \s*\d+\.\d+ = \s*\d+\.\d+ ms \s* AdminService - READ AdminService.Books/) + expect(captured.some(s => s.name === 'AdminService - READ AdminService.Books')).to.be.true + // The incoming SERVER span continued the propagated trace and parented off the external span id. + await eventually(() => { + const server = captured.find(s => s.kind === otel.SpanKind.SERVER && s.spanContext().traceId === traceId) + expect(server, 'incoming SERVER span adopting the propagated trace').to.exist + expect(server.parentSpanContext?.spanId).to.equal(parentSpanId) + }) }) test('custom GET is traced', async () => { const { status } = await GET('/custom/Books', admin) expect(status).to.equal(200) - // primitive check that console has trace logs - expect(log.output).to.match(/\[telemetry\] - elapsed times:/) - expect(log.output).to.match(/\s+\d+\.\d+ → \s*\d+\.\d+ = \s*\d+\.\d+ ms \s* db - READ sap.capire.bookshop.Books/) + expect(captured.some(s => s.name === 'db - READ sap.capire.bookshop.Books')).to.be.true }) test('NonRecordingSpans are handled correctly', async () => { + // Idempotent cleanup: this file has no data.reset, and on the persistent HANA container a + // leftover Author 42 from a prior run would make the POST fail with a unique-constraint 500. + await DELETE.from('sap.capire.bookshop.Authors').where({ ID: 42 }) + reset() const { status: postStatus } = await POST('/odata/v4/admin/Authors', { ID: 42, name: 'Douglas Adams' }, admin) expect(postStatus).to.equal(201) const { status: getStatus } = await GET('/odata/v4/admin/Authors?$select=ID', admin) expect(getStatus).to.equal(200) - // primitive check that console has no trace logs - expect(log.output).not.to.match(/telemetry/) + // The sampler in this test ignores /odata/v4/admin/Authors — no spans should be captured for it. + // (Other unrelated background work may still produce spans; assert only that none mention Authors.) + await eventually(() => { + expect(captured.filter(s => s.attributes['url.path']?.includes('/admin/Authors'))).to.have.lengthOf(0) + }) }) - // REVISIT: jest breaks otel's patching of incoming request handling -> behavior to test not reproducible - xtest('instrumentation hooks', async () => { - await GET('/odata/v4/admin/Books(251)', admin) - // primitive check that console has trace logs - expect(log.output).to.match(/\[telemetry\] - elapsed times:/) - log.clear() - await GET('/odata/v4/admin/Books(252)', admin) - // primitive check that console has no trace logs - expect(log.output).not.to.match(/telemetry/) + // Incoming HTTP instrumentation produces a SERVER span (SpanKind.SERVER === 1) per request, + // carrying the mount-relative `url.path`. Two independent mechanisms suppress that span: + // - the sampler's `ignoreIncomingPaths` (set at the top of this file for /odata/v4/admin/Authors) + // - the `ignoreIncomingRequestHook` (MyIgnoreIncomingRequestHook: /odata/v4/admin/Authors + /Books(252)) + // A non-ignored path still produces a SERVER span; the ignored paths must produce none. + test('instrumentation hooks', async () => { + const serverSpansFor = path => + captured.filter(s => s.kind === otel.SpanKind.SERVER && s.attributes['url.path'] === path) + + // Baseline: a non-ignored path DOES yield an incoming SERVER span. + await asExternalClient(() => GET('/odata/v4/admin/Books', admin)) + await eventually(() => expect(serverSpansFor('/Books')).to.have.lengthOf(1)) + + // Sampler path: /odata/v4/admin/Authors is in ignoreIncomingPaths -> no SERVER span. + reset() + await asExternalClient(() => GET('/odata/v4/admin/Authors?$select=ID', admin)) + await eventually(() => { + expect(captured.some(s => s.kind === otel.SpanKind.SERVER && s.attributes['url.path']?.includes('/Authors'))).to + .be.false + }) + + // ignoreIncomingRequestHook path: /Books(252) is ignored by the hook (not the sampler) -> no SERVER span. + reset() + await asExternalClient(() => GET('/odata/v4/admin/Books(252)', admin)) + await eventually(() => { + expect(serverSpansFor('/Books(252)')).to.have.lengthOf(0) + }) }) test('$batch is traced', async () => { - await POST( - '/odata/v4/genre/$batch', - { - requests: [ - { id: 'r1', method: 'POST', url: '/Genres', headers: { 'content-type': 'application/json' }, body: {} }, - { id: 'r2', method: 'GET', url: '/Genres', headers: {} } - ] - }, - admin + await asExternalClient(() => + POST( + '/odata/v4/genre/$batch', + { + requests: [ + { id: 'r1', method: 'POST', url: '/Genres', headers: { 'content-type': 'application/json' }, body: {} }, + { id: 'r2', method: 'GET', url: '/Genres', headers: {} } + ] + }, + admin + ) ) - // 4: POST: create/ new + read after write, GET: read actives + read drafts - expect(log.output.match(/\[telemetry\] - elapsed times:/g).length).to.equal(4) + // With incoming HTTP instrumentation on, the single $batch POST produces one incoming SERVER + // span that becomes the trace root. Both batch sub-requests (the POST -> CREATE Genres draft + // and the GET -> READ Genres) run within that request context, so their tx spans reparent + // under the SERVER span rather than surfacing as separate roots. Result: exactly 1 meaningful + // root (the SERVER span), containing both the CREATE and the READ sub-operations. + await eventually(() => { + const roots = meaningfulRoots() + expect(roots).to.have.lengthOf(1) + expect(roots[0].kind).to.equal(otel.SpanKind.SERVER) + expect(captured.some(s => s.name === 'GenreService - CREATE GenreService.Genres.drafts')).to.be.true + expect(captured.some(s => s.name === 'GenreService - READ GenreService.Genres')).to.be.true + }) }) test('cds.spawn is traced', async () => { await POST('/odata/v4/admin/test_spawn', {}, admin) - await wait(30) - // 2: action + spawned action - expect(log.output.match(/\[telemetry\] - elapsed times:/g).length).to.equal(2) + // 2 visible roots: the action invocation + the spawned task + await eventually(() => { + expect(meaningfulRoots()).to.have.lengthOf(2) + expect(captured.some(s => s.name === 'cds.spawn - schedule task')).to.be.true + expect(captured.some(s => s.name === 'cds.spawn - run task')).to.be.true + }) }) test('emit is traced', async () => { await POST('/odata/v4/admin/test_emit', {}, admin) - await wait(100) - // 1: local-messaging remains in same context - expect(log.output.match(/\[telemetry\] - elapsed times:/g).length).to.equal(1) + // local-messaging keeps the consumer in the same context → exactly 1 visible root + await eventually(() => expect(meaningfulRoots()).to.have.lengthOf(1)) }) describe('db', () => { describe('ql', () => { test('SELECT is traced', async () => { await SELECT.from('sap.capire.bookshop.Books') - // primitive check that console has trace logs - expect(log.output).to.match(/\[telemetry\] - elapsed times:/) - expect(log.output).to.match( - /\s+\d+\.\d+ → \s*\d+\.\d+ = \s*\d+\.\d+ ms \s* db - READ sap\.capire\.bookshop\.Books/ - ) + expect(captured.some(s => s.name === 'db - READ sap.capire.bookshop.Books')).to.be.true }) }) test('native db statement is traced', async () => { const db = await cds.connect.to('db') await db.run('SELECT ID, title, stock, price FROM AdminService_Books WHERE ID = 201 OR ID = 207') - // primitive check that console has trace logs - expect(log.output).to.match(/\[telemetry\] - elapsed times:/) - expect(log.output).to.match( - /\s+\d+\.\d+ → \s*\d+\.\d+ = \s*\d+\.\d+ ms \s* db - SELECT .* FROM AdminService_Books WHERE ID = 201 OR I…/ - ) + // The wrapper "db - SELECT …" span carries the raw SQL as part of the name. + expect(captured.some(s => s.name.startsWith('db - SELECT') && s.name.includes('AdminService_Books'))).to.be.true }) }) test('custom spans are supported', async () => { await GET('/odata/v4/catalog/ListOfBooks', {}, admin) - await wait(100) - expect(log.output.match(/my custom span/g).length).to.equal(1) + await eventually(() => expect(captured.filter(s => s.name === 'my custom span')).to.have.lengthOf(1)) }) // --- TODO --- diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 00000000..66400b5f --- /dev/null +++ b/test/utils.js @@ -0,0 +1,99 @@ +// Shared test helpers, centralized here so the ~10 tracing/metrics suites stop copy-pasting them. +// +// Kept dependency-light on purpose: it does NOT `require('@sap/cds')` at module top (doing so once +// broke span capture for the in-memory span exporter — the cds require has to happen inside the test +// file, after the profile is applied). Only @opentelemetry primitives + node timers here. +// +// `clearOutbox` uses the global `DELETE` (the cds query API). That global is installed by cds.test() +// in the test process, and clearOutbox is only ever called from within hooks/tests (after setup), so +// the global is reliably present at call time — the module never needs to require @sap/cds itself. + +const otel = require('@opentelemetry/api') +const { suppressTracing } = require('@opentelemetry/core') +const { setTimeout: wait } = require('node:timers/promises') + +// The test's HTTP client runs in-process and, with outgoing HTTP instrumentation now enabled, would +// itself create a CLIENT span for every request — an artificial extra root that also overwrites any +// manually-set `traceparent` header. Real callers are separate, un-instrumented processes, so we run +// client-side requests under suppressTracing to model that: the outgoing CLIENT span is skipped and +// the incoming SERVER span is created normally by the server handler. +const asExternalClient = fn => otel.context.with(suppressTracing(otel.context.active()), fn) + +// Best-effort outbox clear that can NEVER hang the surrounding hook. On the shared HANA HDI +// container a background queue worker may be holding the connection pool (draining/retrying), so a +// bare `DELETE` can block indefinitely — which previously turned into a hook timeout that starved +// the pool and cascaded into ECONNREFUSED for the NEXT test file's server. Race the DELETE against a +// short timeout and swallow errors: if it can't complete quickly, the leftover rows are handled by +// the next file's own beforeEach clear anyway. +async function clearOutbox(timeout = 5000) { + try { + await Promise.race([DELETE.from('cds.outbox.Messages'), wait(timeout)]) + } catch { + // pool draining / server shutting down — nothing left to clean matters + } +} + +// Force-flush the tracer provider's span processor so any spans buffered by background activity are +// exported into `captured`. The global provider is a ProxyTracerProvider (no forceFlush) whose +// delegate is the real NodeTracerProvider; guard for the no-op provider so a misconfigured profile +// fails loudly, not silently. +async function flushSpans() { + const provider = otel.trace.getTracerProvider() + const delegate = provider.getDelegate?.() ?? provider + if (typeof delegate.forceFlush === 'function') await delegate.forceFlush() +} + +// State-based wait: repeatedly flush + re-run the assertion until it holds or times out. Replaces +// the fixed `wait(...)` sleeps that flake on HANA, where background/spawned work flushes its data +// after any reasonable fixed window. +// +// `flush` is the target to force before each re-check. It defaults to `flushSpans` (the span +// callers). Metric callers pass the meter provider's `forceFlush` (exported by MyInMemoryMetricReader) +// — passing it keeps this module from depending on the reader. Defaults (timeout 15000, interval 50) +// match the span call sites; metric call sites pass their own timeout/interval explicitly. +async function eventually(fn, { flush = flushSpans, timeout = 15000, interval = 50 } = {}) { + const start = Date.now() + let lastError + while (true) { + await flush() + try { + await fn() + return + } catch (err) { + lastError = err + if (Date.now() - start >= timeout) throw lastError + await wait(interval) + } + } +} + +// Build an `expectEventually(assertion)` bound to a specific flush target + poll defaults, so the +// metric suites don't each re-declare the same one-line wrapper. Metric callers pass the meter +// provider's `forceFlush` and their own {timeout, interval} (which vary per suite); the returned +// helper takes just the assertion. Equivalent to `a => eventually(a, { flush, timeout, interval })`. +const makeExpectEventually = + (flush, { timeout, interval } = {}) => + assertion => + eventually(assertion, { flush, timeout, interval }) + +// On HANA the persistent-outbox queue scheduler periodically scans `cds.outbox.Messages` in its own +// `db - tx` (a SELECT + optional UPDATE that finds nothing to dispatch). Those land as extra root +// traces unrelated to the emit under test — and because the single HDI container is shared across all +// test files, scans triggered by other files' lingering workers show up too. Filter those pure +// outbox-scan traces so the exact root-count assertions stay stable. A scan trace is a `db - tx` root +// whose every span only touches `cds.outbox.Messages` (no application entity, no messaging/handle span). +const isOutboxScanTrace = g => + g.root.name === 'db - tx' && g.all.every(s => s.name === 'db - tx' || s.name.includes('cds.outbox.Messages')) + +// Drop the outbox-scan bookkeeping traces from a `groupedByTrace()` array, returning the meaningful groups. +const meaningful = groups => groups.filter(g => !isOutboxScanTrace(g)) + +module.exports = { + asExternalClient, + clearOutbox, + flushSpans, + eventually, + makeExpectEventually, + isOutboxScanTrace, + meaningful +} diff --git a/vitest.config.mjs b/vitest.config.mjs new file mode 100644 index 00000000..987f3e1b --- /dev/null +++ b/vitest.config.mjs @@ -0,0 +1,65 @@ +import { defineConfig, configDefaults } from 'vitest/config' + +// Default: 42s timeout, run every *.test.js file. +let testTimeout = 42000 +let hookTimeout = 30000 +let include = ['test/**/*.test.js'] +let exclude = configDefaults.exclude + +// HANA CI runs the FULL suite (`test/**/*.test.js`, the default `include`) with a +// 10x test timeout since HANA is slower than sqlite. The `cds_requires_telemetry_tracing` +// env has to be set here, before any test file requires @sap/cds, so keep it in the +// config module. +const HANA = process.env.CI && process.env.HANA_DRIVER +if (HANA) { + testTimeout *= 10 + + // Multitenancy needs a bound BTP Service Manager (MTX) to provision per-tenant HDI + // containers. The HANA CI runs against a single pre-provisioned HDI container with no + // Service Manager, so these two suites can't run there — exclude them from the HANA job + // entirely (they still run on sqlite with in-memory tenants). + exclude = [...configDefaults.exclude, '**/tracing-mt.test.js', '**/metrics-outbox-multitenant.test.js'] + + // Signal "running on HANA" to test files that must branch at COLLECTION time (before + // cds.test() applies its --profile), e.g. the queue/outbox files that skip the sqlite-only + // cds.spawn cases. Reading cds.env at collection time would freeze the env singleton before + // the profile is applied, so files read this env var instead. + process.env.TELEMETRY_TEST_HANA = '1' + + if (process.env.HANA_PROM) + process.env.cds_requires_telemetry_tracing = JSON.stringify({ _hana_prom: process.env.HANA_PROM === 'true' }) +} + +export default defineConfig({ + test: { + // globals:true keeps describe/test/beforeEach/... available without importing + // them in every test file (smallest diff to the existing jest suite). + globals: true, + include, + exclude, + testTimeout, + hookTimeout, + // A couple of queue/outbox tests are timing-sensitive against the SHARED remote HANA Cloud + // HDI container (non-deterministic queue-worker latency); the afterAll settle reduces but + // can't fully remove the flakiness. Retry on HANA only so an unlucky timing miss self-heals; + // sqlite (per-file in-memory DB) is deterministic and gets no retries. + retry: HANA ? 2 : 0, + // The OTLP exporters (and CAP's telemetry SDK) can leave open handles/timers + // alive. Run each test file in its own forked child process so that, once a + // file finishes, its process is torn down and the handles die with it. This + // is what makes the suite EXIT CLEANLY where jest needed --forceExit. + // (In Vitest 4 the former poolOptions.forks.* are top-level options.) + pool: 'forks', + // fresh child per file: matches jest's per-file isolation and preserves the + // top-of-module process.env mutations some test files rely on. + isolate: true, + // On HANA every test file shares ONE HDI container (unlike sqlite's per-file + // in-memory DB), so files must not run concurrently: parallel workers collide on + // fixture INSERTs and on the shared cds.outbox.Messages table. Run files serially + // on HANA; the queue/outbox test files also clear the outbox in a beforeAll so a + // prior file's leftover rows can't bleed in. (sqlite keeps full parallelism.) + fileParallelism: !HANA, + // don't hang the run waiting on lingering handles at teardown. + teardownTimeout: 5000 + } +})