diff --git a/.github/actions/cleanup-tests/action.yml b/.github/actions/cleanup-tests/action.yml index 2c87e472b..ea142cfb4 100644 --- a/.github/actions/cleanup-tests/action.yml +++ b/.github/actions/cleanup-tests/action.yml @@ -18,7 +18,7 @@ runs: docker compose -f tests/docker/docker-compose.yml logs requestor > log-output/${{inputs.type}}-requestor.log - name: Upload provider output and logs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: ${{inputs.type}}-golem-provider-and-requestor-logs diff --git a/.github/actions/prepare-tests/action.yml b/.github/actions/prepare-tests/action.yml index 2e7ac04b6..76d700a05 100644 --- a/.github/actions/prepare-tests/action.yml +++ b/.github/actions/prepare-tests/action.yml @@ -1,5 +1,10 @@ name: "Prepare providers and requestor" description: "Prepare providers and requestor" +inputs: + type: + description: "Type of test" + required: true + default: "test" runs: using: "composite" steps: @@ -11,6 +16,7 @@ runs: - name: Set up Versions shell: bash run: | + echo "PAYMENT_NETWORK=${PAYMENT_NETWORK}" >> $GITHUB_ENV echo "PROVIDER_VERSION=${PROVIDER_VERSION}" >> $GITHUB_ENV echo "REQUESTOR_VERSION=${REQUESTOR_VERSION}" >> $GITHUB_ENV echo "PROVIDER_WASI_VERSION=${PROVIDER_WASI_VERSION}" >> $GITHUB_ENV @@ -37,9 +43,19 @@ runs: shell: bash run: | sleep 10 + echo "Going to fund the requestor" docker exec -t docker-requestor-1 /bin/sh -c "/golem-js/tests/docker/fundRequestor.sh" + echo "Successfully funded the requestor" - name: Install and build the SDK in the docker container shell: bash run: | - docker exec -t docker-requestor-1 /bin/sh -c "cd /golem-js && npm i && npm run build && ./node_modules/.bin/cypress install && npm install --prefix examples && npm install ts-node" + echo "Going to build the SDK on the requestor" + docker exec -t docker-requestor-1 /bin/sh -c "cd /golem-js && npm i && npm run build" + echo "Successfully built the SDK on the requestor" + + - name: Install Cypress + if: ${{ inputs.type == 'cypress' }} + shell: bash + run: | + docker exec -t docker-requestor-1 /bin/sh -c "cd /golem-js && ./node_modules/.bin/cypress install" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84ce9f8a3..f33e38773 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,10 +28,10 @@ jobs: timeout-minutes: 10 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup NodeJS ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} @@ -44,3 +44,4 @@ jobs: npm run build npm install --prefix examples npm run --prefix examples lint:ts + npm run test:import diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 253e096a7..8bd30db00 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,14 +14,18 @@ on: # Allows triggering the workflow manually workflow_dispatch: inputs: + payment_network: + description: "Payment network (holesky, goerli, mainnet, mumbai, polygon, rinkeby)" + required: false + default: "holesky" provider_version: - description: "Provider version (e.g., v0.14.0 or pre-rel-v0.14.1)" + description: "Provider version (e.g., v0.15.0 or pre-rel-v0.15.1)" required: false - default: "v0.14.0" + default: "v0.15.2" requestor_version: - description: "Requestor version (e.g., v0.14.0 or pre-rel-v0.14.1)" + description: "Requestor version (e.g., v0.15.0 or pre-rel-v0.15.1)" required: false - default: "v0.14.0" + default: "v0.15.2" provider_wasi_version: description: "Provider WASI version (e.g., v0.2.2)" required: false @@ -36,10 +40,11 @@ permissions: contents: read # for checkout env: - PROVIDER_VERSION: ${{ github.event.inputs.provider_version || 'v0.14.0' }} - REQUESTOR_VERSION: ${{ github.event.inputs.requestor_version || 'v0.14.0' }} + PROVIDER_VERSION: ${{ github.event.inputs.provider_version || 'v0.15.2' }} + REQUESTOR_VERSION: ${{ github.event.inputs.requestor_version || 'v0.15.2' }} PROVIDER_WASI_VERSION: ${{ github.event.inputs.provider_wasi_version || 'v0.2.2' }} PROVIDER_VM_VERSION: ${{ github.event.inputs.provider_vm_version || 'v0.3.0' }} + PAYMENT_NETWORK: ${{ github.event.inputs.payment_network || 'holesky' }} jobs: regular-checks: @@ -55,10 +60,10 @@ jobs: timeout-minutes: 10 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup NodeJS ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} @@ -71,12 +76,13 @@ jobs: npm run build npm install --prefix examples npm run --prefix examples lint:ts + npm run test:import - name: Upload unit test reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: - name: unit-test-report + name: unit-test-report-${{matrix.os}}-node-${{matrix.node-version}} path: reports/unit-report.xml run-e2e-tests: @@ -84,7 +90,7 @@ jobs: needs: regular-checks runs-on: goth2 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Prepare providers and requestor uses: ./.github/actions/prepare-tests @@ -93,7 +99,7 @@ jobs: run: docker exec -t docker-requestor-1 /bin/sh -c "cd /golem-js && npm run test:e2e" - name: Upload E2E reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: e2e-test-report @@ -101,27 +107,49 @@ jobs: - name: Cleanup test environment uses: ./.github/actions/cleanup-tests + if: always() with: type: "e2e" + run-examples-tests: + name: Run Examples tests + needs: regular-checks + runs-on: goth2 + steps: + - uses: actions/checkout@v4 + + - name: Prepare providers and requestor + uses: ./.github/actions/prepare-tests + + - name: Run the Examples tests + run: docker exec -t docker-requestor-1 /bin/sh -c "cd /golem-js && npm run test:examples -- --exitOnError" + + - name: Cleanup test environment + uses: ./.github/actions/cleanup-tests + if: always() + with: + type: "examples" + run-cypress-tests: name: Run Cypress tests needs: regular-checks runs-on: goth2 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Prepare providers and requestor uses: ./.github/actions/prepare-tests + with: + type: "cypress" - name: Run web server - run: docker exec -t -d docker-requestor-1 /bin/sh -c "cd /golem-js/examples/web && node app.mjs" + run: docker exec -t -d docker-requestor-1 /bin/sh -c "cd /golem-js/examples && npm run web" - name: Run test suite run: docker exec -t docker-requestor-1 /bin/sh -c "cd /golem-js && npm run test:cypress -- --browser chromium" - name: Upload test logs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: cypress-logs @@ -132,27 +160,9 @@ jobs: with: type: "cypress" - run-examples-tests: - name: Run Examples tests - needs: regular-checks - runs-on: goth2 - steps: - - uses: actions/checkout@v3 - - - name: Prepare providers and requestor - uses: ./.github/actions/prepare-tests - - - name: Run the Examples tests - run: docker exec -t docker-requestor-1 /bin/sh -c "cd /golem-js && npm run test:examples -- --exitOnError" - - - name: Cleanup test environment - uses: ./.github/actions/cleanup-tests - with: - type: "examples" - release: name: Release the SDK to NPM and GitHub - needs: [run-e2e-tests, run-cypress-tests, run-examples-tests] + needs: [run-e2e-tests, run-examples-tests, run-cypress-tests] runs-on: ubuntu-latest permissions: contents: write # to be able to publish a GitHub release @@ -161,16 +171,25 @@ jobs: id-token: write # to enable use of OIDC for npm provenance steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup NodeJS - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: # Semantic release requires this as bare minimum node-version: 20 + # Why this? https://github.com/npm/cli/issues/7279 + # Why this way? https://github.com/actions/setup-node/issues/213 + - name: Install latest npm + shell: bash + run: | + npm install -g npm@latest && + npm --version && + npm list -g --depth 0 + - name: Install dependencies run: npm install @@ -185,4 +204,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-release -run-name: "${{ github.workflow }} - Requestor: ${{ github.event.inputs.requestor_version }}, Provider: ${{ github.event.inputs.provider_version }}, WASI: ${{ github.event.inputs.provider_wasi_version }}, VM: ${{ github.event.inputs.provider_vm_version }}" +run-name: "${{ github.workflow }} - Network: ${{ github.event.inputs.payment_network }}, Requestor: ${{ github.event.inputs.requestor_version }}, Provider: ${{ github.event.inputs.provider_version }}, WASI: ${{ github.event.inputs.provider_wasi_version }}, VM: ${{ github.event.inputs.provider_vm_version }}" diff --git a/.gitignore b/.gitignore index c9ff9d5a1..cdd4e7899 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ coverage/ /examples/yacat/*.txt /examples/yacat/*.hash /examples/yacat/*.potfile -/docs +api-reference/ logs/ .cypress diff --git a/.docs/summary-generator.cjs b/.typedoc/summary-generator.cjs similarity index 96% rename from .docs/summary-generator.cjs rename to .typedoc/summary-generator.cjs index 012acc63b..1eee19679 100644 --- a/.docs/summary-generator.cjs +++ b/.typedoc/summary-generator.cjs @@ -21,7 +21,7 @@ async function prepareDocAnchor(docsDir, type, file) { } (async () => { - const docsDir = process.argv[2] || "docs"; + const docsDir = process.argv[2] || "api-reference"; const directoryPath = path.join(__dirname, "..", docsDir); const logFilePath = path.join(process.argv[3] || docsDir, "overview.md"); const logStream = fs.createWriteStream(logFilePath, { flags: "w" }); diff --git a/.docs/typedoc-frontmatter-theme.cjs b/.typedoc/typedoc-frontmatter-theme.cjs similarity index 100% rename from .docs/typedoc-frontmatter-theme.cjs rename to .typedoc/typedoc-frontmatter-theme.cjs diff --git a/README.md b/README.md index 77ead52bb..ee1940224 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +
+ +

+ golem-js SDK logo +

+ # Golem JavaScript API ![GitHub](https://img.shields.io/github/license/golemfactory/golem-js) @@ -14,25 +20,36 @@ - [Golem JavaScript API](#golem-javascript-api) - [Table of contents](#table-of-contents) - - [What's Golem and `golem-js`?](#whats-golem-and-golem-js) - - [System requirements](#system-requirements) + - [Features](#features) + - [Getting Started](#getting-started) + - [What's Golem and `golem-js`?](#whats-golem-and-golem-js) + - [SDK Learning resources](#sdk-learning-resources) - [Installation](#installation) - - [Building](#building) - - [Usage](#usage) - - [Hello World example](#hello-world-example) - - [More examples](#more-examples) - [Supported environments](#supported-environments) - - [Golem Network Market Basics](#golem-network-market-basics) - - [Mid-agreement payments to the Providers for used resources](#mid-agreement-payments-to-the-providers-for-used-resources) - - [Limit price limits to filter out offers that are too expensive](#limit-price-limits-to-filter-out-offers-that-are-too-expensive) - - [Work with reliable providers](#work-with-reliable-providers) + - [Getting started with Golem Network](#getting-started-with-golem-network) + - [Obtain an `app-key` to use with SDK](#obtain-an-app-key-to-use-with-sdk) + - [Usage](#usage) + - [Examples](#examples) + - [Documentation](#documentation) - [Debugging](#debugging) - [Testing](#testing) - [Contributing](#contributing) + - [Discord](#discord) - [See also](#see-also) -## What's Golem and `golem-js`? +## Features + +Become a **Requestor** in the **Golem Network** and use this SDK to: + +- 🌐 Acquire compute resources from Providers using a convenient API +- 🚒 Run your workloads with these resources and get the results back to your machine +- πŸ” Build N-tier application deployments and run them within a VPN +- πŸ’° Settle payments with Providers for the resources you've utilized + +## Getting Started + +### What's Golem and `golem-js`? **[The Golem Network](https://golem.network)** fosters a global group of creators building ambitious software solutions that will shape the technological landscape of future generations by accessing computing resources across the platform. @@ -42,19 +59,47 @@ resources and connecting users through a flexible, open-source platform. **golem-js** is the JavaScript API that allows developers to connect to their Golem nodes and manage their distributed, computational loads through Golem Network. -## System requirements +### SDK Learning resources -To use `golem-js`, it is necessary to have yagna installed, with a **recommended minimum version of v0.14.0**. Yagna is a -service that communicates and performs operations on the Golem Network, upon your requests via the SDK. You -can [follow these instructions](https://docs.golem.network/docs/creators/javascript/quickstarts/quickstart#install-yagna-2) -to set it up. +- [Basic concepts documentation](./docs/CONCEPTS.md) - Learn about the basic and advanced building blocks at your + disposal. +- [Usage documentation](./docs/USAGE.md) - Explore supported usage and implementation patterns. +- [Feature documentation](./docs/FEATURES.md) - Description of interesting features that we've prepared. + +## Installation + +To quickly get started with a new project using `golem-js`, you can use the following template: + +```bash +npx @golem-sdk/cli@latest new my-awesome-golem-project +``` + +`@golem-sdk/golem-js` is available as a [NPM package](https://www.npmjs.com/package/@golem-sdk/golem-js). + +You can install it through `npm`: + +```bash +npm install @golem-sdk/golem-js +``` + +or by `yarn`: + +```bash +yarn add @golem-sdk/golem-js +``` -### Simplified installation steps +## Supported environments -In order to get started and on Golem Network and obtain test GLM tokens (`tGLM`) that will allow you to build on the -test network, follow these steps: +The SDK is designed to work with LTS versions of Node (starting from 18) +and with browsers. The minimum supported `yagna` version is `0.15.2`. + +## Getting started with Golem Network -#### Join the network as a requestor and obtain test tokens +Before you start using the SDK, you need to have `yagna` installed and running on your machine. Yagna is a service that +communicates and performs operations on the Golem Network, upon your requests via the SDK. You can follow the +instructions below or visit +the [official documentation](https://docs.golem.network/docs/creators/javascript/quickstarts/quickstart#install-yagna-2) +to set it up. ```bash # Join the network as a requestor @@ -63,7 +108,12 @@ curl -sSf https://join.golem.network/as-requestor | bash - # Start the golem node on your machine, # you can use `daemonize` to run this in background yagna service run +``` + +Now that you have `yagna` running, you can initialize your requestor and request funds (`tGLM` tokens) on the test +network. +```bash # IN SEPARATE TERMINAL (if not daemonized) # Initialize your requestor yagna payment init --sender --network holesky @@ -75,205 +125,95 @@ yagna payment fund --network holesky yagna payment status --network holesky ``` -#### Obtain your `app-key` to use with SDK +### Obtain an `app-key` to use with SDK If you don't have any app-keys available from `yagna app-key list`, go ahead and create one with the command below. -You will need this key in order to communicate with `yagna` from your application via `golem-js`.You can set it +You will need this key in order to communicate with `yagna` from your application. You can set it as `YAGNA_APPKEY` environment variable. ```bash yagna app-key create my-golem-app ``` -## Installation - -`@golem-sdk/golem-js` is available as a [NPM package](https://www.npmjs.com/package/@golem-sdk/golem-js). - -You can install it through `npm`: - -```bash -npm install @golem-sdk/golem-js -``` - -or by `yarn`: - -```bash -yarn add @golem-sdk/golem-js -``` - -## Building - -To build a library available to the NodeJS environment: - -```bash -npm run build -# or -yarn build -``` - -This will generate production code in the `dist/` directory ready to be used in your nodejs or browser applications. - ## Usage -### Hello World example +You can rent a single machine and run a simple task on it: ```ts -import { TaskExecutor } from "@golem-sdk/golem-js"; +import { MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; + +// Define the order that we're going to place on the market +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + // We're only going to rent the provider for 5 minutes max + rentHours: 5 / 60, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +(async () => { + const glm = new GolemNetwork(); -(async function main() { - const executor = await TaskExecutor.create("golem/alpine:latest"); try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (error) { - console.error("Computation failed:", error); + await glm.connect(); + // Rent a machine + const rental = await glm.oneOf({ order }); + await rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem! πŸ‘‹")) + .then((res) => console.log(res.stdout)); + await rental.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); } finally { - await executor.shutdown(); + await glm.disconnect(); } -})(); +})().catch(console.error); ``` -### More examples +Read about [other available usage patterns](./docs/USAGE.md) to learn more on how you can leverage the SDK. + +## Examples The [examples directory](./examples) in the repository contains various usage patterns for the SDK. You can browse through them and learn about the recommended practices. All examples are automatically tested during our release process. -In case you find an issue with the examples, feel free to submit -an [issue report](https://github.com/golemfactory/golem-js/issues) to the repository. - You can find even more examples and tutorials in the [JavaScript API section of the Golem Network Docs](https://docs.golem.network/docs/creators/javascript). -## Supported environments - -The SDK is designed to work with LTS versions of Node (starting from 18) -and with browsers. - -## Golem Network Market Basics - -The Golem Network provides an open marketplace where anyone can join as a Provider and supply the network with their -computing power. In return for their service, they are billing Requestors (users of this SDK) according to the pricing -that they define. - -As a Requestor, you might want to: - -- control the limit price so that you're not going to over-spend your funds -- control the interactions with the providers if you have a list of the ones which you like or the ones which you would - like to avoid - -To make this easy, we provided you with a set of predefined market proposal filters, which you can combine to implement -your own market strategy (described below). - -### Mid-agreement payments to the Providers for used resources - -When you obtain resources from the Provider and start using them, the billing cycle will start immediately. -Since reliable service and payments are important for all actors in the Golem Network, -the SDK makes use of the mid-agreement payments model and implements best practices for the market, which include: - -- responding and accepting debit notes for activities that last longer than 30 minutes -- issuing mid-agreement payments (pay-as-you-go) - -By default, the SDK will: - -- accept debit notes sent by the Providers within two minutes of receipt (so that the Provider knows that we're alive, - and it will continue serving the resources) -- issue a mid-agreement payment every 12 hours (so that the provider will be paid on a regular interval for serving the - resources for more than 10 hours) +## Documentation -You can learn more about -the [mid-agreement and other payment models from the official docs](https://docs.golem.network/docs/golem/payments). - -These values are defaults and can be influenced by the following settings: - -- `DemandOptions.expirationSec` -- `DemandOptions.debitNotesAcceptanceTimeoutSec` -- `DemandOptions.midAgreementPaymentTimeoutSec` - -If you're using `TaskExecutor` to run tasks on Golem, you can pass them as part of the configuration object accepted -by `TaskExecutor.create`. Consult [JS API reference](https://docs.golem.network/docs/golem-js/reference/overview) for -details. - -### Limit price limits to filter out offers that are too expensive - -```typescript -import { TaskExecutor, ProposalFilterFactory } from "@golem-sdk/golem-js"; - -const executor = await TaskExecutor.create({ - // What do you want to run - package: "golem/alpine:3.18.2", - - // How much you wish to spend - budget: 0.5, - proposalFilter: ProposalFilterFactory.limitPriceFilter({ - start: 1, - cpuPerSec: 1 / 3600, - envPerSec: 1 / 3600, - }), - - // Where you want to spend - payment: { - network: "polygon", - }, -}); -``` - -To learn more about other filters, please check -the [API reference of the market/strategy module](https://docs.golem.network/docs/golem-js/reference/modules/market_strategy) - -### Work with reliable providers - -The `getHealthyProvidersWhiteList` helper will provide you with a list of Provider ID's that were checked with basic -health-checks. Using this whitelist will increase the chance of working with a reliable provider. Please note, that you -can also build up your own list of favourite providers and use it in a similar fashion. - -```typescript -import { MarketHelpers, ProposalFilterFactory, TaskExecutor } from "@golem-sdk/golem-js"; - -// Collect the whitelist -const verifiedProviders = await MarketHelpers.getHealthyProvidersWhiteList(); - -// Prepare the whitelist filter -const whiteList = ProposalFilterFactory.allowProvidersById(verifiedProviders); - -// Prepare the price filter -const acceptablePrice = ProposalFilterFactory.limitPriceFilter({ - start: 1, - cpuPerSec: 1 / 3600, - envPerSec: 1 / 3600, -}); - -const executor = await TaskExecutor.create({ - // What do you want to run - package: "golem/alpine:3.18.2", - - // How much you wish to spend - budget: 0.5, - proposalFilter: (proposal) => acceptablePrice(proposal) && whiteList(proposal), - - // Where you want to spend - payment: { - network: "polygon", - }, -}); -``` +Visit our [official documentation](https://docs.golem.network/docs/creators/javascript) to learn more about the +JavaScript SDK and how to use it. ## Debugging -The SDK uses the [debug](https://www.npmjs.com/package/debug) package to provide debug logs. To enable them, set the `DEBUG` environment variable to `golem-js:*` or `golem-js:market:*` to see all logs or only the market-related ones, respectively. For more information, please refer to the [debug package documentation](https://www.npmjs.com/package/debug). +The SDK uses the [debug](https://www.npmjs.com/package/debug) package to provide debug logs. To enable them, set +the `DEBUG` environment variable to `golem-js:*` or `golem-js:market:*` to see all logs or only the market-related ones, +respectively. For more information, please refer to +the [debug package documentation](https://www.npmjs.com/package/debug). ## Testing -Read the dedicated [testing documentation](./TESTING.md) to learn more about how to run tests of the SDK. +Read the dedicated [testing documentation](./docs/TESTING.md) to learn more about how to run tests of the SDK. ## Contributing -It is recommended to run unit tests and static code analysis before committing changes. +Read the [Contributing Guide](docs/CONTRIBUTING.md) for details on how you can get involved. In case you find an issue with the examples or the SDK itself, feel free to submit +an [issue report](https://github.com/golemfactory/golem-js/issues) to the repository. + +## Discord -```bash -yarn lint -# and -yarn format -``` +Feel invited to join our [Discord](http://discord.gg/golem). You can meet other SDK users and developers in the `#sdk-discussion` and `#js-discussion` channels. ## See also diff --git a/cypress.config.ts b/cypress.config.ts index 4ce809e49..52efca4fe 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -3,7 +3,6 @@ import { defineConfig } from "cypress"; export default defineConfig({ fileServerFolder: "examples/web", supportFolder: "tests/cypress/support", - fixturesFolder: "tests/cypress/fixtures", videosFolder: ".cypress/video", screenshotsFolder: ".cypress/screenshots", defaultCommandTimeout: 180000, @@ -19,6 +18,7 @@ export default defineConfig({ config.env.YAGNA_APPKEY = process.env.YAGNA_APPKEY; config.env.YAGNA_API_BASEPATH = process.env.YAGNA_API_URL; config.env.YAGNA_SUBNET = process.env.YAGNA_SUBNET; + config.env.PAYMENT_NETWORK = process.env.PAYMENT_NETWORK; res(config); }); }, diff --git a/docs/CONCEPTS.md b/docs/CONCEPTS.md new file mode 100644 index 000000000..9143f7deb --- /dev/null +++ b/docs/CONCEPTS.md @@ -0,0 +1,113 @@ +# Concepts + +This document explains the concepts modelled by the SDK which foster your interactions with the Golem Network. + +## Table of Contents + + + +- [Concepts](#concepts) + - [Table of Contents](#table-of-contents) + - [GolemNetwork module](#golemnetwork-module) + - [Why is it needed](#why-is-it-needed) + - [What should it do](#what-should-it-do) + - [How it was done](#how-it-was-done) + - [Resource Rental Model](#resource-rental-model) + - [Why it is needed](#why-it-is-needed) + - [What it should do](#what-it-should-do) + - [How it was done](#how-it-was-done-1) + - [How is it different from TaskExecutor available in versions `1.x` and `2.x`](#how-is-it-different-from-taskexecutor-available-in-versions-1x-and-2x) + - [Exe Unit](#exe-unit) + - [Why is it needed](#why-is-it-needed-1) + - [What it should do](#what-it-should-do-1) + - [How it was done](#how-it-was-done-2) + + +## GolemNetwork module + +### Why is it needed + +When picturing the problem domain which is tackled by Golem Network, one see that the activities performed in this domain can fall into the following categories: + +- **Market** - where the computational needs of the Requestors need to meet the offers of the Providers, so that they can negotiate and establish an agreement. +- **Activity** - where Activities are started on ExeUnits running on the Providers under the terms of the negotiated Agreement. +- **Work** - where the Requestors preform their operations on the acquired resources (within the started activities). +- **Payment** - where payment related entities (DebitNotes, Invoices) are exchanged and final payments are made by the Requestor to the Provider. + +With `golem-js` we help you (the Requestor) by taking care of the _Market_, _Activity_ and _Payments_ faucets of the domain. + +### What should it do + +The `GolemNetwork` should serve as the main entry-point for `golem-js`. Users are expected to create new instances of this object, use `connect` and `disconnect` methods properly to feel the notion of "connecting" to the Golem Network. + +```ts +import { GolemNetwork } from "@golem-sdk/golem-js"; + +const glm = new GolemNetwork(); + +try { + await glm.connect(); + + // Do your work here +} catch (err) { + // Handle any errors +} finally { + await glm.disconnect(); +} +``` + +Once the user _connects_ to the network (in reality, connecting to the locally installed `yagna`), they have two was of leveraging the API's of that object. + +1. Use high-level generic purpose APIs such as `oneOf` or `manyOf` to acquire computational resources, without the need of diving deep into the various subdomains within Golem Network's problem space. +2. Use low-level modules representing these subdomains by accessing `glm.market`, `glm.activity`, `glm.payment` properties of the `GolemNetwork` object. + +### How it was done + +We do this by shaping modules reflecting these subdomains and exposing them from `golem-js` in form of properties of the `GolemNetwork` object. This way, you can rely on `golem-js` in these three areas and focus on your field of expertise, which is _Work_. + +## Resource Rental Model + +### Why it is needed + +The [Golem Network's whitepaper](https://assets.website-files.com/62446d07873fde065cbcb8d5/62446d07873fdeb626bcb927_Golemwhitepaper.pdf) coined the following definition: + +> Golem connects computers in a peer-to-peer network, enabling both application +> owners and individual users ("requestors") to rent resources of other users’ +> ("providers") machines. These resources can be used to complete tasks requiring any +> amount of computation time and capacity. + +Since version `3.0`, `golem-js` leverages the notion of _renting compute resources_ and models the exposed domain APIs around it. + +### What it should do + +The primary tasks for the model to deliver are: + +- provide an abstraction over the complexity of _Golem Network Protocol_ and leverage the "rent compute resources, access agreement and cost information" +- shorten the path of the user required to access a deployed instance of their workload +- provide convenience APIs that allow easier exploration of the Golem Network domain (example: `rental.agreement.provider` allows _easier_ access to Provider information) + +### How it was done + +To deliver this vision, `golem-js` exposes the `ResourceRental` **aggregate** which wraps around the details of the _Golem Network Protocol_: the loosely coupled entities which it defines, and the processes/communication schemes which it requires. These include things _Agreements_, _Allocations_, _Activities_, _Invoices_, _DebitNotes_ and related conversations required by the protocol such as _debit note or invoice acceptance_ between Provider and Requestor (you). + +When using the `ResourceRental` model, you can still access these lower level domain objects via the APIs exposed by the `ResourceRental` object instance. + +### How is it different from TaskExecutor available in versions `1.x` and `2.x` + +`TaskExecutor` implemented the so-called _Task Model_, which is oriented around the notion of a _Task_ defined as a series of commands to be executed that have to succeed altogether for the task to be considered as successful. While this model is suitable for use-cases involving batch-map-reduce type of operations that can be distributed across many rented resources, it falls short in cases where users want to rent out resources and execute long-running activities such as hosting web services for many users (multi-tenancy). + +## Exe Unit + +### Why is it needed + +When you obtain resources via the Golem Network, your _Golem Virtual Machine Image (GVMI for short)_ is going to be deployed by `golem-js` into the _Activity_ running on the Provider. Technically, on the Provider instantiates an _ExeUnit_ which is a physical implementation of the _Activity_ of the Golem Network Protocol. Without digging in too much details, it's this _ExeUnit_ which at the end performs the operations that you issue via the Golem Network. + +As a Requestor you're interested in quickly executing your commands within the container that runs your image. The `ExeUnit` abstraction delivered by the SDK is meant to do enable you to do so. The `ExeUnit` type documents the available features of particular ExeUnit type so that you can build up your solution faster, and in a type-safe manner. + +### What it should do + +The delivered `ExeUnit` implementation is a **use case** object which exposes APIs that simplify the interaction with the exe-unit running on the Provider. Technically, it models the _commands_ supported by the exe-unit depending on its runtime, so that the user can focus on issuing `exe.upload` or `exe.run` commands instead of understanding the implementation details of the VM exe-unit runtime. + +### How it was done + +Given a Requestor obtains `ResourceRental`, they can obtain the handle to the exe-unit thanks to `getExeUnit` method. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 000000000..ccd1d3c39 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,81 @@ +# Contributing + +You want to contribute to `golem-js`? That's great! This guide will help you get started. + +## Setup local environment + +1. Clone this repository +2. In the root of this project run `npm install` to install all necessary dependencies +3. To build the SDK run `npm run build` +4. Install yagna as described in the [README](../README.md) file - you will need it to test your changes against testnet (no real funds will be required to execute workloads on Golem Network) + +### Unit Testing + +For unit testing `golem-js` uses `jest` with [ts-mockito](https://www.npmjs.com/package/@johanblumenberg/ts-mockito) to mock code. + +You can run tests using: + +```bash +npm run test:unit +``` + +The test files are usually co-located with the files that they test in the `src/` folder. + +### Pre-commit hooks + +We use `husky` to enforce few rules using `prettier`, `eslint` or even commit message format which allows us to use [semantic-release](https://github.com/semantic-release/semantic-release). + +## Pull Request Guidelines + +Our development revolves around few branches: + +- `master` - contains the latest stable production code (production track) +- `beta` - where the SDK team developers next major releases (slow track) +- `alpha` - when a different major release has to take precedence before `beta` (fast track) + +The process is as follows: + +- Depending on the contribution you're planning to make, create a `feature/`, `bugfix/` branch from the base branch (typically `master`), and merge back against that branch. +- In case of any contribution: + - Make sure you provide proper description for the PR (see template below) + - Add test cases if possible within the same PR + +### PR description templates + +#### Feature + +```markdown +## Why is it needed? + +_Explain why the feature is valuable and what problem does it solve._ + +## What should be changed? + +_Explain the general idea behind the code changes - high level description of your solution to the problem stated above._ +``` + +#### Bugfix + +```markdown +## Steps to reproduce + +1. _Do this_ +2. _Then that_ +3. _Finally this_ + +## Expected result + +_Describe the desired outcome (how does fixed look like)_ + +## Actual result + +_Describe the actual outcome (what happens now)_ +``` + +## Discord + +Feel invited to join our [Discord](http://discord.gg/golem). You can meet other SDK users and developers in the `#sdk-discussion` and `#js-discussion` channels. + +## Thanks πŸ’™ + +Thanks for all your contributions and efforts towards improving `golem-js`! diff --git a/docs/FEATURES.md b/docs/FEATURES.md new file mode 100644 index 000000000..41be52ba0 --- /dev/null +++ b/docs/FEATURES.md @@ -0,0 +1,205 @@ +# Features + +## Table of Contents + + + +- [Features](#features) + - [Table of Contents](#table-of-contents) + - [Streaming command results](#streaming-command-results) + - [File transfer](#file-transfer) + - [VPN](#vpn) + - [Events](#events) + - [Custom filters](#custom-filters) + - [Custom ranking of proposals](#custom-ranking-of-proposals) + - [Uploading local images to the provider](#uploading-local-images-to-the-provider) + - [Setup and teardown methods](#setup-and-teardown-methods) + - [Market scan](#market-scan) + - [Read more](#read-more) + + +## Streaming command results + +Instead of waiting for the command to finish, you can stream the results as they come in. This is useful for +long-running +commands, where you want to see the output as it's being produced. + +```ts +const remoteProcess = await exe.runAndStream( + ` +sleep 1 +echo -n 'Hello from stdout' >&1 +sleep 1 +echo -n 'Hello from stdout again' >&1 +sleep 1 +echo -n 'Hello from stdout yet again' >&1 +`, +); +remoteProcess.stdout.on("data", (data) => console.log("stdout>", data)); +await remoteProcess.waitForExit(); +``` + +[Check the full example](../examples/basic/run-and-stream.ts) + +## File transfer + +You can transfer files to and from the remote machine. This is useful when you need to provide input files or retrieve +the results of the computation. + +```ts +await exe + .beginBatch() + .run(`echo "Message from provider ${exe.provider.name}. Hello 😻" >> /golem/work/message.txt`) + .downloadFile("/golem/work/message.txt", "./message.txt") + .end(); +console.log(await readFile("./results.txt", { encoding: "utf-8" })); +``` + +[Check the full example](../examples/basic/transfer.ts) + +## VPN + +You can connect yourself and multiple providers to a VPN network. This is useful when you want to communicate +securely between the nodes. + +```ts +const network = await glm.createNetwork({ ip: "192.168.7.0/24" }); +// ... +const exe1 = await rental1.getExeUnit(); +const exe2 = await rental2.getExeUnit(); +await exe1 + .run(`ping ${exe2.getIp()} -c 4`) + .then((res) => console.log(`Response from provider: ${exe1.provider.name} (ip: ${exe1.getIp()})`, res.stdout)); +``` + +[Check the full example](../examples/basic/vpn.ts) + +## Events + +You can listen to various events that are emitted by the SDK. This is useful when you want to react to certain +conditions, like calculating the total cost of all invoices received. + +```ts +glm.payment.events.on("invoiceAccepted", (invoice) => { + console.log("Invoice '%s' accepted for %s GLM", invoice.id, invoice.amount); +}); +``` + +[Check the full example](../examples/basic/events.ts) + +## Custom filters + +You can define custom filters to select the providers that you want to work with. This is useful when you want to +blacklist or whitelist certain providers. + +```ts +const myFilter: ProposalFilter = (proposal) => proposal.provider.name !== "bad-provider"; + +const order: MarketOrderSpec = { + market: { + offerProposalFilter: myFilter, + // other options + }, +}; +``` + +[Check the full example](../examples/advanced/proposal-filter.ts) + +We have also prepared a set of predefined filters for common +use-cases. [Check out the example with predefined filters here](../examples/advanced/proposal-predefined-filter.ts) + +## Custom ranking of proposals + +You can define a method that will select which proposal should be chosen first. This is useful when you want to +prioritize certain providers over others. + +```ts +const scores = { + "very-good-provider": 10, + "good-provider": 5, + "bad-provider": -10, +}; + +const bestProviderSelector = (proposals: OfferProposal[]) => { + return proposals.sort((a, b) => (scores[b.provider.name] || 0) - (scores[a.provider.name] || 0))[0]; +}; + +const order: MarketOrderSpec = { + market: { + proposalSelector: bestProviderSelector, + // other options + }, +}; +``` + +[Check the full example](../examples/advanced/proposal-selector.ts) + +## Uploading local images to the provider + +You can avoid using the registry and upload a GVMI image directly to the provider. This is useful when you want to +quickly prototype your image without having to update the registry with every change. + +```ts +const order: MarketOrderSpec = { + demand: { + workload: { + imageUrl: "file:///path/to/your/image.gvmi", + }, + // other options + }, +}; +``` + +[Check the full example](../examples/advanced//local-image/) + +## Setup and teardown methods + +You can define a setup method that will be executed the first time a provider is rented and a teardown method +that will be executed before the rental is done. This is useful when you want to avoid doing the same work +multiple times when running multiple tasks on the same provider. + +```ts +// I want to upload a big file to each provider only once +const setup: LifecycleFunction = async (exe) => exe.uploadFile("./big-file.txt", "/golem/work/big-file.txt"); + +// I want to remove the file after I'm done +const teardown: LifecycleFunction = async (exe) => exe.run("rm /golem/work/big-file.txt"); + +const pool = await glm.manyOf({ + order, + poolSize, + setup, + teardown, +}); +``` + +[Check the full example](../examples/advanced/setup-and-teardown.ts) + +## Market scan + +You can scan the market for available providers and their offers. This is useful when you want to see what's available +before placing an order. + +```ts +await glm.market + .scan(order) + .pipe(takeUntil(timer(10_000))) + .subscribe({ + next: (scannedOffer) => { + console.log("Found offer from", scannedOffer.provider.name); + }, + complete: () => { + console.log("Market scan completed"); + }, + }); +``` + +[Check the full example](../examples/advanced/scan.ts) + +## Read more + +If you wish to learn more about how the SDK functions under the hood, please check out our more advanced examples: + +- [Creating pools manually](./.../examples/advanced/manual-pools.ts) +- [Performing all market operations manually](./.../examples/advanced/step-by-step.ts) +- [(for library authors) Override internal module](./.../examples/advanced/override-module.ts) diff --git a/TESTING.md b/docs/TESTING.md similarity index 100% rename from TESTING.md rename to docs/TESTING.md diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md new file mode 100644 index 000000000..6e1ec44be --- /dev/null +++ b/docs/UPGRADING.md @@ -0,0 +1,174 @@ +# Upgrade Guide + +This document describes the breaking changes introduced in each major version of `golem-js` and the necessary steps you need to take to ensure your application continues to work correctly. + +## Upgrading from 2.x to 3.x + +### Migrating from `TaskExecutor` to `GolemNetwork` + +Since the `TaskExecutor` has been removed in this release, you can migrate to the [@golem-sdk/task-executor](https://www.npmjs.com/package/@golem-sdk/task-executor) package as `1.x` of that package is compatible with `golem-js@2.x`. + +If you wish to stick to `golem-js`, here are the examples of changes which you need to make: + +#### Simple, single-command use cases + +Areas where the changes are needed: + +- You stop using `TaskExecutor` and switch to `GolemNetwork` instead. +- Be explicit about the expected computation time and pricing strategy so that `golem-js` can estimate the budget. +- You reach for an exe-unit representation with the `ResourceRental.getExeUnit` method and call your commands via the provided `ExeUnit` instance. + +**Before:** + +```ts +// before +import { TaskExecutor } from "@golem-sdk/golem-js"; + +(async function main() { + const executor = await TaskExecutor.create("golem/alpine:latest"); + try { + await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); + } catch (error) { + console.error("Failed to execute work:", error); + } finally { + await executor.shutdown(); + } +})(); +``` + +**After:** + +```ts +// after +import { GolemNetwork } from "@golem-sdk/golem-js"; + +(async function main() { + const glm = new GolemNetwork(); + try { + await glm.connect(); + + const retnal = await glm.oneOf({ + order: { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + // You have to be now explicit about about your terms and expectatios from the market + market: { + rentHours: 5 / 60, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, + }, + }); + + // You will work with exe-unit objects instead of "executor" + await rental + .getExeUnit() + .then((exe) => exe.run("echo 'Hello World'")) + .then((res) => console.log(res.stdout)); + } catch (error) { + console.error("Failed to execute work:", error); + } finally { + await glm.disconnect(); + } +})(); +``` + +#### Engaging with many providers at the same time + +Areas where the changes are needed: + +- instead of using `maxParallelTasks` from `TaskExecutor`, use `poolSize` option on `GolemNetwork.manyOf` market order spec argument. + +**Before:** + +```ts +// before +import { GolemNetwork } from "@golem-sdk/golem-js"; + +(async function main() { + const executor = await GolemNetwork.create({ + imageTag: "golem/alpine:latest", + // πŸ”’ Number of max providers which you want to engage with + maxParallelTasks: 3, + }); + + try { + const inputs = [1, 2, 3, 4, 5]; + + const results = await Promise.allSettled( + inputs.map((input) => executor.run((ctx) => ctx.run(`echo 'Hello ${input}`))), + ); + + const responses = results.map((p) => (p.status === "fulfilled" ? p.value.stdout : null)).filter((v) => v !== null); + + console.log(responses); + } catch (error) { + console.error("Failed to execute work:", error); + } finally { + await executor.shutdown(); + } +})(); +``` + +```ts +// after +import { GolemNetwork } from "@golem-sdk/golem-js"; + +(async function main() { + const glm = new GolemNetwork(); + try { + await glm.connect(); + + // 🌟 You acquire a pool of ResourceRentals + const pool = await glm.manyOf({ + // πŸ”’ Number of max providers which you want to engage with + poolSize: 3, + order: { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + // You have to be now explicit about about your terms and expectatios from the market + market: { + rentHours: 5 / 60, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, + }, + }); + + const inputs = [1, 2, 3, 4, 5]; + + // You still take the necessary precaucions, pipeline your work and processing + const results = await Promise.allSettled( + inputs.map((input) => + // 🌟🌟 You access rentals from the pool + pool.withRental((rental) => + rental + // 🌟🌟🌟 You issue the comands as in case of a single-provider scenario + .getExeUnit() + .run(`echo 'Hello ${input}`) + .then((res) => res.stdout), + ), + ), + ); + + // You still filter for the values which succeeded + const responses = results.map((p) => (p.status === "fulfilled" ? p.value : null)).filter((v) => v !== null); + + console.log(responses); + } catch (error) { + console.error("Failed to execute work:", error); + } finally { + await glm.disconnect(); + } +})(); +``` diff --git a/docs/USAGE.md b/docs/USAGE.md new file mode 100644 index 000000000..5289ac82b --- /dev/null +++ b/docs/USAGE.md @@ -0,0 +1,113 @@ +# Usage + +## Table of Contents + + + +- [Usage](#usage) + - [Table of Contents](#table-of-contents) + - [Renting a single machine and running a simple task on it](#renting-a-single-machine-and-running-a-simple-task-on-it) + - [Renting many machines and running tasks in parallel](#renting-many-machines-and-running-tasks-in-parallel) + + +## Renting a single machine and running a simple task on it + +```ts +import { MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; + +// Define the order that we're going to place on the market +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + // We're only going to rent the provider for 5 minutes max + rentHours: 5 / 60, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +(async () => { + const glm = new GolemNetwork(); + + try { + await glm.connect(); + // Rent a machine + const rental = await glm.oneOf({ order }); + await rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem! πŸ‘‹")) + .then((res) => console.log(res.stdout)); + await rental.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); +``` + +## Renting many machines and running tasks in parallel + +```ts +import { GolemNetwork, MarketOrderSpec } from "@golem-sdk/golem-js"; + +// Define the order that we're going to place on the market +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +(async () => { + const glm = new GolemNetwork(); + + try { + await glm.connect(); + // create a pool that can grow up to 3 rentals at the same time + const pool = await glm.manyOf({ + poolSize: 3, + order, + }); + // run 3 tasks in parallel on 3 different machines + await Promise.allSettled([ + pool.withRental(async (rental) => + rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem from the first machine! πŸ‘‹")) + .then((res) => console.log(res.stdout)), + ), + pool.withRental(async (rental) => + rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem from the second machine! πŸ‘‹")) + .then((res) => console.log(res.stdout)), + ), + pool.withRental(async (rental) => + rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem from the third machine! πŸ‘‹")) + .then((res) => console.log(res.stdout)), + ), + ]); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); +``` diff --git a/examples/.gitignore b/examples/.gitignore index 45249ec25..28299a44b 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1,3 +1,2 @@ *.bin -*.gvmi package-lock.json \ No newline at end of file diff --git a/examples/advanced/local-image/Dockerfile b/examples/advanced/local-image/Dockerfile new file mode 100644 index 000000000..5c5e5762d --- /dev/null +++ b/examples/advanced/local-image/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine:latest +WORKDIR /golem/work +RUN echo "hello from my local image πŸ‘‹" > /golem/work/hello.txt \ No newline at end of file diff --git a/examples/advanced/local-image/alpine.gvmi b/examples/advanced/local-image/alpine.gvmi new file mode 100644 index 000000000..cccb32cca Binary files /dev/null and b/examples/advanced/local-image/alpine.gvmi differ diff --git a/examples/advanced/local-image/serveLocalGvmi.ts b/examples/advanced/local-image/serveLocalGvmi.ts new file mode 100644 index 000000000..3c4503539 --- /dev/null +++ b/examples/advanced/local-image/serveLocalGvmi.ts @@ -0,0 +1,54 @@ +/** + * This example demonstrates how to upload a local GVMI file to the provider. + * Take a look at the `Dockerfile` in the same directory to see what's inside the image. + */ +import { GolemNetwork, MarketOrderSpec } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +// get the absolute path to the local image in case this file is run from a different directory +const getImagePath = (path: string) => new URL(path, import.meta.url).toString(); + +(async () => { + const logger = pinoPrettyLogger({ + level: "info", + }); + + const glm = new GolemNetwork({ + logger, + }); + + try { + await glm.connect(); + + const order: MarketOrderSpec = { + demand: { + workload: { + // if the image url starts with "file://" it will be treated as a local file + // and the sdk will automatically serve it to the provider + imageUrl: getImagePath("./alpine.gvmi"), + }, + }, + market: { + rentHours: 5 / 60, + pricing: { + model: "linear", + maxStartPrice: 1, + maxCpuPerHourPrice: 1, + maxEnvPerHourPrice: 1, + }, + }, + }; + + const rental = await glm.oneOf({ order }); + // in our Dockerfile we have created a file called hello.txt, let's read it + const result = await rental + .getExeUnit() + .then((exe) => exe.run("cat hello.txt")) + .then((res) => res.stdout); + console.log(result); + } catch (err) { + console.error("Failed to run example on Golem", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/advanced/manual-pools.ts b/examples/advanced/manual-pools.ts new file mode 100644 index 000000000..6e0d2e11a --- /dev/null +++ b/examples/advanced/manual-pools.ts @@ -0,0 +1,103 @@ +import { Allocation, DraftOfferProposalPool, GolemNetwork } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +const RENTAL_DURATION_HOURS = 0.25; +const ALLOCATION_DURATION_HOURS = RENTAL_DURATION_HOURS + 0.25; + +const demandOptions = { + demand: { + workload: { + imageTag: "golem/alpine:latest", + minCpuCores: 1, + minMemGib: 1, + minStorageGib: 2, + }, + }, + market: { + rentHours: RENTAL_DURATION_HOURS, + pricing: { + model: "linear", + maxStartPrice: 1, + maxCpuPerHourPrice: 1, + maxEnvPerHourPrice: 1, + }, + }, +} as const; + +(async () => { + const logger = pinoPrettyLogger({ + level: "info", + }); + + const glm = new GolemNetwork({ + logger, + }); + + console.assert( + ALLOCATION_DURATION_HOURS > RENTAL_DURATION_HOURS, + "Always create allocations that will live longer than the planned rental duration", + ); + + let allocation: Allocation | undefined; + + try { + await glm.connect(); + + allocation = await glm.payment.createAllocation({ budget: 1, expirationSec: ALLOCATION_DURATION_HOURS * 60 * 60 }); + + const proposalPool = new DraftOfferProposalPool({ minCount: 1 }); + const demandSpecification = await glm.market.buildDemandDetails( + demandOptions.demand, + demandOptions.market, + allocation, + ); + + const draftProposal$ = glm.market.collectDraftOfferProposals({ + demandSpecification, + pricing: demandOptions.market.pricing, + }); + + const proposalSubscription = proposalPool.readFrom(draftProposal$); + + /** How many providers you plan to engage simultaneously */ + const PARALLELISM = 2; + + const depModules = { + market: glm.market, + activity: glm.activity, + payment: glm.payment, + rental: glm.rental, + }; + + const pool = depModules.rental.createResourceRentalPool(proposalPool, allocation, { + poolSize: { max: PARALLELISM }, + }); + + const rental1 = await pool.acquire(); + const rental2 = await pool.acquire(); + + await Promise.allSettled([ + rental1 + .getExeUnit() + .then((exe) => exe.run("echo Hello from first activity πŸ‘‹")) + .then((result) => console.log(result.stdout)), + rental2 + .getExeUnit() + .then((exe) => exe.run("echo Hello from second activity πŸ‘‹")) + .then((result) => console.log(result.stdout)), + ]); + + await pool.release(rental1); + await pool.release(rental2); + + proposalSubscription.unsubscribe(); + await pool.drainAndClear(); + } catch (err) { + console.error("Pool execution failed:", err); + } finally { + await glm.disconnect(); + if (allocation) { + await glm.payment.releaseAllocation(allocation); + } + } +})().catch(console.error); diff --git a/examples/advanced/override-module.ts b/examples/advanced/override-module.ts new file mode 100644 index 000000000..f71ffd5d4 --- /dev/null +++ b/examples/advanced/override-module.ts @@ -0,0 +1,72 @@ +/** + * In this advanced example, we will provide our own implementation of one of the core modules + * of the SDK. This example is catered towards library authors who want to extend the SDK's + * functionality or to advanced users who know what they're doing. + * It's **very** easy to break things if you don't have a good understanding of the SDK's internals, + * therefore this feature is not recommended for most users. + */ + +import { MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +// let's override the `estimateBudget` method from the `MarketModule` interface +// to provide our own implementation +// we need to import the default implementation of the module we want to override +import { MarketModuleImpl } from "@golem-sdk/golem-js"; + +class MyMarketModule extends MarketModuleImpl { + estimateBudget({ maxAgreements, order }: { maxAgreements: number; order: MarketOrderSpec }): number { + // let's take the original estimate and add 20% to it as a buffer + const originalEstimate = super.estimateBudget({ maxAgreements, order }); + return originalEstimate * 1.2; + } +} + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + // based on this order, the "normal" estimateBudget would return 1.5 + // (0.5 start price + 0.5 / hour for CPU + 0.5 / hour for env). + // Our override should return 1.8 (1.5 * 1.2) + market: { + rentHours: 1, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 0.5, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + // here's where we provide our own implementation + override: { + market: MyMarketModule, + }, + }); + + // look at the console output to see the budget estimate + glm.payment.events.on("allocationCreated", ({ allocation }) => { + console.log("Allocation created with budget:", Number(allocation.remainingAmount).toFixed(2)); + }); + + try { + await glm.connect(); + const rental = await glm.oneOf({ order }); + await rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem! πŸ‘‹")) + .then((res) => console.log(res.stdout)); + await rental.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/advanced/payment-filters.ts b/examples/advanced/payment-filters.ts new file mode 100644 index 000000000..ff1b28092 --- /dev/null +++ b/examples/advanced/payment-filters.ts @@ -0,0 +1,75 @@ +/** + * This example demonstrates how to use payment filters to prevent auto-accepting + * invoices and debit notes that don't meet certain criteria. + */ +import { MarketOrderSpec, GolemNetwork, InvoiceFilter, DebitNoteFilter } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +/* let's create a sample filter that doesn't accept invoices if + * the payable amount is higher than 1000 GLM. + * Be careful when processing floating point numbers in JavaScript, as they can + * be imprecise. For this reason, we recommend using a library like decimal.js-light. + * `invoice.getPreciseAmount()` is a method that returns the amount as a Decimal object. + */ +const invoiceFilter: InvoiceFilter = async (invoice) => { + console.debug( + "Invoice %s for %s GLM is passing through the filter", + invoice.id, + invoice.getPreciseAmount().toFixed(6), + ); + return invoice.getPreciseAmount().lte(1000); +}; + +/* Let's create another sample filter. This time we will get the demand that + * the debit note is related to from the provided context and compare the payment platforms. + */ +const debitNoteFilter: DebitNoteFilter = async (debitNote, context) => { + console.debug( + "Debit Note %s for %s GLM is passing through the filter", + debitNote.id, + debitNote.getPreciseAmount().toFixed(6), + ); + return debitNote.paymentPlatform === context.demand.paymentPlatform; +}; + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, + // Here's where we specify the payment filters + payment: { + debitNoteFilter, + invoiceFilter, + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + const rental = await glm.oneOf({ order }); + await rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem! πŸ‘‹")) + .then((res) => console.log(res.stdout)); + await rental.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/advanced/proposal-filter.ts b/examples/advanced/proposal-filter.ts new file mode 100644 index 000000000..a9599ed59 --- /dev/null +++ b/examples/advanced/proposal-filter.ts @@ -0,0 +1,49 @@ +import { MarketOrderSpec, GolemNetwork, OfferProposalFilter } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +/** + * Example demonstrating how to write a custom offer proposal filter. + * In this case the offer proposal must include VPN access and must not be from "bad-provider" + */ +const myFilter: OfferProposalFilter = (proposal) => + Boolean( + proposal.provider.name !== "bad-provider" && proposal.properties["golem.runtime.capabilities"]?.includes("vpn"), + ); + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + offerProposalFilter: myFilter, + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + const rental = await glm.oneOf({ order }); + await rental + .getExeUnit() + .then((exe) => exe.run(`echo [provider:${exe.provider.name}] Hello, Golem! πŸ‘‹`)) + .then((res) => console.log(res.stdout)); + await rental.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/advanced/proposal-predefined-filter.ts b/examples/advanced/proposal-predefined-filter.ts new file mode 100644 index 000000000..cee05be92 --- /dev/null +++ b/examples/advanced/proposal-predefined-filter.ts @@ -0,0 +1,47 @@ +import { MarketOrderSpec, GolemNetwork, OfferProposalFilterFactory } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +/** + * Example showing how to use a offer proposal filter using the predefined filter `disallowProvidersByName`, + * which blocks any proposal from a provider whose name is in the array of + */ + +const blackListProvidersNames = ["provider-1", "bad-provider", "slow-provider"]; + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + offerProposalFilter: OfferProposalFilterFactory.disallowProvidersByName(blackListProvidersNames), + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + const rental = await glm.oneOf({ order }); + await rental + .getExeUnit() + .then((exe) => exe.run(`echo [provider:${exe.provider.name}] Hello, Golem! πŸ‘‹`)) + .then((res) => console.log(res.stdout)); + await rental.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/advanced/proposal-selector.ts b/examples/advanced/proposal-selector.ts new file mode 100644 index 000000000..fa818cebb --- /dev/null +++ b/examples/advanced/proposal-selector.ts @@ -0,0 +1,55 @@ +import { MarketOrderSpec, GolemNetwork, OfferProposal } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +/** + * Example demonstrating how to write a selector which choose the best provider based on scores provided as object: [providerName]: score + * A higher score rewards the provider. + */ +const scores = { + "provider-1": 100, + "golem-provider": 50, + "super-provider": 25, +}; + +const bestProviderSelector = (scores: { [providerName: string]: number }) => (proposals: OfferProposal[]) => { + proposals.sort((a, b) => ((scores?.[a.provider.name] || 0) >= (scores?.[b.provider.name] || 0) ? -1 : 1)); + return proposals[0]; +}; + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + offerProposalSelector: bestProviderSelector(scores), + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + const rental = await glm.oneOf({ order }); + await rental + .getExeUnit() + .then((exe) => exe.run(`echo [provider:${exe.provider.name}] Hello, Golem! πŸ‘‹`)) + .then((res) => console.log(res.stdout)); + await rental.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/advanced/reuse-allocation.ts b/examples/advanced/reuse-allocation.ts new file mode 100644 index 000000000..e2b212b18 --- /dev/null +++ b/examples/advanced/reuse-allocation.ts @@ -0,0 +1,84 @@ +/** + * This advanced example demonstrates create an allocation manually and then reuse + * it across multiple market orders. + */ +import { MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +(async () => { + const ALLOCATION_DURATION_HOURS = 1; + const RENTAL_DURATION_HOURS = 0.5; + + console.assert( + ALLOCATION_DURATION_HOURS > RENTAL_DURATION_HOURS, + "Always create allocations that will live longer than the planned rental duration", + ); + + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + + const allocation = await glm.payment.createAllocation({ + budget: 1, + expirationSec: ALLOCATION_DURATION_HOURS * 60 * 60, + }); + + const firstOrder: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: RENTAL_DURATION_HOURS, + pricing: { + model: "burn-rate", + avgGlmPerHour: 0.5, + }, + }, + payment: { + // You can either pass the allocation object ... + allocation, + }, + }; + const secondOrder: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: RENTAL_DURATION_HOURS, + pricing: { + model: "burn-rate", + avgGlmPerHour: 0.5, + }, + }, + payment: { + // ... or just the allocation ID + allocation: allocation.id, + }, + }; + + const rental1 = await glm.oneOf({ order: firstOrder }); + const rental2 = await glm.oneOf({ order: secondOrder }); + + await rental1 + .getExeUnit() + .then((exe) => exe.run("echo Running on first rental")) + .then((res) => console.log(res.stdout)); + await rental2 + .getExeUnit() + .then((exe) => exe.run("echo Running on second rental")) + .then((res) => console.log(res.stdout)); + + await rental1.stopAndFinalize(); + await rental2.stopAndFinalize(); + await glm.payment.releaseAllocation(allocation); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/advanced/scan.ts b/examples/advanced/scan.ts new file mode 100644 index 000000000..38eb645f8 --- /dev/null +++ b/examples/advanced/scan.ts @@ -0,0 +1,65 @@ +/** + * This example demonstrates how to scan the market for providers that meet specific requirements. + */ +import { GolemNetwork, ScanOptions } from "@golem-sdk/golem-js"; +import { last, map, scan, takeUntil, tap, timer } from "rxjs"; + +// What providers are we looking for? +const scanOptions: ScanOptions = { + // fairly powerful machine but not too powerful + workload: { + engine: "vm", + minCpuCores: 4, + maxCpuCores: 16, + minMemGib: 4, + maxMemGib: 8, + capabilities: ["vpn"], + minStorageGib: 16, + }, + // let's look at mainnet providers only + payment: { + network: "polygon", + }, +}; + +(async () => { + const glm = new GolemNetwork(); + await glm.connect(); + const spec = glm.market.buildScanSpecification(scanOptions); + + // For advanced users: you can also add constraints manually: + // spec.constraints.push("(golem.node.id.name=my-favorite-provider)"); + + const SCAN_DURATION_MS = 10_000; + + console.log(`Scanning for ${SCAN_DURATION_MS / 1000} seconds...`); + glm.market + .scan(spec) + .pipe( + tap((scannedOffer) => { + console.log("Found offer from", scannedOffer.provider.name); + }), + // calculate the cost of an hour of work + map( + (scannedOffer) => + scannedOffer.pricing.start + // + scannedOffer.pricing.cpuSec * 3600 + + scannedOffer.pricing.envSec * 3600, + ), + // calculate the running average + scan((total, cost) => total + cost, 0), + map((totalCost, index) => totalCost / (index + 1)), + // stop scanning after SCAN_DURATION_MS + takeUntil(timer(SCAN_DURATION_MS)), + last(), + ) + .subscribe({ + next: (averageCost) => { + console.log("Average cost for an hour of work:", averageCost.toFixed(6), "GLM"); + }, + complete: () => { + console.log("Scan completed, shutting down..."); + glm.disconnect(); + }, + }); +})(); diff --git a/examples/advanced/setup-and-teardown.ts b/examples/advanced/setup-and-teardown.ts new file mode 100644 index 000000000..ae708dd4c --- /dev/null +++ b/examples/advanced/setup-and-teardown.ts @@ -0,0 +1,101 @@ +/** + * This advanced example shows how to use the setup and teardown lifecycle functions + * to avoid doing the same work multiple times when running multiple tasks on the same provider. + */ +import { MarketOrderSpec, GolemNetwork, LifecycleFunction } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +const order: MarketOrderSpec = { + demand: { + workload: { + imageTag: "golem/examples-openssl:latest", + }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +/** + * Let's decrypt these messages in parallel on multiple providers. + * We know that the messages were encrypted using the aes-256-cbc algorithm + * with 5000000 iterations and the password "golemnetwork". + */ +const secretMessages = ` +U2FsdGVkX18S4g4iyUzjfqIwP/cGppD86mrVQ0kYerfSe3OnGORuU4nYLG6WXy5KG6vLVLq1N/jnyrwkT8yEBA== +U2FsdGVkX1+jEgVgmS+xu37tT5OHieX8cioZHPyjUTh+YodWf0In3DaqtFcEfw2cLHIBd94s4nEmONHCs9x4Rg== +U2FsdGVkX1+JU+fBsnKEGZHGoQpEY/DqlnbCQVg+KgLtkFbtjuHpQbMjnb7iBuj4o4yIYU00jM67+gqn89hrNA== +U2FsdGVkX1897oplQ7utV9zpx/86GABjUP29Xr/GsahKQ9eRv9GgnzW9BCqHKiFjiB2q2F6gCJINspbuiFY+Fg== +U2FsdGVkX183p8EUPOZj/QZFQSeIeYNSSfHcRrBF0NXSJ4RfibvT5HtJJ/5I9fVpc1XpbLwDsCW2yFdQKSzbXA== +U2FsdGVkX184PQiKxx8Sfvl+BOy9JYrBQqdMxWEDc3GBDkEb3qYCWL7FPxZpCEwoZ10MpvY1EKb4lMMxWth6bw== +U2FsdGVkX1/ujngC/IwK8UAvj41t/FbSHVFiiXI7+KeHoKW3HKcwZYb0E+nncpPC6ZT0DgWLzvDaUyBqOS+tkA== +U2FsdGVkX19GNHf4ORUAy2PC3MMnvjx7aZSRNSqkW20fZ03Dc2OnZEWBDPa1J4yx +U2FsdGVkX18iL21PNCohSdFuIOufknLXmINnYf3q15Fl+1vFcRnmC8b9zcrob5Iz/9dNkvrgAeNFmAwWK0bwPw== +U2FsdGVkX18jQbGQ7KTAaRask5efrXEWvvGhe4jQ0MT9mwwH4ULjvoWDm1mNlsjYtb1nRt0O6iBd4O9moHLbbg== +`.trim(); + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + // for this example we disable info logs to better show the output + level: "warn", + }), + }); + + try { + await glm.connect(); + + // I want to upload my encrypted messages to the provider before I start working + const setup: LifecycleFunction = async (exe) => + exe + .run(`echo "${secretMessages}" > /golem/work/encrypted-messages.txt`) + .then(() => console.log("Uploaded the encrypted messages to the provider %s", exe.provider.name)); + + // I want to remove the encrypted messages from the provider after I finish working + const teardown: LifecycleFunction = async (exe) => + exe + .run("rm /golem/work/encrypted-messages.txt") + .then(() => console.log("Removed the encrypted messages from the provider %s", exe.provider.name)); + + const pool = await glm.manyOf({ + poolSize: { max: 3 }, // I want to decrypt in parallel on a maximum of 3 machines simultaneously + order, + setup, + teardown, + }); + + // map each message to a decryption task + const decryptionTasks = new Array(10).fill(null).map((_, i) => + pool.withRental(async (rental) => { + const exe = await rental.getExeUnit(); + const result = await exe.run( + `sed '${i + 1}!d' /golem/work/encrypted-messages.txt | \ + openssl \ + enc -aes-256-cbc -d -a \ + -pass pass:golemnetwork \ + -iter 5000000`, + ); + console.log("Finished decrypting message #%s on the provider %s", i + 1, exe.provider.name); + return String(result.stdout).trim(); + }), + ); + const decryptedMessages = await Promise.all(decryptionTasks); + await pool.drainAndClear(); + + console.log("Decrypted messages:"); + for (const message of decryptedMessages) { + // display the decrypted messages in light-blue color + console.log("\x1b[36m%s\x1b[0m", message); + } + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/advanced/step-by-step.ts b/examples/advanced/step-by-step.ts new file mode 100644 index 000000000..5ec4e30a9 --- /dev/null +++ b/examples/advanced/step-by-step.ts @@ -0,0 +1,176 @@ +/** + * This advanced example demonstrates how to perform all market interactions "manually". + * It should give you a basic understanding of how this SDK works under the hood. + * If you're just getting started with golem-js, take a look at the basic examples first. + * Keep in mind that this is not the recommended way to interact with the Golem Network, as + * it doesn't cover all edge cases and error handling. This example should be used for educational purposes only. + */ +import { + Allocation, + GolemNetwork, + MarketOrderSpec, + OfferProposal, + OfferProposalReceivedEvent, +} from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; +import { filter, map, switchMap, take } from "rxjs"; + +(async () => { + const logger = pinoPrettyLogger({ + level: "info", + }); + + const glm = new GolemNetwork({ + logger, + }); + + const RENTAL_DURATION_HOURS = 10 / 60; + const ALLOCATION_DURATION_HOURS = RENTAL_DURATION_HOURS + 0.25; + + console.assert( + ALLOCATION_DURATION_HOURS > RENTAL_DURATION_HOURS, + "Always create allocations that will live longer than the planned rental duration", + ); + + let allocation: Allocation | undefined; + try { + await glm.connect(); + + // Define the order that we're going to place on the market + + const order: MarketOrderSpec = { + demand: { + workload: { + imageTag: "golem/alpine:latest", + minCpuCores: 1, + minMemGib: 2, + }, + }, + market: { + // We're only going to rent the provider for 5 minutes max + rentHours: RENTAL_DURATION_HOURS, + pricing: { + model: "linear", + maxStartPrice: 1, + maxCpuPerHourPrice: 1, + maxEnvPerHourPrice: 1, + }, + }, + }; + // Allocate funds to cover the order, we will only pay for the actual usage + // so any unused funds will be returned to us at the end + + allocation = await glm.payment.createAllocation({ + budget: glm.market.estimateBudget({ order, maxAgreements: 1 }), + expirationSec: ALLOCATION_DURATION_HOURS * 60 * 60, + }); + + // Convert the human-readable order to a protocol-level format that we will publish on the network + const demandSpecification = await glm.market.buildDemandDetails(order.demand, order.market, allocation); + + // Publish the order on the market + // This methods creates and observable that publishes the order and refreshes it every 30 minutes. + // Unsubscribing from the observable will remove the order from the market + const demand$ = glm.market.publishAndRefreshDemand(demandSpecification); + + // Now, for each created demand, let's listen to proposals from providers + const offerProposal$ = demand$.pipe( + switchMap((demand) => glm.market.collectMarketProposalEvents(demand)), + // to keep things simple we don't care about any other events + // related to this demand, only proposals from providers + filter((event): event is OfferProposalReceivedEvent => event.type === "ProposalReceived"), + map((event) => event.proposal), + ); + + // Each received proposal can be in one of two states: initial or draft + // Initial proposals are the first ones received from providers and require us to respond with a counter-offer + // Draft proposals are the ones that we have already negotiated and are ready to be accepted + // Both types come in the same stream, so let's write a handler that will respond to initial proposals + // and save draft proposals for later + const draftProposals: OfferProposal[] = []; + const offerProposalsSubscription = offerProposal$.subscribe((offerProposal) => { + if (offerProposal.isInitial()) { + // here we can define our own counter-offer + // to keep this example simple, we will respond with the same + // specification as the one we used to publish the demand + // feel free to modify this to your needs + glm.market.negotiateProposal(offerProposal, demandSpecification).catch(console.error); + } else if (offerProposal.isDraft()) { + draftProposals.push(offerProposal); + } + }); + + // Let's wait for a couple seconds to receive some proposals + while (draftProposals.length < 1) { + console.log("Waiting for proposals..."); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + // We have received at least one draft proposal, we can now stop listening for more + offerProposalsSubscription.unsubscribe(); + + // Remember that signing the proposal can fail, so in a production environment + // you should handle the error and retry with a different proposal. + // To keep this example simple, we will not retry and just crash if the signing fails + const draftProposal = draftProposals[0]!; + const agreement = await glm.market.proposeAgreement(draftProposal); + console.log("Agreement signed with provider", agreement.provider.name); + + // Provider is ready to start the computation + // Let's setup payment first + // As the computation happens, we will receive debit notes to inform us about the cost + // and an invoice at the end to settle the payment + const invoiceSubscription = glm.payment + .observeInvoices() + .pipe( + // make sure we only process invoices related to our agreement + filter((invoice) => invoice.agreementId === agreement.id), + // end the stream after we receive an invoice + take(1), + ) + .subscribe((invoice) => { + console.log("Received invoice for ", invoice.getPreciseAmount().toFixed(4), "GLM"); + glm.payment.acceptInvoice(invoice, allocation!, invoice.amount).catch(console.error); + }); + const debitNoteSubscription = glm.payment + .observeDebitNotes() + .pipe( + // make sure we only process invoices related to our agreement + filter((debitNote) => debitNote.agreementId === agreement.id), + ) + .subscribe((debitNote) => { + console.log("Received debit note for ", debitNote.getPreciseAmount().toFixed(4), "GLM"); + glm.payment.acceptDebitNote(debitNote, allocation!, debitNote.totalAmountDue).catch(console.error); + }); + + // Start the computation + // First lets start the activity - this will deploy our image on the provider's machine + const activity = await glm.activity.createActivity(agreement); + // Then let's create a ExeUnit, which is a set of utilities to interact with the + // providers machine, like running commands, uploading files, etc. + const exe = await glm.activity.createExeUnit(activity); + // Now we can run a simple command on the provider's machine + const result = await exe.run("echo Hello, Golem πŸ‘‹!"); + console.log("Result of the command ran on the provider's machine:", result.stdout); + + // We're done, let's clean up + // First we need to destroy the activity + await glm.activity.destroyActivity(activity); + // Then let's terminate the agreement + await glm.market.terminateAgreement(agreement); + // Before we finish, let's wait for the invoice to be settled + while (!invoiceSubscription.closed) { + console.log("Waiting for the invoice to be settled..."); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + // We're done! Let's cleanup the subscriptions, release the remaining funds and disconnect from the network + invoiceSubscription.unsubscribe(); + debitNoteSubscription.unsubscribe(); + } catch (err) { + console.error("Failed to run example on Golem", err); + } finally { + if (allocation) { + await glm.payment.releaseAllocation(allocation); + } + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/basic/events.ts b/examples/basic/events.ts new file mode 100644 index 000000000..9c6462d35 --- /dev/null +++ b/examples/basic/events.ts @@ -0,0 +1,66 @@ +/** + * This example showcases how users can listen to various events exposed from golem-js + */ +import { GolemNetwork } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + payment: { + driver: "erc20", + network: "holesky", + }, + }); + + try { + await glm.connect(); + + glm.market.events.on("agreementApproved", (event) => { + console.log("Agreement '%s' approved", event.agreement.id); + }); + + glm.market.events.on("agreementTerminated", (event) => { + console.log( + "Agreement '%s' terminated by '%s' with reason '%s'", + event.agreement.id, + event.terminatedBy, + event.reason, + ); + }); + + glm.market.events.on("offerCounterProposalRejected", (event) => { + console.warn("Proposal rejected by provider", event); + }); + + const rental = await glm.oneOf({ + order: { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, + }, + }); + + await rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem! πŸ‘‹")) + .then((res) => console.log(res.stdout)); + + await rental.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/basic/many-of.ts b/examples/basic/many-of.ts new file mode 100644 index 000000000..7f381eb33 --- /dev/null +++ b/examples/basic/many-of.ts @@ -0,0 +1,62 @@ +/** + * This example demonstrates how easily rent multiple machines at once. + */ + +import { GolemNetwork, MarketOrderSpec } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + // create a pool that can grow up to 3 rentals at the same time + const pool = await glm.manyOf({ + poolSize: 3, + order, + }); + await Promise.allSettled([ + pool.withRental(async (rental) => + rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem from the first machine! πŸ‘‹")) + .then((res) => console.log(res.stdout)), + ), + pool.withRental(async (rental) => + rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem from the second machine! πŸ‘‹")) + .then((res) => console.log(res.stdout)), + ), + pool.withRental(async (rental) => + rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem from the third machine! πŸ‘‹")) + .then((res) => console.log(res.stdout)), + ), + ]); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/basic/one-of.ts b/examples/basic/one-of.ts new file mode 100644 index 000000000..214145c3b --- /dev/null +++ b/examples/basic/one-of.ts @@ -0,0 +1,39 @@ +import { MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + const rental = await glm.oneOf({ order }); + await rental + .getExeUnit() + .then((exe) => exe.run("echo Hello, Golem! πŸ‘‹")) + .then((res) => console.log(res.stdout)); + await rental.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/basic/run-and-stream.ts b/examples/basic/run-and-stream.ts new file mode 100644 index 000000000..a573c9643 --- /dev/null +++ b/examples/basic/run-and-stream.ts @@ -0,0 +1,59 @@ +import { MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +/** + * Example demonstrating the execution of a command on a provider which may take a long time + * and returns the results from the execution during the command as a stream + */ + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + const rental = await glm.oneOf({ order }); + const exe = await rental.getExeUnit(); + + const remoteProcess = await exe.runAndStream( + ` + sleep 1 + echo -n 'Hello from stdout' >&1 + echo -n 'Hello from stderr' >&2 + sleep 1 + echo -n 'Hello from stdout again' >&1 + echo -n 'Hello from stderr again' >&2 + sleep 1 + echo -n 'Hello from stdout yet again' >&1 + echo -n 'Hello from stderr yet again' >&2 + `, + ); + remoteProcess.stdout.subscribe((data) => console.log("stdout>", data)); + remoteProcess.stderr.subscribe((data) => console.error("stderr>", data)); + await remoteProcess.waitForExit(); + + await rental.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/basic/transfer.ts b/examples/basic/transfer.ts new file mode 100644 index 000000000..4874c4f0a --- /dev/null +++ b/examples/basic/transfer.ts @@ -0,0 +1,66 @@ +import { MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; +import { appendFile, readFile, unlink } from "fs/promises"; + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + const pool = await glm.manyOf({ + poolSize: 2, + order, + }); + const rental1 = await pool.acquire(); + const rental2 = await pool.acquire(); + + const exe1 = await rental1.getExeUnit(); + const exe2 = await rental2.getExeUnit(); + + await exe1 + .beginBatch() + .run(`echo "Message from provider ${exe1.provider.name}. Hello 😻" >> /golem/work/message.txt`) + .downloadFile("/golem/work/message.txt", "./message.txt") + .end(); + + await appendFile("./message.txt", "Message from requestor. Hello 🀠\n"); + + await exe2 + .beginBatch() + .uploadFile("./message.txt", "/golem/work/message.txt") + .run(`echo "Message from provider ${exe2.provider.name}. Hello πŸ‘»" >> /golem/work/message.txt`) + .downloadFile("/golem/work/message.txt", "./results.txt") + .end(); + + console.log("File content: "); + console.log(await readFile("./results.txt", { encoding: "utf-8" })); + + await rental1.stopAndFinalize(); + await rental2.stopAndFinalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + await unlink("./message.txt"); + await unlink("./results.txt"); + } +})().catch(console.error); diff --git a/examples/basic/vpn.ts b/examples/basic/vpn.ts new file mode 100644 index 000000000..c93351d10 --- /dev/null +++ b/examples/basic/vpn.ts @@ -0,0 +1,53 @@ +import { MarketOrderSpec, GolemNetwork } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + const network = await glm.createNetwork({ ip: "192.168.7.0/24" }); + const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, + network, + }; + // create a pool that can grow up to 2 rentals at the same time + const pool = await glm.manyOf({ + poolSize: 2, + order, + }); + const rental1 = await pool.acquire(); + const rental2 = await pool.acquire(); + const exe1 = await rental1.getExeUnit(); + const exe2 = await rental2.getExeUnit(); + await exe1 + .run(`ping ${exe2.getIp()} -c 4`) + .then((res) => console.log(`Response from provider: ${exe1.provider.name} (ip: ${exe1.getIp()})`, res.stdout)); + await exe2 + .run(`ping ${exe1.getIp()} -c 4`) + .then((res) => console.log(`Response from provider: ${exe2.provider.name} (ip: ${exe2.getIp()})`, res.stdout)); + await pool.destroy(rental1); + await pool.destroy(rental2); + + await glm.destroyNetwork(network); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/blender/blender.Dockerfile b/examples/blender/blender.Dockerfile deleted file mode 100644 index 5cfae061f..000000000 --- a/examples/blender/blender.Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM golemfactory/blender:1.13 -COPY run-blender.sh /golem/entrypoints/ -VOLUME /golem/work /golem/output /golem/resource diff --git a/examples/blender/blender.ts b/examples/blender/blender.ts deleted file mode 100644 index eecee706b..000000000 --- a/examples/blender/blender.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -import { program } from "commander"; -import { fileURLToPath } from "url"; - -const DIR_NAME = fileURLToPath(new URL(".", import.meta.url)); - -const blenderParams = (frame) => ({ - scene_file: "/golem/resource/scene.blend", - resolution: [400, 300], - use_compositing: false, - crops: [ - { - outfilebasename: "out", - borders_x: [0.0, 1.0], - borders_y: [0.0, 1.0], - }, - ], - samples: 100, - frames: [frame], - output_format: "PNG", - RESOURCES_DIR: "/golem/resources", - WORK_DIR: "/golem/work", - OUTPUT_DIR: "/golem/output", -}); - -async function main(subnetTag: string, driver?: string, network?: string, maxParallelTasks?: number) { - const executor = await TaskExecutor.create({ - subnetTag, - payment: { driver, network }, - package: "golem/blender:latest", - maxParallelTasks, - }); - - try { - executor.onActivityReady(async (ctx) => { - console.log("Uploading the scene to the provider %s", ctx.provider.name); - await ctx.uploadFile(`${DIR_NAME}/cubes.blend`, "/golem/resource/scene.blend"); - console.log("Upload of the scene to the provider %s finished", ctx.provider.name); - }); - - const futureResults = [0, 10, 20, 30, 40, 50].map(async (frame) => - executor.run(async (ctx) => { - console.log("Started rendering of frame %d on provider %s", frame, ctx.provider.name); - - const result = await ctx - .beginBatch() - .uploadJson(blenderParams(frame), "/golem/work/params.json") - .run("/golem/entrypoints/run-blender.sh") - .downloadFile(`/golem/output/out${frame?.toString().padStart(4, "0")}.png`, `${DIR_NAME}/output_${frame}.png`) - .end(); - - console.log("Finished rendering of frame %d on provider %s", frame, ctx.provider.name); - - return result?.length ? `output_${frame}.png` : ""; - }), - ); - - console.log("Scheduling all tasks"); - const results = await Promise.all(futureResults); - console.log("Completed all tasks"); - - results.forEach((result) => console.log(result)); - } catch (error) { - console.error("Computation failed:", error); - } finally { - await executor.shutdown(); - } -} - -program - .option("--subnet-tag ", "set subnet name, for example 'public'") - .option("--payment-driver, --driver ", "payment driver name, for example 'erc20'") - .option("--payment-network, --network ", "network name, for example 'holesky'") - .option("-t, --max-parallel-tasks ", "max parallel tasks"); -program.parse(); -const options = program.opts(); -main(options.subnetTag, options.driver, options.network, options.maxParallelTasks); diff --git a/examples/blender/run-blender.sh b/examples/blender/run-blender.sh deleted file mode 100755 index e7a6d31a5..000000000 --- a/examples/blender/run-blender.sh +++ /dev/null @@ -1,4 +0,0 @@ -#! /bin/bash - -cd /golem/work -python3 /golem/entrypoints/render_entrypoint.py diff --git a/examples/build-golem-images.sh b/examples/build-golem-images.sh deleted file mode 100755 index 4d8e710aa..000000000 --- a/examples/build-golem-images.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -echo "Building golem/alpine:latest" -docker build -t golem/alpine:latest -f hello-world/Dockerfile . -gvmkit-build --push golem/alpine:latest - -echo "Building golem/imagemagick:latest" -docker build -t golem/imagemagick:latest -f web/imagemagick.Dockerfile . -gvmkit-build --push golem/imagemagick:latest - -echo "Building golem/examples-ssh:latest" -docker build -t golem/examples-ssh:latest -f ssh/Dockerfile . -gvmkit-build --push golem/examples-ssh:latest - -echo "Building golem/examples-hashcat:legacy" -docker build -t golem/examples-hashcat:legacy -f yacat/yacat.Dockerfile . -gvmkit-build --push golem/examples-hashcat:legacy \ No newline at end of file diff --git a/examples/docs-examples/examples/composing-tasks/alert-code.mjs b/examples/docs-examples/examples/composing-tasks/alert-code.mjs deleted file mode 100644 index 4644524e6..000000000 --- a/examples/docs-examples/examples/composing-tasks/alert-code.mjs +++ /dev/null @@ -1,27 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => { - const res = await ctx - .beginBatch() - .uploadFile("./worker.mjs", "/golem/input/worker.mjs") - .run("node /golem/input/worker.mjs > /golem/input/output.txt") - .run("cat /golem/input/output.txt") - .downloadFile("/golem/input/output.txt", "./output.txt") - .endStream(); - - for await (const chunk of res) { - if (chunk.index === 2) console.log(chunk.stdout); - } - }); - } catch (err) { - console.error("Task encountered an error:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/composing-tasks/batch-end.mjs b/examples/docs-examples/examples/composing-tasks/batch-end.mjs deleted file mode 100644 index 33163c996..000000000 --- a/examples/docs-examples/examples/composing-tasks/batch-end.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => { - return ( - await ctx - .beginBatch() - .uploadFile("./worker.mjs", "/golem/input/worker.mjs") - .run("node /golem/input/worker.mjs > /golem/input/output.txt") - .run("cat /golem/input/output.txt") - .downloadFile("/golem/input/output.txt", "./output.txt") - .end() - )[2]?.stdout; - }); - - console.log(result); - } catch (error) { - console.error("Computation failed:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/composing-tasks/batch-endstream-chunks.mjs b/examples/docs-examples/examples/composing-tasks/batch-endstream-chunks.mjs deleted file mode 100644 index 9710a0591..000000000 --- a/examples/docs-examples/examples/composing-tasks/batch-endstream-chunks.mjs +++ /dev/null @@ -1,30 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => { - const res = await ctx - .beginBatch() - .uploadFile("./worker.mjs", "/golem/input/worker.mjs") - .run("node /golem/input/worker.mjs > /golem/input/output.txt") - .run("cat /golem/input/output.txt") - .downloadFile("/golem/input/output.txt", "./output.txt") - .endStream(); - - return new Promise((resolve) => { - res.on("data", (result) => console.log(result)); - res.on("error", (error) => console.error(error)); - res.once("close", resolve); - }); - }); - } catch (error) { - console.error("Computation failed:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/composing-tasks/batch-endstream-forawait.mjs b/examples/docs-examples/examples/composing-tasks/batch-endstream-forawait.mjs deleted file mode 100644 index 1af23f9e5..000000000 --- a/examples/docs-examples/examples/composing-tasks/batch-endstream-forawait.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => { - const res = await ctx - .beginBatch() - .uploadFile("./worker.mjs", "/golem/input/worker.mjs") - .run("node /golem/input/worker.mjs > /golem/input/output.txt") - .run("cat /golem/input/output.txt") - .downloadFile("/golem/input/output.txt", "./output.txt") - .endStream(); - - for await (const chunk of res) { - console.log(chunk.stdout); - } - }); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/composing-tasks/multiple-run-prosaic.mjs b/examples/docs-examples/examples/composing-tasks/multiple-run-prosaic.mjs deleted file mode 100644 index aac9223db..000000000 --- a/examples/docs-examples/examples/composing-tasks/multiple-run-prosaic.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => { - await ctx.uploadFile("./worker.mjs", "/golem/input/worker.mjs"); - await ctx.run("node /golem/input/worker.mjs > /golem/input/output.txt"); - const result = await ctx.run("cat /golem/input/output.txt"); - await ctx.downloadFile("/golem/input/output.txt", "./output.txt"); - return result.stdout; - }); - - console.log(result); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/composing-tasks/single-command.cjs b/examples/docs-examples/examples/composing-tasks/single-command.cjs deleted file mode 100644 index ac3027917..000000000 --- a/examples/docs-examples/examples/composing-tasks/single-command.cjs +++ /dev/null @@ -1,17 +0,0 @@ -const { TaskExecutor } = require("@golem-sdk/golem-js"); - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => (await ctx.run("node -v")).stdout); - console.log("Task result:", result); - } catch (err) { - console.error("Task failed:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/composing-tasks/single-command.mjs b/examples/docs-examples/examples/composing-tasks/single-command.mjs deleted file mode 100644 index 71bc40d89..000000000 --- a/examples/docs-examples/examples/composing-tasks/single-command.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => (await ctx.run("node -v")).stdout); - console.log("Task result:", result); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/composing-tasks/single-command.ts b/examples/docs-examples/examples/composing-tasks/single-command.ts deleted file mode 100644 index a6614526d..000000000 --- a/examples/docs-examples/examples/composing-tasks/single-command.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => (await ctx.run("node -v")).stdout); - console.log("Task result:", result); - } catch (err) { - console.error("Error while running the task:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/composing-tasks/worker.mjs b/examples/docs-examples/examples/composing-tasks/worker.mjs deleted file mode 100644 index accefceba..000000000 --- a/examples/docs-examples/examples/composing-tasks/worker.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log("Hello World"); diff --git a/examples/docs-examples/examples/executing-tasks/action_log.txt b/examples/docs-examples/examples/executing-tasks/action_log.txt deleted file mode 100644 index 170536dc7..000000000 --- a/examples/docs-examples/examples/executing-tasks/action_log.txt +++ /dev/null @@ -1 +0,0 @@ -some action log diff --git a/examples/docs-examples/examples/executing-tasks/foreach.mjs b/examples/docs-examples/examples/executing-tasks/foreach.mjs deleted file mode 100644 index 98f17f2ec..000000000 --- a/examples/docs-examples/examples/executing-tasks/foreach.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const data = [1, 2, 3, 4, 5]; - - for (const item of data) { - await executor.run(async (ctx) => { - console.log((await ctx.run(`echo "${item}"`)).stdout); - }); - } - } catch (err) { - console.error("An error occurred during execution", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/executing-tasks/map.mjs b/examples/docs-examples/examples/executing-tasks/map.mjs deleted file mode 100644 index 807e773b2..000000000 --- a/examples/docs-examples/examples/executing-tasks/map.mjs +++ /dev/null @@ -1,25 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const data = [1, 2, 3, 4, 5]; - - const futureResults = data.map(async (item) => - executor.run(async (ctx) => { - return await ctx.run(`echo "${item}"`); - }), - ); - - const results = await Promise.all(futureResults); - results.forEach((result) => console.log(result.stdout)); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/executing-tasks/max-parallel-tasks.mjs b/examples/docs-examples/examples/executing-tasks/max-parallel-tasks.mjs deleted file mode 100644 index 52e26bba7..000000000 --- a/examples/docs-examples/examples/executing-tasks/max-parallel-tasks.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - maxParallelTasks: 3, - }); - - try { - const data = [1, 2, 3, 4, 5]; - const futureResults = data.map((item) => executor.run((ctx) => ctx.run(`echo "${item}"`))); - const results = await Promise.all(futureResults); - results.forEach((result) => console.log(result.stdout)); - } catch (err) { - console.error("Error occurred during task execution:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/executing-tasks/on-activity-ready.mjs b/examples/docs-examples/examples/executing-tasks/on-activity-ready.mjs deleted file mode 100644 index 6d8684d48..000000000 --- a/examples/docs-examples/examples/executing-tasks/on-activity-ready.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - maxParallelTasks: 3, - }); - - executor.onActivityReady(async (ctx) => { - console.log(ctx.provider.name + " is downloading action_log file"); - await ctx.uploadFile("./action_log.txt", "/golem/input/action_log.txt"); - }); - - const inputs = [1, 2, 3, 4, 5]; - - try { - const futureResults = inputs.map(async (item) => { - return await executor.run(async (ctx) => { - await ctx - .beginBatch() - .run(`echo 'processing item: ${item}' >> /golem/input/action_log.txt`) - .downloadFile("/golem/input/action_log.txt", `./output_${ctx.provider.name}.txt`) - .end(); - }); - }); - await Promise.all(futureResults); - } catch (error) { - console.error("A critical error occurred:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/executing-tasks/single-run.mjs b/examples/docs-examples/examples/executing-tasks/single-run.mjs deleted file mode 100644 index 9333d7ee0..000000000 --- a/examples/docs-examples/examples/executing-tasks/single-run.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => (await ctx.run("node -v")).stdout); - console.log("Task result:", result); - } catch (err) { - console.error("Error during task execution:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/selecting-providers/custom-price.mjs b/examples/docs-examples/examples/selecting-providers/custom-price.mjs deleted file mode 100644 index 0598755dd..000000000 --- a/examples/docs-examples/examples/selecting-providers/custom-price.mjs +++ /dev/null @@ -1,47 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -/** - * Example demonstrating how to write a custom proposal filter. - */ - -var costData = []; - -const myFilter = async (proposal) => { - let decision = false; - let usageVector = proposal.properties["golem.com.usage.vector"]; - let counterIdx = usageVector.findIndex((ele) => ele === "golem.usage.duration_sec"); - let proposedCost = proposal.properties["golem.com.pricing.model.linear.coeffs"][counterIdx]; - costData.push(proposedCost); - if (costData.length < 6) return false; - else { - costData.shift(); - let averageProposedCost = costData.reduce((part, x) => part + x, 0) / 5; - if (proposedCost <= 1.2 * averageProposedCost) decision = true; - if (decision) { - console.log(proposedCost, averageProposedCost); - } - } - console.log(costData); - console.log(proposal.properties["golem.node.id.name"], proposal.properties["golem.com.pricing.model.linear.coeffs"]); - return decision; -}; - -(async function main() { - const executor = await TaskExecutor.create({ - package: "9a3b5d67b0b27746283cb5f287c13eab1beaa12d92a9f536b747c7ae", - proposalFilter: myFilter, - yagnaOptions: { apiKey: "try_golem" }, - startupTimeout: 60_000, - }); - - try { - await executor.run(async (ctx) => { - const result = await ctx.run('echo "This task is run on ${ctx.provider.id}"'); - console.log(result.stdout, ctx.provider.id); - }); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/selecting-providers/demand.mjs b/examples/docs-examples/examples/selecting-providers/demand.mjs deleted file mode 100644 index cb14caad5..000000000 --- a/examples/docs-examples/examples/selecting-providers/demand.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async function main() { - const executor = await TaskExecutor.create({ - package: "9a3b5d67b0b27746283cb5f287c13eab1beaa12d92a9f536b747c7ae", - //minCpuCores : 2, - //minMemGib : 8, - //minStorageGib: 10, - minCpuThreads: 1, - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/selecting-providers/whitelist.mjs b/examples/docs-examples/examples/selecting-providers/whitelist.mjs deleted file mode 100644 index 0d4621c7b..000000000 --- a/examples/docs-examples/examples/selecting-providers/whitelist.mjs +++ /dev/null @@ -1,30 +0,0 @@ -import { TaskExecutor, ProposalFilterFactory } from "@golem-sdk/golem-js"; - -/** - * Example demonstrating how to use the predefined filter `allowProvidersByName`, - * which only allows offers from a provider whose name is in the array - */ - -const whiteListNames = ["provider-2", "fractal_01_3.h", "sharkoon_379_0.h", "fractal_01_1.h", "sharkoon_379_1.h"]; -console.log("Will accept only proposals from:"); -for (let i = 0; i < whiteListNames.length; i++) { - console.log(whiteListNames[i]); -} - -(async function main() { - const executor = await TaskExecutor.create({ - package: "9a3b5d67b0b27746283cb5f287c13eab1beaa12d92a9f536b747c7ae", - proposalFilter: ProposalFilterFactory.allowProvidersByName(whiteListNames), - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - await executor.run(async (ctx) => - console.log((await ctx.run(`echo "This task is run on ${ctx.provider.name}"`)).stdout), - ); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/sending-data/downloading-file.mjs b/examples/docs-examples/examples/sending-data/downloading-file.mjs deleted file mode 100644 index 49a216b36..000000000 --- a/examples/docs-examples/examples/sending-data/downloading-file.mjs +++ /dev/null @@ -1,27 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "dcd99a5904bebf7ca655a833b73cc42b67fd40b4a111572e3d2007c3", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => { - const res = await ctx - .beginBatch() - .run("ls -l /golem > /golem/work/output.txt") - .run("cat /golem/work/output.txt") - .downloadFile("/golem/work/output.txt", "./output.txt") - .end(); - - return res[2]?.stdout; - }); - - console.log(result); - } catch (error) { - console.error("An error occurred:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/sending-data/uploading-file.mjs b/examples/docs-examples/examples/sending-data/uploading-file.mjs deleted file mode 100644 index f6be2bd3e..000000000 --- a/examples/docs-examples/examples/sending-data/uploading-file.mjs +++ /dev/null @@ -1,32 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -import { createHash } from "node:crypto"; -import * as fs from "fs"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const buff = fs.readFileSync("worker.mjs"); - const hash = createHash("md5").update(buff).digest("hex"); - - const result = await executor.run(async (ctx) => { - await ctx.uploadFile("./worker.mjs", "/golem/input/worker.mjs"); - - const res = await ctx.run( - `node -e "const crypto = require('node:crypto'); const fs = require('fs'); const buff = fs.readFileSync('/golem/input/worker.mjs'); const hash = crypto.createHash('md5').update(buff).digest('hex'); console.log(hash); "`, - ); - - return res.stdout; - }); - - console.log("md5 of the file sent to provider: ", result); - console.log("Locally computed md5: ", hash); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/sending-data/uploading-json.mjs b/examples/docs-examples/examples/sending-data/uploading-json.mjs deleted file mode 100644 index 92f6a946c..000000000 --- a/examples/docs-examples/examples/sending-data/uploading-json.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "dcd99a5904bebf7ca655a833b73cc42b67fd40b4a111572e3d2007c3", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const output = await executor.run(async (ctx) => { - // Upload test JSON object - await ctx.uploadJson({ input: "Hello World" }, "/golem/work/input.json"); - // Read the content of the JSON object. - return await ctx.run("cat /golem/work/input.json"); - }); - - console.log(output.stdout); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/sending-data/worker.mjs b/examples/docs-examples/examples/sending-data/worker.mjs deleted file mode 100644 index accefceba..000000000 --- a/examples/docs-examples/examples/sending-data/worker.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log("Hello World"); diff --git a/examples/docs-examples/examples/switching-to-mainnet/run-on-polygon.mjs b/examples/docs-examples/examples/switching-to-mainnet/run-on-polygon.mjs deleted file mode 100644 index 111772904..000000000 --- a/examples/docs-examples/examples/switching-to-mainnet/run-on-polygon.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - payment: { network: "polygon" }, - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => (await ctx.run("node -v")).stdout); - console.log("Task result:", result); - } catch (err) { - console.error("Computation failed:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/transferring-data/download-file.mjs b/examples/docs-examples/examples/transferring-data/download-file.mjs deleted file mode 100644 index c050d6220..000000000 --- a/examples/docs-examples/examples/transferring-data/download-file.mjs +++ /dev/null @@ -1,27 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "dcd99a5904bebf7ca655a833b73cc42b67fd40b4a111572e3d2007c3", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => { - const res = await ctx - .beginBatch() - .run("ls -l /golem > /golem/work/output.txt") - .run("cat /golem/work/output.txt") - .downloadFile("/golem/work/output.txt", "./output.txt") - .end(); - - return res[2]?.stdout; - }); - - console.log(result); - } catch (error) { - console.error(error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/transferring-data/transfer-data-in-browser.html b/examples/docs-examples/examples/transferring-data/transfer-data-in-browser.html deleted file mode 100644 index 0de983ded..000000000 --- a/examples/docs-examples/examples/transferring-data/transfer-data-in-browser.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - WebRequestor Task API - - -

WebRequestor - Meme Example

-
-
-

Options

-
-
- - -
-
- - -
-
-

Input data

-
-
- - -
-
- - -
-
-

Actions

-
-
- -
-
-
-

Result Meme

- -
-
-
-
-

Logs

-
    -
    -
    -
    - - - - diff --git a/examples/docs-examples/examples/transferring-data/upload-file.mjs b/examples/docs-examples/examples/transferring-data/upload-file.mjs deleted file mode 100644 index 1fd81d215..000000000 --- a/examples/docs-examples/examples/transferring-data/upload-file.mjs +++ /dev/null @@ -1,32 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -import { createHash } from "node:crypto"; -import * as fs from "fs"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - const buff = fs.readFileSync("worker.mjs"); - const hash = createHash("md5").update(buff).digest("hex"); - - try { - const result = await executor.run(async (ctx) => { - await ctx.uploadFile("./worker.mjs", "/golem/input/worker.mjs"); - - const res = await ctx.run( - `node -e "const crypto = require('node:crypto'); const fs = require('fs'); const buff = fs.readFileSync('/golem/input/worker.mjs'); const hash = crypto.createHash('md5').update(buff).digest('hex'); console.log(hash);"`, - ); - - return res.stdout; - }); - - console.log("md5 of the file sent to provider: ", result); - console.log("Locally computed md5: ", hash); - } catch (error) { - console.error("Computation failed:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/transferring-data/upload-json-in-browser.html b/examples/docs-examples/examples/transferring-data/upload-json-in-browser.html deleted file mode 100644 index d25fcc35f..000000000 --- a/examples/docs-examples/examples/transferring-data/upload-json-in-browser.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - Golem JSON App - - - -

    JSON upload and download

    -
    -
    -

    Options

    -
    -
    - - -
    -
    - - -
    -
    -

    Actions

    -
    -
    - -
    -
    -
    -

    Results

    -
      -
      -
      -
      -
      -

      Logs

      -
        -
        -
        -
        - - diff --git a/examples/docs-examples/examples/transferring-data/upload-json.mjs b/examples/docs-examples/examples/transferring-data/upload-json.mjs deleted file mode 100644 index 69394b855..000000000 --- a/examples/docs-examples/examples/transferring-data/upload-json.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "dcd99a5904bebf7ca655a833b73cc42b67fd40b4a111572e3d2007c3", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const output = await executor.run(async (ctx) => { - // Upload test JSON object - await ctx.uploadJson({ input: "Hello World" }, "/golem/work/input.json"); - - // Read the content of the JSON object. - return await ctx.run("cat /golem/work/input.json"); - }); - console.log(output.stdout); - } catch (error) { - console.error("Computation failed:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/transferring-data/worker.mjs b/examples/docs-examples/examples/transferring-data/worker.mjs deleted file mode 100644 index accefceba..000000000 --- a/examples/docs-examples/examples/transferring-data/worker.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log("Hello World"); diff --git a/examples/docs-examples/examples/using-app-keys/index.mjs b/examples/docs-examples/examples/using-app-keys/index.mjs deleted file mode 100644 index f37204d8b..000000000 --- a/examples/docs-examples/examples/using-app-keys/index.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - // replace 'try_golem' with 'insert-your-32-char-app-key-here' - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => (await ctx.run("node -v")).stdout); - console.log("Task result:", result); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/working-with-images/hash.mjs b/examples/docs-examples/examples/working-with-images/hash.mjs deleted file mode 100644 index 71bc40d89..000000000 --- a/examples/docs-examples/examples/working-with-images/hash.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => (await ctx.run("node -v")).stdout); - console.log("Task result:", result); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/working-with-images/tag.mjs b/examples/docs-examples/examples/working-with-images/tag.mjs deleted file mode 100644 index 3deaa6801..000000000 --- a/examples/docs-examples/examples/working-with-images/tag.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - yagnaOptions: { apiKey: "try_golem" }, -}); - -try { - const result = await executor.run(async (ctx) => (await ctx.run("node -v")).stdout); - console.log("Task result:", result); -} catch (err) { - console.error("An error occurred:", err); -} finally { - await executor.shutdown(); -} diff --git a/examples/docs-examples/examples/working-with-results/multi-command-end.mjs b/examples/docs-examples/examples/working-with-results/multi-command-end.mjs deleted file mode 100644 index c94c4785a..000000000 --- a/examples/docs-examples/examples/working-with-results/multi-command-end.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => { - const res = await ctx - .beginBatch() - .uploadFile("./worker.mjs", "/golem/input/worker.mjs") - .run("node /golem/input/worker.mjs > /golem/input/output.txt") - .run("cat /golem/input/output.txt") - .downloadFile("/golem/input/output.txt", "./output.txt") - .end(); - - return res; - }); - - console.log(result); - } catch (error) { - console.error(error); - } finally { - if (executor) await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/working-with-results/multi-command-endstream.mjs b/examples/docs-examples/examples/working-with-results/multi-command-endstream.mjs deleted file mode 100644 index e13fe2e74..000000000 --- a/examples/docs-examples/examples/working-with-results/multi-command-endstream.mjs +++ /dev/null @@ -1,30 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => { - const res = await ctx - .beginBatch() - .uploadFile("./worker.mjs", "/golem/input/worker.mjs") - .run("node /golem/input/worker.mjs > /golem/input/output.txt") - .run("cat /golem/input/output.txt") - .downloadFile("/golem/input/output.txt", "./output.txt") - .endStream(); - - return new Promise((resolve, reject) => { - res.on("data", (result) => console.log(result)); - res.on("error", (error) => reject(error)); - res.on("close", resolve); - }); - }); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/working-with-results/multi-command-fail.mjs b/examples/docs-examples/examples/working-with-results/multi-command-fail.mjs deleted file mode 100644 index 25e54e7eb..000000000 --- a/examples/docs-examples/examples/working-with-results/multi-command-fail.mjs +++ /dev/null @@ -1,27 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => { - const res = await ctx - .beginBatch() - .run("cat /golem/input/output.txt > /golem/input/output.txt") - .downloadFile("/golem/output/output.txt", "./output.txt") // there is no such file in output folder - .run("ls -l /golem/") - .end(); - - return res; - }); - - console.log(result); - } catch (error) { - console.error("An error occurred:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/working-with-results/single-command-fail.mjs b/examples/docs-examples/examples/working-with-results/single-command-fail.mjs deleted file mode 100644 index 412ebc304..000000000 --- a/examples/docs-examples/examples/working-with-results/single-command-fail.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - // there is a mistake and instead of 'node -v' we call 'node -w' - const result = await executor.run(async (ctx) => await ctx.run("node -w")); - console.log("Task result:", result); - } catch (err) { - console.error("Error during the task:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/working-with-results/single-command.mjs b/examples/docs-examples/examples/working-with-results/single-command.mjs deleted file mode 100644 index 462fe5017..000000000 --- a/examples/docs-examples/examples/working-with-results/single-command.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => await ctx.run("node -v")); - console.log("Task result:", result); - } catch (err) { - console.error("Error during the task:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/examples/working-with-results/worker.mjs b/examples/docs-examples/examples/working-with-results/worker.mjs deleted file mode 100644 index accefceba..000000000 --- a/examples/docs-examples/examples/working-with-results/worker.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log("Hello World"); diff --git a/examples/docs-examples/quickstarts/quickstart/requestor.mjs b/examples/docs-examples/quickstarts/quickstart/requestor.mjs deleted file mode 100644 index cebbaa168..000000000 --- a/examples/docs-examples/quickstarts/quickstart/requestor.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - try { - const result = await executor.run(async (ctx) => (await ctx.run("node -v")).stdout); - console.log("Task result:", result); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/quickstarts/retrievable-task/task.mjs b/examples/docs-examples/quickstarts/retrievable-task/task.mjs index f11d0a5b0..b73dda544 100644 --- a/examples/docs-examples/quickstarts/retrievable-task/task.mjs +++ b/examples/docs-examples/quickstarts/retrievable-task/task.mjs @@ -10,8 +10,8 @@ const job = golem.createJob({ imageTag: "golem/alpine:latest", }, }); -job.startWork(async (ctx) => { - const response = await ctx.run("echo 'Hello, Golem!'"); +job.startWork(async (exe) => { + const response = await exe.run("echo 'Hello, Golem!'"); return response.stdout; }); diff --git a/examples/docs-examples/quickstarts/web-quickstart/index.html b/examples/docs-examples/quickstarts/web-quickstart/index.html deleted file mode 100644 index 72c5d0cf9..000000000 --- a/examples/docs-examples/quickstarts/web-quickstart/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - WebRequestor QuickStart - - -

        WebRequestor - QuickStart

        -
        -
        -

        Options

        -
        -
        - - -
        -
        - - -
        -
        -

        Actions

        -
        -
        - -
        -
        -
        -

        Results

        -
          -
          -
          -
          -
          -

          Logs

          -
            -
            -
            -
            - - - diff --git a/examples/docs-examples/quickstarts/web-quickstart/requestor.mjs b/examples/docs-examples/quickstarts/web-quickstart/requestor.mjs deleted file mode 100644 index a9c4c7f16..000000000 --- a/examples/docs-examples/quickstarts/web-quickstart/requestor.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import * as golem from "https://unpkg.com/@golem-sdk/golem-js"; - -function appendResults(result) { - const results = document.getElementById("results"); - const div = document.createElement("div"); - div.appendChild(document.createTextNode(result)); - results.appendChild(div); -} - -function appendLog(msg, level = "info") { - const logs = document.getElementById("logs"); - const div = document.createElement("div"); - div.appendChild(document.createTextNode(`[${new Date().toISOString()}] [${level}] ${msg}`)); - logs.appendChild(div); -} - -const logger = { - error: (msg) => appendLog(msg, "error"), - info: (msg) => appendLog(msg, "info"), - warn: (msg) => appendLog(msg, "warn"), - debug: (msg) => appendLog(msg, "debug"), - child: () => logger, -}; - -async function run() { - const executor = await golem.TaskExecutor.create({ - package: "dcd99a5904bebf7ca655a833b73cc42b67fd40b4a111572e3d2007c3", - yagnaOptions: { apiKey: "try_golem", basePath: document.getElementById("YAGNA_API_BASEPATH").value }, - subnetTag: document.getElementById("SUBNET_TAG").value, - logger, - }); - - try { - await executor.run(async (ctx) => appendResults((await ctx.run("echo 'Hello World'")).stdout)); - } catch (error) { - logger.error("Computation failed:", error); - } finally { - await executor.shutdown(); - } -} - -document.getElementById("echo").onclick = run; diff --git a/examples/docs-examples/tool-examples/converting-Docker-image/Dockerfile b/examples/docs-examples/tool-examples/converting-Docker-image/Dockerfile deleted file mode 100644 index d0de19d0f..000000000 --- a/examples/docs-examples/tool-examples/converting-Docker-image/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM debian:latest -VOLUME /golem/input /golem/output -WORKDIR /golem/work diff --git a/examples/docs-examples/tutorials/accessing-internet/manifest.json b/examples/docs-examples/tutorials/accessing-internet/manifest.json deleted file mode 100644 index 1674f149e..000000000 --- a/examples/docs-examples/tutorials/accessing-internet/manifest.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "version": "0.1.0", - "createdAt": "2023-10-19T11:23:08.156+02:00", - "expiresAt": "2024-01-17T11:23:08.156+01:00", - "metadata": { - "name": "outbound-docs", - "description": "", - "version": "1.0.0" - }, - "payload": [ - { - "platform": { - "os": "linux", - "arch": "x86_64" - }, - "hash": "sha3:dad8f776b0eb9f37ea0d63de42757034dd085fe30cc4537c2e119d80", - "urls": [ - "http://registry.golem.network/download/f37c8ba2b534ca631060fb8db4ac218d3199faf656aa2c92f402c2b700797c21" - ] - } - ], - "compManifest": { - "version": "0.1.0", - "net": { - "inet": { - "out": { - "urls": ["https://ipfs.io"], - "protocols": ["https"] - } - } - } - } -} diff --git a/examples/docs-examples/tutorials/accessing-internet/outbound-ipfs.mjs b/examples/docs-examples/tutorials/accessing-internet/outbound-ipfs.mjs deleted file mode 100644 index f0a9d8fbc..000000000 --- a/examples/docs-examples/tutorials/accessing-internet/outbound-ipfs.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -import { readFile } from "fs/promises"; - -const url = "https://ipfs.io/ipfs/bafybeihkoviema7g3gxyt6la7vd5ho32ictqbilu3wnlo3rs7ewhnp7lly"; - -(async function main() { - // Load the manifest. - const manifest = await readFile(`./manifest.json`); - - // Create and configure a TaskExecutor instance. - const executor = await TaskExecutor.create({ - capabilities: ["inet", "manifest-support"], - yagnaOptions: { apiKey: "try_golem" }, - manifest: manifest.toString("base64"), - }); - - try { - await executor.run(async (ctx) => { - const result = await ctx.run(`curl ${url} -o /golem/work/example.jpg`); - - console.log((await ctx.run("ls -l")).stdout); - if (result.result === "Ok") { - console.log("File downloaded!"); - } else { - console.error("Failed to download the file!", result.stderr); - } - }); - } catch (err) { - console.error("The task failed due to", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/tutorials/building-custom-image/Dockerfile b/examples/docs-examples/tutorials/building-custom-image/Dockerfile deleted file mode 100644 index d29ee22e0..000000000 --- a/examples/docs-examples/tutorials/building-custom-image/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM node:latest -WORKDIR /golem/work -VOLUME /golem/work -COPY Dockerfile /golem/info/description.txt -COPY Dockerfile /golem/work/info.txt \ No newline at end of file diff --git a/examples/docs-examples/tutorials/building-custom-image/index.mjs b/examples/docs-examples/tutorials/building-custom-image/index.mjs deleted file mode 100644 index d910f2337..000000000 --- a/examples/docs-examples/tutorials/building-custom-image/index.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -(async () => { - const executor = await TaskExecutor.create({ - package: "8b238595299444d0733b41095f27fadd819a71d29002b614c665b27c", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const result = await executor.run(async (ctx) => { - console.log("Description.txt: ", (await ctx.run("cat /golem/info/description.txt")).stdout); - console.log("/golem/work content: ", (await ctx.run("ls /golem/work")).stdout); - }); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/tutorials/quickstart/index.mjs b/examples/docs-examples/tutorials/quickstart/index.mjs deleted file mode 100644 index 57f99baa5..000000000 --- a/examples/docs-examples/tutorials/quickstart/index.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async () => { - const executor = await TaskExecutor.create({ - package: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - yagnaOptions: { apiKey: "try_golem" }, - }); - - try { - const taskResult = await executor.run(async (ctx) => (await ctx.run("node -v")).stdout); - console.log("Task result:", taskResult); - } catch (err) { - console.error("Task execution failed:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/docs-examples/tutorials/running-from-browser/index.html b/examples/docs-examples/tutorials/running-from-browser/index.html deleted file mode 100644 index 5370e3447..000000000 --- a/examples/docs-examples/tutorials/running-from-browser/index.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - WebRequestor Task API - - -

            WebRequestor - Hello World

            -
            -
            -

            Options

            -
            -
            - - -
            -
            - - -
            -
            -

            Actions

            -
            -
            - -
            -
            -
            -

            Results

            -
              -
              -
              -
              -
              -

              Logs

              -
                -
                -
                -
                - - - - diff --git a/examples/docs-examples/tutorials/running-parallel-tasks/index.mjs b/examples/docs-examples/tutorials/running-parallel-tasks/index.mjs deleted file mode 100644 index 5dcfa88d8..000000000 --- a/examples/docs-examples/tutorials/running-parallel-tasks/index.mjs +++ /dev/null @@ -1,59 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -import { program } from "commander"; - -async function main(args) { - const executor = await TaskExecutor.create({ - package: "055911c811e56da4d75ffc928361a78ed13077933ffa8320fb1ec2db", - maxParallelTasks: args.numberOfProviders, - yagnaOptions: { apiKey: `try_golem` }, - }); - - const keyspace = await executor.run(async (ctx) => { - const result = await ctx.run(`hashcat --keyspace -a 3 ${args.mask} -m 400`); - return parseInt(result.stdout || ""); - }); - - if (!keyspace) throw new Error(`Cannot calculate keyspace`); - - console.log(`Keyspace size computed. Keyspace size = ${keyspace}.`); - const step = Math.floor(keyspace / args.numberOfProviders + 1); - const range = [...Array(Math.floor(keyspace / step) + 1).keys()].map((i) => i * step); - - const findPasswordInRange = async (skip) => { - const password = await executor.run(async (ctx) => { - const [, potfileResult] = await ctx - .beginBatch() - .run( - `hashcat -a 3 -m 400 '${args.hash}' '${args.mask}' --skip=${skip} --limit=${ - skip + step - } -o pass.potfile || true`, - ) - .run("cat pass.potfile || true") - .end(); - if (!potfileResult.stdout) return false; - // potfile format is: hash:password - return potfileResult.stdout.toString().trim().split(":")[1]; - }); - if (!password) { - throw new Error(`Cannot find password in range ${skip} - ${skip + step}`); - } - return password; - }; - - try { - const password = await Promise.any(range.map(findPasswordInRange)); - console.log(`Password found: ${password}`); - } catch (err) { - console.log(`Password not found`); - } finally { - await executor.shutdown(); - } -} - -program - .option("--number-of-providers ", "number of providers", (value) => parseInt(value), 3) - .option("--mask ") - .requiredOption("--hash "); -program.parse(); -const options = program.opts(); -main(options).catch((error) => console.error(error)); diff --git a/examples/experimental/deployment/new-api.ts b/examples/experimental/deployment/new-api.ts new file mode 100644 index 000000000..607e049bd --- /dev/null +++ b/examples/experimental/deployment/new-api.ts @@ -0,0 +1,105 @@ +import { GolemNetwork } from "@golem-sdk/golem-js"; +import { GolemDeploymentBuilder } from "@golem-sdk/golem-js/experimental"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +async function main() { + const golem = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await golem.connect(); + + const builder = new GolemDeploymentBuilder(golem); + + builder + .createNetwork("basic", { + ip: "192.168.7.0/24", + }) + .createResourceRentalPool("app", { + demand: { + workload: { + imageTag: "golem/node:latest", + }, + }, + market: { + rentHours: 12, + pricing: { + model: "linear", + maxStartPrice: 1, + maxCpuPerHourPrice: 1, + maxEnvPerHourPrice: 1, + }, + }, + deployment: { + replicas: 2, + network: "basic", + }, + }) + .createResourceRentalPool("db", { + demand: { + workload: { + imageTag: "golem/alpine:latest", + minCpuCores: 1, + minMemGib: 2, + minStorageGib: 4, + }, + }, + market: { + rentHours: 12 /* REQUIRED */, + pricing: { + model: "linear", + maxStartPrice: 1 /* REQUIRED */, + maxCpuPerHourPrice: 1 /* REQUIRED */, + maxEnvPerHourPrice: 1 /* REQUIRED */, + }, + }, + deployment: { + replicas: 1, + network: "basic", + }, + }); + + const deployment = builder.getDeployment(); + + // Start your deployment + await deployment.start(); + + // Get your pool of activities for specified need + const appPool = deployment.getResourceRentalPool("app"); + const dbPool = deployment.getResourceRentalPool("db"); + + // Get an instance out of the pool for use + const appReplica1 = await appPool.acquire(); + const appReplica2 = await appPool.acquire(); + const dbReplica = await dbPool.acquire(); + + await Promise.allSettled([ + appReplica1 + .getExeUnit() + .then((exe) => exe.run("echo Running some code on app replica 1 πŸƒ")) + .then((res) => console.log(res.stdout)), + appReplica2 + .getExeUnit() + .then((exe) => exe.run("echo Running some code on app replica 2 πŸƒ")) + .then((res) => console.log(res.stdout)), + dbReplica + .getExeUnit() + .then((exe) => exe.run("echo Running some code on db replica πŸƒ")) + .then((res) => console.log(res.stdout)), + ]); + + await Promise.allSettled([appPool.destroy(appReplica1), appPool.destroy(appReplica2), dbPool.destroy(dbReplica)]); + + // Stop the deployment cleanly + await deployment.stop(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await golem.disconnect(); + } +} + +main().catch(console.error); diff --git a/examples/express/public/.gitkeep b/examples/experimental/express/public/.gitkeep similarity index 100% rename from examples/express/public/.gitkeep rename to examples/experimental/express/public/.gitkeep diff --git a/examples/express/server.ts b/examples/experimental/express/server.ts similarity index 85% rename from examples/express/server.ts rename to examples/experimental/express/server.ts index c413e690a..2a970081a 100644 --- a/examples/express/server.ts +++ b/examples/experimental/express/server.ts @@ -1,16 +1,12 @@ import express from "express"; -import { GolemNetwork, JobState } from "@golem-sdk/golem-js/experimental"; +import { JobManager, JobState } from "@golem-sdk/golem-js/experimental"; const app = express(); const port = 3000; app.use(express.text()); -const golemClient = new GolemNetwork({ - yagna: { - apiKey: "try_golem", - }, -}); +const golemClient = new JobManager(); await golemClient .init() @@ -28,8 +24,19 @@ app.post("/tts", async (req, res) => { return; } const job = golemClient.createJob({ - package: { - imageTag: "severyn/espeak:latest", + demand: { + activity: { + imageTag: "severyn/espeak:latest", + }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 1, + maxCpuPerHourPrice: 1, + maxEnvPerHourPrice: 1, + }, }, }); @@ -46,9 +53,9 @@ app.post("/tts", async (req, res) => { console.log("Job succeeded", job.results); }); - job.startWork(async (ctx) => { + job.startWork(async (exe) => { const fileName = `${Math.random().toString(36).slice(2)}.wav`; - await ctx + await exe .beginBatch() .run(`espeak "${req.body}" -w /golem/output/output.wav`) .downloadFile("/golem/output/output.wav", `public/${fileName}`) diff --git a/examples/job/cancel.ts b/examples/experimental/job/cancel.ts similarity index 59% rename from examples/job/cancel.ts rename to examples/experimental/job/cancel.ts index 3c186c85b..422caf4ba 100644 --- a/examples/job/cancel.ts +++ b/examples/experimental/job/cancel.ts @@ -1,19 +1,30 @@ -import { GolemNetwork } from "@golem-sdk/golem-js/experimental"; - -const golem = new GolemNetwork({ +import { JobManager } from "@golem-sdk/golem-js/experimental"; +import { MarketOrderSpec } from "@golem-sdk/golem-js"; +const golem = new JobManager({ yagna: { apiKey: "try_golem", }, }); +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "severyn/espeak:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 1, + maxCpuPerHourPrice: 1, + maxEnvPerHourPrice: 1, + }, + }, +}; + async function main() { await golem.init(); - const job = golem.createJob({ - package: { - imageTag: "golem/alpine:latest", - }, - }); + const job = golem.createJob(order); console.log("Job object created, initial status is", job.state); @@ -30,8 +41,8 @@ async function main() { console.log("Job ended event emitted"); }); - job.startWork(async (ctx) => { - return String((await ctx.run("echo -n 'Hello Golem!'")).stdout); + job.startWork(async (exe) => { + return String((await exe.run("echo -n 'Hello Golem!'")).stdout); }); console.log("Canceling job..."); diff --git a/examples/job/getJobById.ts b/examples/experimental/job/getJobById.ts similarity index 61% rename from examples/job/getJobById.ts rename to examples/experimental/job/getJobById.ts index 0903713d7..7276a2984 100644 --- a/examples/job/getJobById.ts +++ b/examples/experimental/job/getJobById.ts @@ -1,17 +1,29 @@ -import { GolemNetwork } from "@golem-sdk/golem-js/experimental"; +import { JobManager } from "@golem-sdk/golem-js/experimental"; +import { MarketOrderSpec } from "@golem-sdk/golem-js"; -const golem = new GolemNetwork({ +const golem = new JobManager({ yagna: { apiKey: "try_golem", }, }); -function startJob() { - const job = golem.createJob({ - package: { - imageTag: "golem/alpine:latest", +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "severyn/espeak:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 1, + maxCpuPerHourPrice: 1, + maxEnvPerHourPrice: 1, }, - }); + }, +}; + +function startJob() { + const job = golem.createJob(order); console.log("Job object created, initial status is", job.state); @@ -25,8 +37,8 @@ function startJob() { console.log("Job ended event emitted"); }); - job.startWork(async (ctx) => { - return String((await ctx.run("echo -n 'Hello Golem!'")).stdout); + job.startWork(async (exe) => { + return String((await exe.run("echo -n 'Hello Golem!'")).stdout); }); return job.id; } diff --git a/examples/job/waitForResults.ts b/examples/experimental/job/waitForResults.ts similarity index 56% rename from examples/job/waitForResults.ts rename to examples/experimental/job/waitForResults.ts index 4b3278872..d71bdf73b 100644 --- a/examples/job/waitForResults.ts +++ b/examples/experimental/job/waitForResults.ts @@ -1,19 +1,32 @@ -import { GolemNetwork } from "@golem-sdk/golem-js/experimental"; - -const golem = new GolemNetwork({ +import { JobManager } from "@golem-sdk/golem-js/experimental"; +import { MarketOrderSpec } from "@golem-sdk/golem-js"; +const golem = new JobManager({ yagna: { apiKey: "try_golem", }, }); +const order: MarketOrderSpec = { + demand: { + workload: { + imageTag: "severyn/espeak:latest", + }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 1, + maxCpuPerHourPrice: 1, + maxEnvPerHourPrice: 1, + }, + }, +}; + async function main() { await golem.init(); - const job = golem.createJob({ - package: { - imageTag: "golem/alpine:latest", - }, - }); + const job = golem.createJob(order); console.log("Job object created, initial status is", job.state); @@ -27,8 +40,8 @@ async function main() { console.log("Job ended event emitted"); }); - job.startWork(async (ctx) => { - return String((await ctx.run("echo -n 'Hello Golem!'")).stdout); + job.startWork(async (exe) => { + return String((await exe.run("echo -n 'Hello Golem!'")).stdout); }); const result = await job.waitForResult(); diff --git a/examples/experimental/reputation/basic.ts b/examples/experimental/reputation/basic.ts deleted file mode 100644 index b83097dc6..000000000 --- a/examples/experimental/reputation/basic.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { TaskExecutor, sleep } from "@golem-sdk/golem-js"; -import { ReputationSystem } from "@golem-sdk/golem-js/experimental"; - -/** - * This example uses the reputation system to filter out proposals from providers with low reputation and ones that were not tested yet. - * - * This improves the likelihood of successful computations. - * - * This is an experimental feature and the API is subject to change. - * - * @experimental - */ -(async function main() { - console.log("WARNING: This test always run on polygon, so real costs will occur."); - console.log("If you do not wish to continue, press Ctrl+C to abort."); - console.log("The test will start in 5 seconds..."); - await sleep(5, false); - - const reputation = await ReputationSystem.create({ - paymentNetwork: "polygon", - }); - - console.log("Listed providers:", reputation.getData().testedProviders.length); - - const executor = await TaskExecutor.create({ - payment: { network: "polygon" }, - package: "golem/alpine:latest", - proposalFilter: reputation.proposalFilter(), - agreementSelector: reputation.agreementSelector(), - }); - - try { - await executor.run(async (ctx) => { - const result = await ctx.run("echo 'Hello World'"); - console.log(result.stdout); - }); - } catch (error) { - console.error("Computation failed:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/external-request/Dockerfile b/examples/external-request/Dockerfile deleted file mode 100644 index b54977f69..000000000 --- a/examples/external-request/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM alpine:latest - -RUN apk update \ - && apk add curl \ - && apk add jq \ - && rm -rf /var/cache/apk/* - -WORKDIR /golem/work diff --git a/examples/external-request/README.md b/examples/external-request/README.md deleted file mode 100644 index 18bad9880..000000000 --- a/examples/external-request/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# External API request - -Example showing how to make a REST call to an external, public API from a VM running on a Provider node. - -## Prerequisites - -Computation Payload Manifest making use of Outbound Network requires either: - -1. Requestor certificate that's trusted by the Providers -2. an instance of a Provider with the particular domain this example uses added to its domain whitelist -3. an instance of a Provider with the requestor's self-signed Certificate imported into its keystore - -The following example will show cases 2. and 3. so it will be necessary to start a local instance of a Provider. - -## Example app - -An example app will request an external API using Provider's network and then it will print the API response to the console. - -### 1. Manifest file - -For an app to make an _Outbound Network_ request it needs to declare which tools it will use and which URLs it will access in a manifest file. - -This example will make an HTTPS request using `curl` to a public REST API with the URL `https://api.coingecko.com`. - -_Computation Payload Manifest_ will need to have following objects: - -- [`net`](../vm-runtime/computation-payload-manifest.md#compmanifestnet--object) computation constraints with `URL`s the app will access (`https://api.coingecko.com`) -- [`script`](../vm-runtime/computation-payload-manifest.md#compmanifestscript) computation constraint with `command`s app will execute (`curl`) -- [`payload`](../vm-runtime/computation-payload-manifest.md#payload-object) defining golem image containing tools used by the app (`curl`) - -Example _Computation Payload Manifest_ must follow a specific schema, and for our example it will take form of following `manifest.json` file. - -### 2. Verification of a request with Computation Payload Manifest - -_Providers_ verify the incoming request with a _Computation Payload Manifest_ by checking if it arrives with a signature and _App author's certificate_ signed by a certificate they trust. If there is no signature, they verify if URLs used by _Computation Payload Manifest_ are whitelisted. - -There are two ways to make our _local_ _Provider_ verify the request: - -- #### Whitelisting of the domain used by the app - - - Add `api.coingecko.com` to Provider's domain whitelist: `ya-provider whitelist add --patterns api.coingecko.com --type strict` - - - And set outbound mode to `Everyone: whitelist`: `ya-provider rule set outbound everyone --mode whitelist` - -- #### Signing manifest and adding signature with a certificate to the request - - Generate self signed certificate and then generate manifest signature. - - With a generated certificate and a signature, you can pass them to the Executor options as follow: - -```ts -const executor = await TaskExecutor.create({ - manifest: Buffer.from(readFileSync(`${__dirname}/manifest.json`, "utf-8")).toString("base64"), - manifestSig: readFileSync(`${__dirname}/manifest.json.base64.sign.sha256.base64`, "utf-8"), - manifestCert: readFileSync(`${__dirname}/golem-manifest.crt.pem.base64`, "utf-8"), - manifestSigAlgorithm: "sha256", - capabilities: ["inet", "manifest-support"], -}); -``` - -### 3. Launching the app - -With both _Requestor_ and _Provider_ yagna nodes and `ya-provider` running in the background run: - -```sh -npm run external-request -``` - -(keep in mind to set `YAGNA_APPKEY` env variable pointing to the local _Requestor_ node) diff --git a/examples/external-request/manifest.json b/examples/external-request/manifest.json deleted file mode 100644 index 0c2c6ec17..000000000 --- a/examples/external-request/manifest.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "version": "0.1.0", - "createdAt": "2022-07-26T12:51:00.000000Z", - "expiresAt": "2100-01-01T00:01:00.000000Z", - "metadata": { - "name": "External API call example", - "description": "Example manifest of a service making an outbound call to the external API", - "version": "0.1.0" - }, - "payload": [ - { - "platform": { - "os": "linux", - "arch": "x86_64" - }, - "hash": "sha3:dad8f776b0eb9f37ea0d63de42757034dd085fe30cc4537c2e119d80", - "urls": [ - "https://registry.golem.network/download/f37c8ba2b534ca631060fb8db4ac218d3199faf656aa2c92f402c2b700797c21" - ] - } - ], - "compManifest": { - "version": "0.1.0", - "net": { - "inet": { - "out": { - "urls": ["https://ipfs.io"], - "protocols": ["https"] - } - } - } - } -} diff --git a/examples/external-request/request.ts b/examples/external-request/request.ts deleted file mode 100644 index e245d7987..000000000 --- a/examples/external-request/request.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { TaskExecutor, ResultState } from "@golem-sdk/golem-js"; -import { readFile } from "fs/promises"; -import { fileURLToPath } from "url"; -const DIR_NAME = fileURLToPath(new URL(".", import.meta.url)); - -(async function main() { - // Load the manifest. - const manifest = await readFile(`${DIR_NAME}/manifest.json`); - - // Create and configure a TaskExecutor instance. - const executor = await TaskExecutor.create({ - capabilities: ["inet", "manifest-support"], - manifest: manifest.toString("base64"), - /** - * Uncomment this if you have a certificate and a signed manifest - */ - // manifestSig: (await readFile(`${DIR_NAME}/manifest.json.base64.sign.sha256`)).toString("base64"), - // manifestCert: (await readFile(`${DIR_NAME}/golem-manifest.crt.pem`)).toString("base64"), - // manifestSigAlgorithm: "sha256", - }); - - try { - const url = "https://ipfs.io/ipfs/bafybeihkoviema7g3gxyt6la7vd5ho32ictqbilu3wnlo3rs7ewhnp7lly"; - const results = await executor.run(async (ctx) => - ctx - .beginBatch() - .run(`curl ${url} -o /golem/work/example.jpg`) - .downloadFile("/golem/work/example.jpg", `${DIR_NAME}/example.jpg`) - .end(), - ); - if (results[1].result === ResultState.Ok) { - console.log("Downloaded file to", `${DIR_NAME}/example.jpg`); - } else { - console.error("Something went wrong", results[1].message); - } - } catch (err) { - console.error("The task failed due to", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/external-request/sign.sh b/examples/external-request/sign.sh deleted file mode 100755 index de7a4966c..000000000 --- a/examples/external-request/sign.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -if [ $# -lt 1 ]; then - echo 1>&2 "$0: Add path to private key as a param" - exit 2 -fi - -PRIVATE_KEY=$1 -base64 -i manifest.json > manifest.json.base64 -openssl dgst -sha256 -sign $PRIVATE_KEY -out manifest.json.base64.sign.sha256 manifest.json.base64 diff --git a/examples/fibonacci/fibonacci.ts b/examples/fibonacci/fibonacci.ts deleted file mode 100644 index f290145e4..000000000 --- a/examples/fibonacci/fibonacci.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -import { program } from "commander"; - -type MainOptions = { - subnetTag: string; - paymentDriver: string; - paymentNetwork: string; - tasksCount: number; - fibonacciNumber: number; -}; - -program - .option("-n, --fibonacci-number ", "fibonacci number", "1") - .option("-c, --tasks-count ", "tasks count", "1") - .option("--subnet-tag ", "set subnet name, for example 'public'", "public") - .option("--payment-driver, --driver ", "payment driver name, for example 'erc20'", "erc20") - .option("--payment-network, --network ", "network name, for example 'holesky'", "holesky") - .action(async (options: MainOptions) => { - const executor = await TaskExecutor.create({ - package: "golem/js-fibonacci:latest", - subnetTag: options.subnetTag, - payment: { driver: options.paymentDriver, network: options.paymentNetwork }, - }); - - const runningTasks: Promise[] = []; - for (let i = 0; i < options.tasksCount; i++) { - runningTasks.push( - executor.run(async (ctx) => { - const result = await ctx.run("/usr/local/bin/node", [ - "/golem/work/fibo.js", - options.fibonacciNumber.toString(), - ]); - console.log(result.stdout); - return result.stdout?.toString().trim(); - }), - ); - } - - try { - await Promise.all(runningTasks); - } finally { - await executor.shutdown(); - } - }); - -program.parse(); diff --git a/examples/fibonacci/image/Dockerfile b/examples/fibonacci/image/Dockerfile deleted file mode 100644 index 4267eb643..000000000 --- a/examples/fibonacci/image/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM node:18-alpine -COPY dist/fibo.js /golem/work/ -VOLUME /golem/input -WORKDIR /golem/work diff --git a/examples/fibonacci/image/package.json b/examples/fibonacci/image/package.json deleted file mode 100644 index 4ac0b9a97..000000000 --- a/examples/fibonacci/image/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "golem/js-fibonacci", - "description": "Example nodejs project that should end-up as a docker image that can be downloaded from the Golem Registry to be run on the provider", - "version": "1.0.0", - "repository": "https://github.com/golemfactory/golem-js", - "private": true, - "scripts": { - "compile": "tsc", - "image:build": "docker build -t $npm_package_name:latest .", - "image:publish": "gvmkit-build --push $npm_package_name:latest", - "build": "npm run compile && npm run image:build && npm run image:publish" - }, - "author": "GolemFactory ", - "license": "LGPL-3.0", - "devDependencies": { - "typescript": "^5.3.3" - } -} diff --git a/examples/fibonacci/image/src/fibo.ts b/examples/fibonacci/image/src/fibo.ts deleted file mode 100644 index 5ff5c8e94..000000000 --- a/examples/fibonacci/image/src/fibo.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-env node */ - -type GetFibonacciSeries = (n: number) => number; - -function fiboLoop(n: number) { - let a = 1, - b = 0; - while (n >= 0) { - [a, b] = [a + b, a]; - n--; - } - return b; -} - -function fiboRecur(n: number): number { - return n > 1 ? fiboRecur(n - 1) + fiboRecur(n - 2) : 1; -} - -function fiboMemo(n: number, memo: Record = {}): number { - if (memo[n]) return memo[n]; - if (n <= 1) return 1; - return (memo[n] = fiboMemo(n - 1, memo) + fiboMemo(n - 2, memo)); -} - -function benchmark(fn: GetFibonacciSeries, n: number, type: string) { - const timeStart = process.hrtime(); - const result = fn(n); - const timeSTop = process.hrtime(timeStart); - return { type, result, time: parseFloat(timeSTop.join(".")) }; -} - -const n = parseInt(process.argv[2] ?? "1"); - -const results = [ - benchmark(fiboLoop, n, "loop"), - benchmark(fiboRecur, n, "recursion"), - benchmark(fiboMemo, n, "memoization"), -].sort((a, b) => a.time - b.time); - -console.table(results); diff --git a/examples/fibonacci/image/tsconfig.json b/examples/fibonacci/image/tsconfig.json deleted file mode 100644 index d0f3e646e..000000000 --- a/examples/fibonacci/image/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - "module": "commonjs" /* Specify what module code is generated. */, - "rootDir": "./src" /* Specify the root folder within your source files. */, - "outDir": "./dist" /* Specify an output folder for all emitted files. */, - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - "strict": true /* Enable all strict type-checking options. */, - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } -} diff --git a/examples/hello-world/Dockerfile b/examples/hello-world/Dockerfile deleted file mode 100644 index 315e05a1a..000000000 --- a/examples/hello-world/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM alpine:latest - -VOLUME /golem/input /golem/output /golem/work -WORKDIR /golem/work diff --git a/examples/hello-world/hello.ts b/examples/hello-world/hello.ts deleted file mode 100644 index 9d31a5c83..000000000 --- a/examples/hello-world/hello.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async function main() { - const executor = await TaskExecutor.create("golem/alpine:latest"); - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (error) { - console.error("Computation failed:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/package-lock.json b/examples/package-lock.json deleted file mode 100644 index 4f09d7e75..000000000 --- a/examples/package-lock.json +++ /dev/null @@ -1,17543 +0,0 @@ -{ - "name": "golem-js-examples", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "golem-js-examples", - "version": "0.0.0", - "license": "LGPL-3.0", - "dependencies": { - "@golem-sdk/golem-js": "file:..", - "commander": "^9.1.0", - "express": "^4.18.2", - "ts-node": "^10.7.0" - }, - "devDependencies": { - "@types/node": "18", - "typescript": "^5.3.3" - }, - "engines": { - "node": ">=16.19.0" - } - }, - "..": { - "name": "@golem-sdk/golem-js", - "version": "0.8.0", - "license": "LGPL-3.0", - "dependencies": { - "async-lock": "^1.4.0", - "axios": "^1.6.2", - "bottleneck": "^2.19.5", - "collect.js": "^4.36.1", - "debug": "^4.3.4", - "decimal.js-light": "^2.5.1", - "eventemitter3": "^5.0.1", - "eventsource": "^2.0.2", - "flatbuffers": "^23.5.26", - "ip-num": "^1.5.1", - "js-sha3": "^0.9.3", - "pino": "^8.17.1", - "pino-pretty": "^10.3.0", - "semver": "^7.5.4", - "tmp": "^0.2.1", - "uuid": "^9.0.1", - "ws": "^8.16.0", - "ya-ts-client": "^0.5.3" - }, - "devDependencies": { - "@commitlint/cli": "^18.4.3", - "@commitlint/config-conventional": "^18.4.3", - "@johanblumenberg/ts-mockito": "^1.0.41", - "@rollup/plugin-alias": "^5.1.0", - "@rollup/plugin-commonjs": "^25.0.7", - "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-typescript": "^11.1.5", - "@types/async-lock": "^1.4.2", - "@types/debug": "^4.1.12", - "@types/eventsource": "^1.1.15", - "@types/express": "^4.17.21", - "@types/jest": "^29.5.11", - "@types/node": "^20.10.4", - "@types/semver": "^7.5.6", - "@types/supertest": "^6.0.2", - "@types/tmp": "^0.2.6", - "@types/uuid": "^9.0.7", - "@types/ws": "^8.5.10", - "@typescript-eslint/eslint-plugin": "^7.0.1", - "@typescript-eslint/parser": "^7.0.1", - "buffer": "^6.0.3", - "cypress": "^13.5.1", - "cypress-log-to-output": "^1.1.2", - "eslint": "^8.56.0", - "eslint-config-prettier": "^9.1.0", - "express": "^4.18.2", - "husky": "^9.0.6", - "jest": "^29.7.0", - "jest-junit": "^16.0.0", - "prettier": "^3.1.1", - "rollup": "^4.9.0", - "rollup-plugin-filesize": "^10.0.0", - "rollup-plugin-ignore": "^1.0.10", - "rollup-plugin-polyfill-node": "^0.13.0", - "rollup-plugin-visualizer": "^5.11.0", - "semantic-release": "^23.0.0", - "stream-browserify": "^3.0.0", - "supertest": "^6.3.3", - "ts-jest": "^29.1.1", - "ts-loader": "^9.5.1", - "ts-node": "^10.9.2", - "tsconfig-paths": "^4.2.0", - "tslint-config-prettier": "^1.18.0", - "typedoc": "^0.25.4", - "typedoc-plugin-markdown": "^3.17.1", - "typedoc-plugin-merge-modules": "^5.1.0", - "typedoc-theme-hierarchy": "4.1.2", - "typescript": "^5.3.3" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-darwin-x64": "^4", - "@rollup/rollup-win32-arm64-msvc": "^4", - "@rollup/rollup-win32-x64-msvc": "^4" - } - }, - "../node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/@ampproject/remapping": { - "version": "2.2.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "../node_modules/@babel/code-frame": { - "version": "7.23.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "../node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "../node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/@babel/compat-data": { - "version": "7.23.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/core": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.6", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", - "@babel/types": "^7.23.6", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "../node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "../node_modules/@babel/generator": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "../node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/helpers": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", - "@babel/types": "^7.23.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/highlight": { - "version": "7.23.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "../node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "../node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/@babel/parser": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "../node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../node_modules/@babel/runtime": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/template": { - "version": "7.22.15", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/traverse": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/@babel/types": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/@colors/colors": { - "version": "1.5.0", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "../node_modules/@commitlint/cli": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/format": "^18.4.3", - "@commitlint/lint": "^18.4.3", - "@commitlint/load": "^18.4.3", - "@commitlint/read": "^18.4.3", - "@commitlint/types": "^18.4.3", - "execa": "^5.0.0", - "lodash.isfunction": "^3.0.9", - "resolve-from": "5.0.0", - "resolve-global": "1.0.0", - "yargs": "^17.0.0" - }, - "bin": { - "commitlint": "cli.js" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/config-conventional": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-changelog-conventionalcommits": "^7.0.2" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/config-validator": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^18.4.3", - "ajv": "^8.11.0" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/ensure": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^18.4.3", - "lodash.camelcase": "^4.3.0", - "lodash.kebabcase": "^4.1.1", - "lodash.snakecase": "^4.1.1", - "lodash.startcase": "^4.4.0", - "lodash.upperfirst": "^4.3.1" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/execute-rule": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/format": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^18.4.3", - "chalk": "^4.1.0" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/is-ignored": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^18.4.3", - "semver": "7.5.4" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/lint": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/is-ignored": "^18.4.3", - "@commitlint/parse": "^18.4.3", - "@commitlint/rules": "^18.4.3", - "@commitlint/types": "^18.4.3" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/load": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/config-validator": "^18.4.3", - "@commitlint/execute-rule": "^18.4.3", - "@commitlint/resolve-extends": "^18.4.3", - "@commitlint/types": "^18.4.3", - "@types/node": "^18.11.9", - "chalk": "^4.1.0", - "cosmiconfig": "^8.3.6", - "cosmiconfig-typescript-loader": "^5.0.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", - "lodash.uniq": "^4.5.0", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/load/node_modules/@types/node": { - "version": "18.19.3", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "../node_modules/@commitlint/message": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/parse": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/types": "^18.4.3", - "conventional-changelog-angular": "^7.0.0", - "conventional-commits-parser": "^5.0.0" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/read": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/top-level": "^18.4.3", - "@commitlint/types": "^18.4.3", - "fs-extra": "^11.0.0", - "git-raw-commits": "^2.0.11", - "minimist": "^1.2.6" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/resolve-extends": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/config-validator": "^18.4.3", - "@commitlint/types": "^18.4.3", - "import-fresh": "^3.0.0", - "lodash.mergewith": "^4.6.2", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/rules": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/ensure": "^18.4.3", - "@commitlint/message": "^18.4.3", - "@commitlint/to-lines": "^18.4.3", - "@commitlint/types": "^18.4.3", - "execa": "^5.0.0" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/to-lines": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/top-level": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@commitlint/types": { - "version": "18.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0" - }, - "engines": { - "node": ">=v18" - } - }, - "../node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "../node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "../node_modules/@cypress/request": { - "version": "3.0.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "http-signature": "~1.3.6", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "6.10.4", - "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/@cypress/request/node_modules/form-data": { - "version": "2.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "../node_modules/@cypress/request/node_modules/uuid": { - "version": "8.3.2", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "../node_modules/@cypress/xvfb": { - "version": "1.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.1.0", - "lodash.once": "^4.1.1" - } - }, - "../node_modules/@cypress/xvfb/node_modules/debug": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "../node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "../node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "../node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "../node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/@eslint/js": { - "version": "8.56.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "../node_modules/@gar/promisify": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "../node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "../node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "dev": true, - "license": "BSD-3-Clause" - }, - "../node_modules/@isaacs/cliui": { - "version": "8.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "../node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "../node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "../node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "../node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "../node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "../node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/@jest/console": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@jest/console/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/@jest/core": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "../node_modules/@jest/core/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/@jest/environment": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@jest/expect": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@jest/expect-utils": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@jest/fake-timers": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@jest/globals": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@jest/reporters": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "../node_modules/@jest/reporters/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/@jest/reporters/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@jest/source-map": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@jest/test-result": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@jest/test-sequencer/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/@jest/transform": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@jest/transform/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/@jest/types": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/@johanblumenberg/ts-mockito": { - "version": "1.0.41", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.20" - } - }, - "../node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "../node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "../node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "../node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "../node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "dev": true, - "license": "MIT" - }, - "../node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "../node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "../node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/@npmcli/fs": { - "version": "3.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@npmcli/git": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/promise-spawn": "^6.0.0", - "lru-cache": "^7.4.4", - "npm-pick-manifest": "^8.0.0", - "proc-log": "^3.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "7.18.3", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "../node_modules/@npmcli/git/node_modules/which": { - "version": "3.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@npmcli/installed-package-contents": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "bin": { - "installed-package-contents": "lib/index.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@npmcli/move-file": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@npmcli/promise-spawn": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "which": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "3.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@npmcli/run-script": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^6.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@npmcli/run-script/node_modules/which": { - "version": "3.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@octokit/auth-token": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "../node_modules/@octokit/core": { - "version": "5.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^4.0.0", - "@octokit/graphql": "^7.0.0", - "@octokit/request": "^8.0.2", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "../node_modules/@octokit/endpoint": { - "version": "9.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^12.0.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "../node_modules/@octokit/graphql": { - "version": "7.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request": "^8.0.1", - "@octokit/types": "^12.0.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "../node_modules/@octokit/openapi-types": { - "version": "19.1.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/@octokit/plugin-paginate-rest": { - "version": "9.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^12.4.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=5" - } - }, - "../node_modules/@octokit/plugin-retry": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=5" - } - }, - "../node_modules/@octokit/plugin-throttling": { - "version": "8.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^12.2.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "^5.0.0" - } - }, - "../node_modules/@octokit/request": { - "version": "8.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^9.0.0", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "../node_modules/@octokit/request-error": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^12.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - }, - "engines": { - "node": ">= 18" - } - }, - "../node_modules/@octokit/types": { - "version": "12.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^19.1.0" - } - }, - "../node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "../node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.22.0" - } - }, - "../node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "4.2.10" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "../node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "dev": true, - "license": "ISC" - }, - "../node_modules/@pnpm/npm-conf": { - "version": "2.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, - "engines": { - "node": ">=12" - } - }, - "../node_modules/@rollup/plugin-alias": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "slash": "^4.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/@rollup/plugin-commonjs": { - "version": "25.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^8.0.3", - "is-reference": "1.2.1", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/@rollup/plugin-inject": { - "version": "5.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/@rollup/plugin-json": { - "version": "6.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/@rollup/plugin-terser": { - "version": "0.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "serialize-javascript": "^6.0.1", - "smob": "^1.0.0", - "terser": "^5.17.4" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/@rollup/plugin-typescript": { - "version": "11.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.14.0||^3.0.0||^4.0.0", - "tslib": "*", - "typescript": ">=3.7.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - }, - "tslib": { - "optional": true - } - } - }, - "../node_modules/@rollup/pluginutils": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.1", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "../node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.1", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "../node_modules/@semantic-release/commit-analyzer": { - "version": "11.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-changelog-angular": "^7.0.0", - "conventional-commits-filter": "^4.0.0", - "conventional-commits-parser": "^5.0.0", - "debug": "^4.0.0", - "import-from-esm": "^1.0.3", - "lodash-es": "^4.17.21", - "micromatch": "^4.0.2" - }, - "engines": { - "node": "^18.17 || >=20.6.1" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" - } - }, - "../node_modules/@semantic-release/error": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "../node_modules/@semantic-release/github": { - "version": "9.2.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/core": "^5.0.0", - "@octokit/plugin-paginate-rest": "^9.0.0", - "@octokit/plugin-retry": "^6.0.0", - "@octokit/plugin-throttling": "^8.0.0", - "@semantic-release/error": "^4.0.0", - "aggregate-error": "^5.0.0", - "debug": "^4.3.4", - "dir-glob": "^3.0.1", - "globby": "^14.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "issue-parser": "^6.0.0", - "lodash-es": "^4.17.21", - "mime": "^4.0.0", - "p-filter": "^3.0.0", - "url-join": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" - } - }, - "../node_modules/@semantic-release/github/node_modules/agent-base": { - "version": "7.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "../node_modules/@semantic-release/github/node_modules/aggregate-error": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^5.2.0", - "indent-string": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/github/node_modules/clean-stack": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "5.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/github/node_modules/escape-string-regexp": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/github/node_modules/globby": { - "version": "14.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^1.0.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/github/node_modules/http-proxy-agent": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "../node_modules/@semantic-release/github/node_modules/https-proxy-agent": { - "version": "7.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "../node_modules/@semantic-release/github/node_modules/indent-string": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/github/node_modules/path-type": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/github/node_modules/slash": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/npm": { - "version": "11.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@semantic-release/error": "^4.0.0", - "aggregate-error": "^5.0.0", - "execa": "^8.0.0", - "fs-extra": "^11.0.0", - "lodash-es": "^4.17.21", - "nerf-dart": "^1.0.0", - "normalize-url": "^8.0.0", - "npm": "^10.0.0", - "rc": "^1.2.8", - "read-pkg": "^9.0.0", - "registry-auth-token": "^5.0.0", - "semver": "^7.1.2", - "tempy": "^3.0.0" - }, - "engines": { - "node": "^18.17 || >=20" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" - } - }, - "../node_modules/@semantic-release/npm/node_modules/aggregate-error": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^5.2.0", - "indent-string": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/npm/node_modules/clean-stack": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "5.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/npm/node_modules/escape-string-regexp": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/npm/node_modules/execa": { - "version": "8.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "../node_modules/@semantic-release/npm/node_modules/get-stream": { - "version": "8.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/npm/node_modules/human-signals": { - "version": "5.0.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "../node_modules/@semantic-release/npm/node_modules/indent-string": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/npm/node_modules/is-stream": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/npm/node_modules/mimic-fn": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/npm/node_modules/npm-run-path": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/npm/node_modules/onetime": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/npm/node_modules/path-key": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/npm/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/@semantic-release/npm/node_modules/strip-final-newline": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@semantic-release/release-notes-generator": { - "version": "12.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-changelog-angular": "^7.0.0", - "conventional-changelog-writer": "^7.0.0", - "conventional-commits-filter": "^4.0.0", - "conventional-commits-parser": "^5.0.0", - "debug": "^4.0.0", - "get-stream": "^7.0.0", - "import-from-esm": "^1.0.3", - "into-stream": "^7.0.0", - "lodash-es": "^4.17.21", - "read-pkg-up": "^11.0.0" - }, - "engines": { - "node": "^18.17 || >=20.6.1" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" - } - }, - "../node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@sigstore/bundle": { - "version": "1.1.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@sigstore/sign": { - "version": "1.0.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^1.1.0", - "@sigstore/protobuf-specs": "^0.2.0", - "make-fetch-happen": "^11.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@sigstore/sign/node_modules/lru-cache": { - "version": "7.18.3", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "../node_modules/@sigstore/sign/node_modules/make-fetch-happen": { - "version": "11.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@sigstore/sign/node_modules/minipass-fetch": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "../node_modules/@sigstore/sign/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "../node_modules/@sigstore/tuf": { - "version": "1.0.3", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0", - "tuf-js": "^1.1.7" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@sinclair/typebox": { - "version": "0.27.8", - "dev": true, - "license": "MIT" - }, - "../node_modules/@sindresorhus/is": { - "version": "4.6.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "../node_modules/@sindresorhus/merge-streams": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/@sinonjs/commons": { - "version": "3.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "../node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "../node_modules/@tootallnate/once": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "../node_modules/@tsconfig/node10": { - "version": "1.0.9", - "dev": true, - "license": "MIT" - }, - "../node_modules/@tsconfig/node12": { - "version": "1.0.11", - "dev": true, - "license": "MIT" - }, - "../node_modules/@tsconfig/node14": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/@tsconfig/node16": { - "version": "1.0.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/@tufjs/canonical-json": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@tufjs/models": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "1.0.0", - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/@types/async-lock": { - "version": "1.4.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/babel__core": { - "version": "7.20.5", - "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.6.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "../node_modules/@types/babel__template": { - "version": "7.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "../node_modules/@types/babel__traverse": { - "version": "7.20.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "../node_modules/@types/body-parser": { - "version": "1.19.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "../node_modules/@types/connect": { - "version": "3.4.38", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "../node_modules/@types/cookiejar": { - "version": "2.1.5", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/eslint": { - "version": "8.44.9", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "../node_modules/@types/eslint-scope": { - "version": "3.7.7", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "../node_modules/@types/estree": { - "version": "1.0.5", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/eventsource": { - "version": "1.1.15", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/express": { - "version": "4.17.21", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "../node_modules/@types/express-serve-static-core": { - "version": "4.17.41", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "../node_modules/@types/graceful-fs": { - "version": "4.1.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "../node_modules/@types/http-errors": { - "version": "2.0.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "../node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "../node_modules/@types/jest": { - "version": "29.5.11", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "../node_modules/@types/json-schema": { - "version": "7.0.15", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/methods": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/mime": { - "version": "1.3.5", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/minimist": { - "version": "1.2.5", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/node": { - "version": "20.10.5", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "../node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/qs": { - "version": "6.9.10", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/range-parser": { - "version": "1.2.7", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/resolve": { - "version": "1.20.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/semver": { - "version": "7.5.6", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/send": { - "version": "0.17.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "../node_modules/@types/serve-static": { - "version": "1.15.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" - } - }, - "../node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/sizzle": { - "version": "2.3.8", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/stack-utils": { - "version": "2.0.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/superagent": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/cookiejar": "^2.1.5", - "@types/methods": "^1.1.4", - "@types/node": "*" - } - }, - "../node_modules/@types/supertest": { - "version": "6.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/methods": "^1.1.4", - "@types/superagent": "^8.1.0" - } - }, - "../node_modules/@types/tmp": { - "version": "0.2.6", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/uuid": { - "version": "9.0.7", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/yargs": { - "version": "17.0.32", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "../node_modules/@types/yargs-parser": { - "version": "21.0.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/@types/yauzl": { - "version": "2.10.3", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "../node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.18.0", - "@typescript-eslint/type-utils": "6.18.0", - "@typescript-eslint/utils": "6.18.0", - "@typescript-eslint/visitor-keys": "6.18.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "../node_modules/@typescript-eslint/parser": { - "version": "6.16.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "6.16.0", - "@typescript-eslint/types": "6.16.0", - "@typescript-eslint/typescript-estree": "6.16.0", - "@typescript-eslint/visitor-keys": "6.16.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "../node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "6.16.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.16.0", - "@typescript-eslint/visitor-keys": "6.16.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "../node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "6.16.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "../node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.16.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "6.16.0", - "@typescript-eslint/visitor-keys": "6.16.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "../node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.16.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.16.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "../node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/@typescript-eslint/parser/node_modules/minimatch": { - "version": "9.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/@typescript-eslint/scope-manager": { - "version": "6.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.18.0", - "@typescript-eslint/visitor-keys": "6.18.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "../node_modules/@typescript-eslint/type-utils": { - "version": "6.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "6.18.0", - "@typescript-eslint/utils": "6.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "../node_modules/@typescript-eslint/types": { - "version": "6.18.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "../node_modules/@typescript-eslint/typescript-estree": { - "version": "6.18.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "6.18.0", - "@typescript-eslint/visitor-keys": "6.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "../node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/@typescript-eslint/utils": { - "version": "6.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.18.0", - "@typescript-eslint/types": "6.18.0", - "@typescript-eslint/typescript-estree": "6.18.0", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "../node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.18.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "../node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "../node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true - }, - "../node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true - }, - "../node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true - }, - "../node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "../node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true - }, - "../node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" - } - }, - "../node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "../node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "../node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true - }, - "../node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" - } - }, - "../node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "../node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" - } - }, - "../node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "../node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "../node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "../node_modules/@xtuc/long": { - "version": "4.2.2", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, - "../node_modules/abbrev": { - "version": "1.1.1", - "dev": true, - "license": "ISC" - }, - "../node_modules/abort-controller": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "../node_modules/accepts": { - "version": "1.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/acorn": { - "version": "8.11.2", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "../node_modules/acorn-import-assertions": { - "version": "1.9.0", - "dev": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "../node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "../node_modules/acorn-walk": { - "version": "8.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "../node_modules/agent-base": { - "version": "6.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "../node_modules/agentkeepalive": { - "version": "4.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "../node_modules/aggregate-error": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/ajv": { - "version": "8.12.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "../node_modules/ansi-align": { - "version": "3.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "../node_modules/ansi-colors": { - "version": "4.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/ansi-escapes": { - "version": "4.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/ansi-styles": { - "version": "4.3.0", - "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/ansicolors": { - "version": "0.3.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/anymatch": { - "version": "3.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/aproba": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/arch": { - "version": "2.2.0", - "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/are-we-there-yet": { - "version": "3.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "3.6.2", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/arg": { - "version": "4.1.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "../node_modules/argv-formatter": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/array-flatten": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/array-ify": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/array-union": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/arrify": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/asap": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "../node_modules/asn1": { - "version": "0.2.6", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "../node_modules/assert-plus": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "../node_modules/astral-regex": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/async": { - "version": "3.2.5", - "dev": true, - "license": "MIT" - }, - "../node_modules/async-limiter": { - "version": "1.0.1", - "license": "MIT" - }, - "../node_modules/async-lock": { - "version": "1.4.1", - "license": "MIT" - }, - "../node_modules/asynckit": { - "version": "0.4.0", - "license": "MIT" - }, - "../node_modules/at-least-node": { - "version": "1.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, - "../node_modules/atomic-sleep": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "../node_modules/aws-sign2": { - "version": "0.7.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "../node_modules/aws4": { - "version": "1.12.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/axios": { - "version": "1.6.2", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "../node_modules/babel-jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "../node_modules/babel-jest/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/babel-plugin-istanbul/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "../node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "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.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@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-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../node_modules/babel-preset-jest": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../node_modules/balanced-match": { - "version": "1.0.2", - "license": "MIT" - }, - "../node_modules/base64-js": { - "version": "1.5.1", - "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/bcrypt-pbkdf": { - "version": "1.0.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "../node_modules/before-after-hook": { - "version": "2.2.3", - "dev": true, - "license": "Apache-2.0" - }, - "../node_modules/blob-util": { - "version": "2.0.2", - "dev": true, - "license": "Apache-2.0" - }, - "../node_modules/bluebird": { - "version": "3.7.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/body-parser": { - "version": "1.20.1", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "../node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "../node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/bottleneck": { - "version": "2.19.5", - "license": "MIT" - }, - "../node_modules/boxen": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/boxen/node_modules/camelcase": { - "version": "6.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/brace-expansion": { - "version": "1.1.11", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "../node_modules/braces": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/brotli-size": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexer": "0.1.1" - }, - "engines": { - "node": ">= 10.16.0" - } - }, - "../node_modules/browserslist": { - "version": "4.22.2", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "../node_modules/bs-logger": { - "version": "0.2.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/bser": { - "version": "2.1.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "../node_modules/buffer": { - "version": "6.0.3", - "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", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "../node_modules/buffer-crc32": { - "version": "0.2.13", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "../node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/builtin-modules": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/builtins": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.0.0" - } - }, - "../node_modules/bytes": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/cacache": { - "version": "17.1.4", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^7.7.1", - "minipass": "^7.0.3", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/cacache/node_modules/glob": { - "version": "10.3.10", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/cacache/node_modules/lru-cache": { - "version": "7.18.3", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "../node_modules/cacache/node_modules/minimatch": { - "version": "9.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/cacache/node_modules/minipass": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "../node_modules/cachedir": { - "version": "2.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/call-bind": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/callsites": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/camelcase": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/camelcase-keys": { - "version": "6.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/caniuse-lite": { - "version": "1.0.30001570", - "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/cardinal": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansicolors": "~0.3.2", - "redeyed": "~2.1.0" - }, - "bin": { - "cdl": "bin/cdl.js" - } - }, - "../node_modules/caseless": { - "version": "0.12.0", - "dev": true, - "license": "Apache-2.0" - }, - "../node_modules/chalk": { - "version": "4.1.2", - "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", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "../node_modules/check-more-types": { - "version": "2.24.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/chownr": { - "version": "2.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "../node_modules/chrome-remote-interface": { - "version": "0.27.2", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "2.11.x", - "ws": "^6.1.0" - }, - "bin": { - "chrome-remote-interface": "bin/client.js" - } - }, - "../node_modules/chrome-remote-interface/node_modules/commander": { - "version": "2.11.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/chrome-trace-event": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.0" - } - }, - "../node_modules/ci-info": { - "version": "3.9.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/cjs-module-lexer": { - "version": "1.2.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/clean-stack": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/cli-boxes": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/cli-cursor": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/cli-table3": { - "version": "0.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "../node_modules/cli-truncate": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/cliui": { - "version": "8.0.1", - "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/co": { - "version": "4.6.0", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "../node_modules/collect-v8-coverage": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/collect.js": { - "version": "4.36.1", - "license": "MIT" - }, - "../node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "../node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/color-support": { - "version": "1.1.3", - "dev": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "../node_modules/colorette": { - "version": "2.0.20", - "license": "MIT" - }, - "../node_modules/colors": { - "version": "1.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, - "../node_modules/combined-stream": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/commander": { - "version": "6.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "../node_modules/common-tags": { - "version": "1.8.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "../node_modules/commondir": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/compare-func": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "../node_modules/component-emitter": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/concat-map": { - "version": "0.0.1", - "license": "MIT" - }, - "../node_modules/config-chain": { - "version": "1.1.13", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "../node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "dev": true, - "license": "ISC" - }, - "../node_modules/console-control-strings": { - "version": "1.1.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/content-disposition": { - "version": "0.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/content-type": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/conventional-changelog-angular": { - "version": "7.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "../node_modules/conventional-changelog-conventionalcommits": { - "version": "7.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "../node_modules/conventional-changelog-writer": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-commits-filter": "^4.0.0", - "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "meow": "^12.0.1", - "semver": "^7.5.2", - "split2": "^4.0.0" - }, - "bin": { - "conventional-changelog-writer": "cli.mjs" - }, - "engines": { - "node": ">=16" - } - }, - "../node_modules/conventional-commits-filter": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "../node_modules/conventional-commits-parser": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-text-path": "^2.0.0", - "JSONStream": "^1.3.5", - "meow": "^12.0.1", - "split2": "^4.0.0" - }, - "bin": { - "conventional-commits-parser": "cli.mjs" - }, - "engines": { - "node": ">=16" - } - }, - "../node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/cookie": { - "version": "0.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/cookie-signature": { - "version": "1.0.6", - "dev": true, - "license": "MIT" - }, - "../node_modules/cookiejar": { - "version": "2.1.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/core-util-is": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/cosmiconfig": { - "version": "8.3.6", - "dev": true, - "license": "MIT", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "../node_modules/cosmiconfig-typescript-loader": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jiti": "^1.19.1" - }, - "engines": { - "node": ">=v16" - }, - "peerDependencies": { - "@types/node": "*", - "cosmiconfig": ">=8.2", - "typescript": ">=4" - } - }, - "../node_modules/create-jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/create-require": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/cross-spawn": { - "version": "7.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/crypto-random-string": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/cypress": { - "version": "13.6.2", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@cypress/request": "^3.0.0", - "@cypress/xvfb": "^1.2.4", - "@types/node": "^18.17.5", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.6.0", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-ci": "^3.0.0", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.5.3", - "supports-color": "^8.1.1", - "tmp": "~0.2.1", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - }, - "bin": { - "cypress": "bin/cypress" - }, - "engines": { - "node": "^16.0.0 || ^18.0.0 || >=20.0.0" - } - }, - "../node_modules/cypress-log-to-output": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.4.2", - "chrome-remote-interface": "^0.27.1" - } - }, - "../node_modules/cypress-log-to-output/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/cypress-log-to-output/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/cypress-log-to-output/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "../node_modules/cypress-log-to-output/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/cypress-log-to-output/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "../node_modules/cypress-log-to-output/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/cypress-log-to-output/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/cypress/node_modules/@types/node": { - "version": "18.19.3", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "../node_modules/cypress/node_modules/buffer": { - "version": "5.7.1", - "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", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "../node_modules/cypress/node_modules/execa": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "../node_modules/cypress/node_modules/fs-extra": { - "version": "9.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/cypress/node_modules/get-stream": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/cypress/node_modules/human-signals": { - "version": "1.1.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8.12.0" - } - }, - "../node_modules/cypress/node_modules/proxy-from-env": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/cypress/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "../node_modules/dargs": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/dashdash": { - "version": "1.14.1", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "../node_modules/dateformat": { - "version": "4.6.3", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "../node_modules/dayjs": { - "version": "1.11.10", - "dev": true, - "license": "MIT" - }, - "../node_modules/debug": { - "version": "4.3.4", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "../node_modules/decamelize": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/decamelize-keys": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/dedent": { - "version": "1.5.1", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "../node_modules/deep-extend": { - "version": "0.6.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "../node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/deepmerge": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/define-data-property": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/define-lazy-prop": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/delayed-stream": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "../node_modules/delegates": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/depd": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/deprecation": { - "version": "2.3.1", - "dev": true, - "license": "ISC" - }, - "../node_modules/destroy": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "../node_modules/detect-newline": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/dezalgo": { - "version": "1.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "../node_modules/diff": { - "version": "4.0.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "../node_modules/diff-sequences": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/dir-glob": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/doctrine": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "../node_modules/dot-prop": { - "version": "5.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/duplexer": { - "version": "0.1.1", - "dev": true - }, - "../node_modules/duplexer2": { - "version": "0.1.4", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "readable-stream": "^2.0.2" - } - }, - "../node_modules/duplexer2/node_modules/readable-stream": { - "version": "2.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "../node_modules/duplexer2/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/duplexer2/node_modules/string_decoder": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "../node_modules/eastasianwidth": { - "version": "0.2.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/ecc-jsbn": { - "version": "0.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "../node_modules/ee-first": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/electron-to-chromium": { - "version": "1.4.614", - "dev": true, - "license": "ISC" - }, - "../node_modules/emittery": { - "version": "0.13.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "../node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/emojilib": { - "version": "2.4.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/encodeurl": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/encoding": { - "version": "0.1.13", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "../node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/end-of-stream": { - "version": "1.4.4", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "../node_modules/enhanced-resolve": { - "version": "5.15.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "../node_modules/enquirer": { - "version": "2.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "../node_modules/env-ci": { - "version": "10.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^8.0.0", - "java-properties": "^1.0.2" - }, - "engines": { - "node": "^18.17 || >=20.6.1" - } - }, - "../node_modules/env-ci/node_modules/execa": { - "version": "8.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "../node_modules/env-ci/node_modules/get-stream": { - "version": "8.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/env-ci/node_modules/human-signals": { - "version": "5.0.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "../node_modules/env-ci/node_modules/is-stream": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/env-ci/node_modules/mimic-fn": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/env-ci/node_modules/npm-run-path": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/env-ci/node_modules/onetime": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/env-ci/node_modules/path-key": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/env-ci/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/env-ci/node_modules/strip-final-newline": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/env-paths": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/err-code": { - "version": "2.0.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/error-ex": { - "version": "1.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "../node_modules/es-module-lexer": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "peer": true - }, - "../node_modules/escalade": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/escape-html": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/eslint": { - "version": "8.56.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/eslint-config-prettier": { - "version": "9.1.0", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "../node_modules/eslint-scope": { - "version": "7.2.2", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "../node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/espree": { - "version": "9.6.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/esprima": { - "version": "4.0.1", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/esquery": { - "version": "1.5.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "../node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "../node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "../node_modules/estree-walker": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/etag": { - "version": "1.8.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/event-target-shim": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/eventemitter2": { - "version": "6.4.7", - "dev": true, - "license": "MIT" - }, - "../node_modules/eventemitter3": { - "version": "5.0.1", - "license": "MIT" - }, - "../node_modules/events": { - "version": "3.3.0", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "../node_modules/eventsource": { - "version": "2.0.2", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "../node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "../node_modules/executable": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/exit": { - "version": "0.1.2", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/expect": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/exponential-backoff": { - "version": "3.1.1", - "dev": true, - "license": "Apache-2.0" - }, - "../node_modules/express": { - "version": "4.18.2", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "../node_modules/express/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "../node_modules/express/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/express/node_modules/qs": { - "version": "6.11.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/extend": { - "version": "3.0.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/extract-zip": { - "version": "2.0.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "../node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/extsprintf": { - "version": "1.3.0", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, - "../node_modules/fast-copy": { - "version": "3.0.1", - "license": "MIT" - }, - "../node_modules/fast-deep-equal": { - "version": "3.1.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/fast-glob": { - "version": "3.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "../node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "../node_modules/fast-redact": { - "version": "3.3.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/fast-safe-stringify": { - "version": "2.1.1", - "license": "MIT" - }, - "../node_modules/fastq": { - "version": "1.15.0", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "../node_modules/fb-watchman": { - "version": "2.0.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "../node_modules/fd-slicer": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "../node_modules/figures": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "../node_modules/file-entry-cache": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "../node_modules/filesize": { - "version": "6.4.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 0.4.0" - } - }, - "../node_modules/fill-range": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/finalhandler": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "../node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/find-up-simple": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/find-versions": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver-regex": "^4.0.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/flat-cache": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "../node_modules/flatbuffers": { - "version": "23.5.26", - "license": "SEE LICENSE IN LICENSE" - }, - "../node_modules/flatted": { - "version": "3.2.9", - "dev": true, - "license": "ISC" - }, - "../node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "../node_modules/foreground-child": { - "version": "3.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/forever-agent": { - "version": "0.6.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "../node_modules/form-data": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/formidable": { - "version": "2.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "dezalgo": "^1.0.4", - "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "../node_modules/formidable/node_modules/qs": { - "version": "6.11.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/forwarded": { - "version": "0.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/fresh": { - "version": "0.5.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/from2": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "../node_modules/from2/node_modules/readable-stream": { - "version": "2.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "../node_modules/from2/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/from2/node_modules/string_decoder": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "../node_modules/fs-extra": { - "version": "11.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "../node_modules/fs-minipass": { - "version": "3.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/fs-minipass/node_modules/minipass": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "../node_modules/fs.realpath": { - "version": "1.0.0", - "license": "ISC" - }, - "../node_modules/function-bind": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/gauge": { - "version": "4.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "../node_modules/get-intrinsic": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/get-package-type": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "../node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/getos": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "async": "^3.2.0" - } - }, - "../node_modules/getpass": { - "version": "0.1.7", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "../node_modules/git-log-parser": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "argv-formatter": "~1.0.0", - "spawn-error-forwarder": "~1.0.0", - "split2": "~1.0.0", - "stream-combiner2": "~1.1.1", - "through2": "~2.0.0", - "traverse": "~0.6.6" - } - }, - "../node_modules/git-log-parser/node_modules/readable-stream": { - "version": "2.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "../node_modules/git-log-parser/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/git-log-parser/node_modules/split2": { - "version": "1.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "through2": "~2.0.0" - } - }, - "../node_modules/git-log-parser/node_modules/string_decoder": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "../node_modules/git-log-parser/node_modules/through2": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "../node_modules/git-raw-commits": { - "version": "2.0.11", - "dev": true, - "license": "MIT", - "dependencies": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/git-raw-commits/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/git-raw-commits/node_modules/hosted-git-info": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/git-raw-commits/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/git-raw-commits/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/git-raw-commits/node_modules/meow": { - "version": "8.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/git-raw-commits/node_modules/normalize-package-data": { - "version": "3.0.3", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/git-raw-commits/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/git-raw-commits/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/git-raw-commits/node_modules/read-pkg": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/git-raw-commits/node_modules/read-pkg-up": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/git-raw-commits/node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "../node_modules/git-raw-commits/node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "dev": true, - "license": "ISC" - }, - "../node_modules/git-raw-commits/node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "../node_modules/git-raw-commits/node_modules/read-pkg/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "../node_modules/git-raw-commits/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "../node_modules/git-raw-commits/node_modules/readable-stream": { - "version": "3.6.2", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/git-raw-commits/node_modules/split2": { - "version": "3.2.2", - "dev": true, - "license": "ISC", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "../node_modules/git-raw-commits/node_modules/type-fest": { - "version": "0.18.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/git-raw-commits/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/git-raw-commits/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "../node_modules/glob": { - "version": "8.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "../node_modules/glob-to-regexp": { - "version": "0.4.1", - "dev": true, - "license": "BSD-2-Clause", - "peer": true - }, - "../node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/global-dirs": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/globals": { - "version": "13.24.0", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/globby": { - "version": "11.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/globby/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/gopd": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/graceful-fs": { - "version": "4.2.11", - "dev": true, - "license": "ISC" - }, - "../node_modules/graphemer": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/gzip-size": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/gzip-size/node_modules/duplexer": { - "version": "0.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/handlebars": { - "version": "4.7.8", - "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" - } - }, - "../node_modules/hard-rejection": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/has-property-descriptors": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/has-proto": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/has-symbols": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/has-unicode": { - "version": "2.0.1", - "dev": true, - "license": "ISC" - }, - "../node_modules/hasown": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/help-me": { - "version": "5.0.0", - "license": "MIT" - }, - "../node_modules/hexoid": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/hook-std": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/hosted-git-info": { - "version": "6.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^7.5.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "7.18.3", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "../node_modules/html-escaper": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/http-cache-semantics": { - "version": "4.1.1", - "dev": true, - "license": "BSD-2-Clause" - }, - "../node_modules/http-errors": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/http-proxy-agent": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/http-signature": { - "version": "1.3.6", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.14.1" - }, - "engines": { - "node": ">=0.10" - } - }, - "../node_modules/https-proxy-agent": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "../node_modules/humanize-ms": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "../node_modules/husky": { - "version": "8.0.3", - "dev": true, - "license": "MIT", - "bin": { - "husky": "lib/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "../node_modules/iconv-lite": { - "version": "0.4.24", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/ieee754": { - "version": "1.2.1", - "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": "BSD-3-Clause" - }, - "../node_modules/ignore": { - "version": "5.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "../node_modules/ignore-walk": { - "version": "6.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/import-fresh": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/import-from-esm": { - "version": "1.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "import-meta-resolve": "^4.0.0" - }, - "engines": { - "node": ">=16.20" - } - }, - "../node_modules/import-local": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/import-meta-resolve": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "../node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "../node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/index-to-position": { - "version": "0.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/infer-owner": { - "version": "1.0.4", - "dev": true, - "license": "ISC" - }, - "../node_modules/inflight": { - "version": "1.0.6", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "../node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "../node_modules/ini": { - "version": "2.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "../node_modules/into-stream": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/ip": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/ip-num": { - "version": "1.5.1", - "license": "MIT" - }, - "../node_modules/ipaddr.js": { - "version": "1.9.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "../node_modules/is-arrayish": { - "version": "0.2.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/is-builtin-module": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/is-ci": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "../node_modules/is-core-module": { - "version": "2.13.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/is-docker": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/is-generator-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/is-installed-globally": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/is-lambda": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/is-module": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/is-number": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "../node_modules/is-obj": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/is-path-inside": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/is-plain-obj": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/is-reference": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "../node_modules/is-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/is-text-path": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "text-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/is-typedarray": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/is-unicode-supported": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/is-wsl": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/isarray": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/isstream": { - "version": "0.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/issue-parser": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.capitalize": "^4.2.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.uniqby": "^4.7.0" - }, - "engines": { - "node": ">=10.13" - } - }, - "../node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "../node_modules/istanbul-lib-instrument": { - "version": "6.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/istanbul-lib-report": { - "version": "3.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/istanbul-reports": { - "version": "3.1.6", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/jackspeak": { - "version": "2.3.6", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "../node_modules/java-properties": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, - "../node_modules/jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "../node_modules/jest-changed-files": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-circus": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-circus/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/jest-cli": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "../node_modules/jest-config": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "../node_modules/jest-config/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/jest-config/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/jest-diff": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-docblock": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-each": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-environment-node": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-get-type": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-haste-map": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "../node_modules/jest-junit": { - "version": "16.0.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "mkdirp": "^1.0.4", - "strip-ansi": "^6.0.1", - "uuid": "^8.3.2", - "xml": "^1.0.1" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "../node_modules/jest-junit/node_modules/uuid": { - "version": "8.3.2", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "../node_modules/jest-leak-detector": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-matcher-utils": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-message-util": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/jest-mock": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "../node_modules/jest-regex-util": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-resolve": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-resolve/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/jest-runner": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-runtime": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-runtime/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/jest-runtime/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/jest-snapshot": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-util": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-validate": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/jest-watcher": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-worker": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "../node_modules/jiti": { - "version": "1.21.0", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "../node_modules/joycon": { - "version": "3.1.1", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "../node_modules/js-sha3": { - "version": "0.9.3", - "license": "MIT" - }, - "../node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/js-yaml": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "../node_modules/jsbn": { - "version": "0.1.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/jsesc": { - "version": "2.5.2", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/json-parse-better-errors": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/json-schema": { - "version": "0.4.0", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "../node_modules/json-schema-traverse": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/json-stringify-safe": { - "version": "5.0.1", - "dev": true, - "license": "ISC" - }, - "../node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "../node_modules/jsonc-parser": { - "version": "3.2.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/jsonfile": { - "version": "6.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "../node_modules/jsonparse": { - "version": "1.3.1", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" - }, - "../node_modules/JSONStream": { - "version": "1.3.5", - "dev": true, - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, - "../node_modules/jsprim": { - "version": "2.0.2", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "../node_modules/keyv": { - "version": "4.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "../node_modules/kind-of": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/kleur": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/lazy-ass": { - "version": "1.6.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "> 0.8" - } - }, - "../node_modules/leven": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/lines-and-columns": { - "version": "1.2.4", - "dev": true, - "license": "MIT" - }, - "../node_modules/listr2": { - "version": "3.14.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, - "../node_modules/load-json-file": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/load-json-file/node_modules/parse-json": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/load-json-file/node_modules/pify": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/load-json-file/node_modules/strip-bom": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/loader-runner": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.11.5" - } - }, - "../node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/lodash": { - "version": "4.17.21", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash-es": { - "version": "4.17.21", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.camelcase": { - "version": "4.3.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.capitalize": { - "version": "4.2.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.escaperegexp": { - "version": "4.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.isfunction": { - "version": "3.0.9", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.isplainobject": { - "version": "4.0.6", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.isstring": { - "version": "4.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.kebabcase": { - "version": "4.1.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.memoize": { - "version": "4.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.merge": { - "version": "4.6.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.mergewith": { - "version": "4.6.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.once": { - "version": "4.1.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.snakecase": { - "version": "4.1.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.startcase": { - "version": "4.4.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.uniq": { - "version": "4.5.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.uniqby": { - "version": "4.7.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/lodash.upperfirst": { - "version": "4.3.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/log-symbols": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/log-update": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "../node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/lru-cache": { - "version": "5.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "../node_modules/lunr": { - "version": "2.3.9", - "dev": true, - "license": "MIT" - }, - "../node_modules/magic-string": { - "version": "0.30.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "../node_modules/make-dir": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/make-error": { - "version": "1.3.6", - "dev": true, - "license": "ISC" - }, - "../node_modules/make-fetch-happen": { - "version": "10.2.1", - "dev": true, - "license": "ISC", - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/make-fetch-happen/node_modules/@npmcli/fs": { - "version": "2.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/make-fetch-happen/node_modules/cacache": { - "version": "16.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/make-fetch-happen/node_modules/fs-minipass": { - "version": "2.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.18.3", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "../node_modules/make-fetch-happen/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/make-fetch-happen/node_modules/ssri": { - "version": "9.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/make-fetch-happen/node_modules/unique-filename": { - "version": "2.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/make-fetch-happen/node_modules/unique-slug": { - "version": "3.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/make-fetch-happen/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/makeerror": { - "version": "1.0.12", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "../node_modules/map-obj": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/marked": { - "version": "9.1.6", - "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 16" - } - }, - "../node_modules/marked-terminal": { - "version": "6.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^6.2.0", - "cardinal": "^2.1.1", - "chalk": "^5.3.0", - "cli-table3": "^0.6.3", - "node-emoji": "^2.1.3", - "supports-hyperlinks": "^3.0.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "marked": ">=1 <12" - } - }, - "../node_modules/marked-terminal/node_modules/ansi-escapes": { - "version": "6.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/marked-terminal/node_modules/chalk": { - "version": "5.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "../node_modules/marked-terminal/node_modules/type-fest": { - "version": "3.13.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/media-typer": { - "version": "0.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/meow": { - "version": "12.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/merge-descriptors": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/merge-stream": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/merge2": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "../node_modules/methods": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/micromatch": { - "version": "4.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "../node_modules/mime": { - "version": "4.0.1", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa" - ], - "license": "MIT", - "bin": { - "mime": "bin/cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "../node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/mime-types": { - "version": "2.1.35", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/mimic-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/min-indent": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/minimatch": { - "version": "3.1.2", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "../node_modules/minimist": { - "version": "1.2.8", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/minimist-options": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/minipass": { - "version": "5.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "../node_modules/minipass-collect": { - "version": "1.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/minipass-collect/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/minipass-fetch": { - "version": "2.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "../node_modules/minipass-fetch/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/minipass-fetch/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/minipass-flush": { - "version": "1.0.5", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/minipass-json-stream": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "../node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/minipass-json-stream/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/minipass-pipeline": { - "version": "1.2.4", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/minipass-sized": { - "version": "1.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/minipass-sized/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/minizlib": { - "version": "2.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/mkdirp": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/ms": { - "version": "2.1.2", - "license": "MIT" - }, - "../node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/negotiator": { - "version": "0.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/neo-async": { - "version": "2.6.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/nerf-dart": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/node-emoji": { - "version": "2.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.6.0", - "char-regex": "^1.0.2", - "emojilib": "^2.4.0", - "skin-tone": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "../node_modules/node-gyp": { - "version": "9.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^12.13 || ^14.13 || >=16" - } - }, - "../node_modules/node-gyp/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/node-int64": { - "version": "0.4.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/node-releases": { - "version": "2.0.14", - "dev": true, - "license": "MIT" - }, - "../node_modules/nopt": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "^1.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/normalize-package-data": { - "version": "5.0.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^6.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/normalize-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/normalize-url": { - "version": "8.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/npm": { - "version": "10.2.5", - "bundleDependencies": [ - "@isaacs/string-locale-compare", - "@npmcli/arborist", - "@npmcli/config", - "@npmcli/fs", - "@npmcli/map-workspaces", - "@npmcli/package-json", - "@npmcli/promise-spawn", - "@npmcli/run-script", - "@sigstore/tuf", - "abbrev", - "archy", - "cacache", - "chalk", - "ci-info", - "cli-columns", - "cli-table3", - "columnify", - "fastest-levenshtein", - "fs-minipass", - "glob", - "graceful-fs", - "hosted-git-info", - "ini", - "init-package-json", - "is-cidr", - "json-parse-even-better-errors", - "libnpmaccess", - "libnpmdiff", - "libnpmexec", - "libnpmfund", - "libnpmhook", - "libnpmorg", - "libnpmpack", - "libnpmpublish", - "libnpmsearch", - "libnpmteam", - "libnpmversion", - "make-fetch-happen", - "minimatch", - "minipass", - "minipass-pipeline", - "ms", - "node-gyp", - "nopt", - "normalize-package-data", - "npm-audit-report", - "npm-install-checks", - "npm-package-arg", - "npm-pick-manifest", - "npm-profile", - "npm-registry-fetch", - "npm-user-validate", - "npmlog", - "p-map", - "pacote", - "parse-conflict-json", - "proc-log", - "qrcode-terminal", - "read", - "semver", - "spdx-expression-parse", - "ssri", - "strip-ansi", - "supports-color", - "tar", - "text-table", - "tiny-relative-date", - "treeverse", - "validate-npm-package-name", - "which", - "write-file-atomic" - ], - "dev": true, - "license": "Artistic-2.0", - "workspaces": [ - "docs", - "smoke-tests", - "mock-globals", - "mock-registry", - "workspaces/*" - ], - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^7.2.1", - "@npmcli/config": "^8.0.2", - "@npmcli/fs": "^3.1.0", - "@npmcli/map-workspaces": "^3.0.4", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.2", - "@sigstore/tuf": "^2.2.0", - "abbrev": "^2.0.0", - "archy": "~1.0.0", - "cacache": "^18.0.1", - "chalk": "^5.3.0", - "ci-info": "^4.0.0", - "cli-columns": "^4.0.0", - "cli-table3": "^0.6.3", - "columnify": "^1.6.0", - "fastest-levenshtein": "^1.0.16", - "fs-minipass": "^3.0.3", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "hosted-git-info": "^7.0.1", - "ini": "^4.1.1", - "init-package-json": "^6.0.0", - "is-cidr": "^5.0.3", - "json-parse-even-better-errors": "^3.0.1", - "libnpmaccess": "^8.0.1", - "libnpmdiff": "^6.0.3", - "libnpmexec": "^7.0.4", - "libnpmfund": "^5.0.1", - "libnpmhook": "^10.0.0", - "libnpmorg": "^6.0.1", - "libnpmpack": "^6.0.3", - "libnpmpublish": "^9.0.2", - "libnpmsearch": "^7.0.0", - "libnpmteam": "^6.0.0", - "libnpmversion": "^5.0.1", - "make-fetch-happen": "^13.0.0", - "minimatch": "^9.0.3", - "minipass": "^7.0.4", - "minipass-pipeline": "^1.2.4", - "ms": "^2.1.2", - "node-gyp": "^10.0.1", - "nopt": "^7.2.0", - "normalize-package-data": "^6.0.0", - "npm-audit-report": "^5.0.0", - "npm-install-checks": "^6.3.0", - "npm-package-arg": "^11.0.1", - "npm-pick-manifest": "^9.0.0", - "npm-profile": "^9.0.0", - "npm-registry-fetch": "^16.1.0", - "npm-user-validate": "^2.0.0", - "npmlog": "^7.0.1", - "p-map": "^4.0.0", - "pacote": "^17.0.5", - "parse-conflict-json": "^3.0.1", - "proc-log": "^3.0.0", - "qrcode-terminal": "^0.12.0", - "read": "^2.1.0", - "semver": "^7.5.4", - "spdx-expression-parse": "^3.0.1", - "ssri": "^10.0.5", - "strip-ansi": "^7.1.0", - "supports-color": "^9.4.0", - "tar": "^6.2.0", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "treeverse": "^3.0.0", - "validate-npm-package-name": "^5.0.0", - "which": "^4.0.0", - "write-file-atomic": "^5.0.1" - }, - "bin": { - "npm": "bin/npm-cli.js", - "npx": "bin/npx-cli.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "../node_modules/npm-bundled": { - "version": "3.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm-install-checks": { - "version": "6.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm-package-arg": { - "version": "10.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm-packlist": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^6.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm-pick-manifest": { - "version": "8.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^10.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm-registry-fetch": { - "version": "14.0.5", - "dev": true, - "license": "ISC", - "dependencies": { - "make-fetch-happen": "^11.0.0", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^10.0.0", - "proc-log": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm-registry-fetch/node_modules/lru-cache": { - "version": "7.18.3", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "../node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { - "version": "11.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm-registry-fetch/node_modules/minipass-fetch": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "../node_modules/npm-registry-fetch/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "../node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/@colors/colors": { - "version": "1.5.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "../node_modules/npm/node_modules/@isaacs/cliui": { - "version": "8.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "../node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/npm/node_modules/@isaacs/string-locale-compare": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "../node_modules/npm/node_modules/@npmcli/agent": { - "version": "2.2.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/arborist": { - "version": "7.2.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^3.1.0", - "@npmcli/installed-package-contents": "^2.0.2", - "@npmcli/map-workspaces": "^3.0.2", - "@npmcli/metavuln-calculator": "^7.0.0", - "@npmcli/name-from-folder": "^2.0.0", - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/query": "^3.0.1", - "@npmcli/run-script": "^7.0.2", - "bin-links": "^4.0.1", - "cacache": "^18.0.0", - "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^7.0.1", - "json-parse-even-better-errors": "^3.0.0", - "json-stringify-nice": "^1.1.4", - "minimatch": "^9.0.0", - "nopt": "^7.0.0", - "npm-install-checks": "^6.2.0", - "npm-package-arg": "^11.0.1", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "npmlog": "^7.0.1", - "pacote": "^17.0.4", - "parse-conflict-json": "^3.0.0", - "proc-log": "^3.0.0", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^1.0.2", - "read-package-json-fast": "^3.0.2", - "semver": "^7.3.7", - "ssri": "^10.0.5", - "treeverse": "^3.0.0", - "walk-up-path": "^3.0.1" - }, - "bin": { - "arborist": "bin/index.js" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/config": { - "version": "8.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/map-workspaces": "^3.0.2", - "ci-info": "^4.0.0", - "ini": "^4.1.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", - "read-package-json-fast": "^3.0.2", - "semver": "^7.3.5", - "walk-up-path": "^3.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/disparity-colors": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ansi-styles": "^4.3.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/disparity-colors/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../node_modules/npm/node_modules/@npmcli/fs": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/git": { - "version": "5.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^3.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^4.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "2.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "bin": { - "installed-package-contents": "lib/index.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "3.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/name-from-folder": "^2.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0", - "read-package-json-fast": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cacache": "^18.0.0", - "json-parse-even-better-errors": "^3.0.0", - "pacote": "^17.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/package-json": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^5.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^3.0.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "which": "^4.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/query": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@npmcli/run-script": { - "version": "7.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^4.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "../node_modules/npm/node_modules/@sigstore/bundle": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@sigstore/sign": { - "version": "2.2.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^2.1.0", - "@sigstore/protobuf-specs": "^0.2.1", - "make-fetch-happen": "^13.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@sigstore/tuf": { - "version": "2.2.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1", - "tuf-js": "^2.1.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/@tufjs/models": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.3" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/abbrev": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/abort-controller": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "../node_modules/npm/node_modules/agent-base": { - "version": "7.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "../node_modules/npm/node_modules/aggregate-error": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/ansi-regex": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "../node_modules/npm/node_modules/ansi-styles": { - "version": "6.2.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../node_modules/npm/node_modules/aproba": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "../node_modules/npm/node_modules/archy": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/are-we-there-yet": { - "version": "4.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^4.1.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/base64-js": { - "version": "1.5.1", - "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" - } - ], - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/bin-links": { - "version": "4.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cmd-shim": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "read-cmd-shim": "^4.0.0", - "write-file-atomic": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/binary-extensions": { - "version": "2.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/npm/node_modules/buffer": { - "version": "6.0.3", - "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" - } - ], - "inBundle": true, - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "../node_modules/npm/node_modules/builtins": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "semver": "^7.0.0" - } - }, - "../node_modules/npm/node_modules/cacache": { - "version": "18.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/chalk": { - "version": "5.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "../node_modules/npm/node_modules/chownr": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "../node_modules/npm/node_modules/ci-info": { - "version": "4.0.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/cidr-regex": { - "version": "4.0.3", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "ip-regex": "^5.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "../node_modules/npm/node_modules/clean-stack": { - "version": "2.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/npm/node_modules/cli-columns": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "../node_modules/npm/node_modules/cli-columns/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/cli-columns/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/cli-table3": { - "version": "0.6.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "../node_modules/npm/node_modules/clone": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "../node_modules/npm/node_modules/cmd-shim": { - "version": "6.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "../node_modules/npm/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/color-support": { - "version": "1.1.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "../node_modules/npm/node_modules/columnify": { - "version": "1.6.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "strip-ansi": "^6.0.1", - "wcwidth": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "../node_modules/npm/node_modules/columnify/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/columnify/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "../node_modules/npm/node_modules/console-control-strings": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "../node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/npm/node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/npm/node_modules/cssesc": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/npm/node_modules/debug": { - "version": "4.3.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "../node_modules/npm/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/defaults": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/npm/node_modules/delegates": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/diff": { - "version": "5.1.0", - "dev": true, - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "../node_modules/npm/node_modules/eastasianwidth": { - "version": "0.2.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/encoding": { - "version": "0.1.13", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "../node_modules/npm/node_modules/env-paths": { - "version": "2.2.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/npm/node_modules/err-code": { - "version": "2.0.3", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/event-target-shim": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/npm/node_modules/events": { - "version": "3.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "../node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "Apache-2.0" - }, - "../node_modules/npm/node_modules/fastest-levenshtein": { - "version": "1.0.16", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "../node_modules/npm/node_modules/foreground-child": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/npm/node_modules/fs-minipass": { - "version": "3.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/function-bind": { - "version": "1.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/npm/node_modules/gauge": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^4.0.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/gauge/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/gauge/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/glob": { - "version": "10.3.10", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/npm/node_modules/graceful-fs": { - "version": "4.2.11", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "../node_modules/npm/node_modules/has-unicode": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "../node_modules/npm/node_modules/hasown": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/npm/node_modules/hosted-git-info": { - "version": "7.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.1.1", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause" - }, - "../node_modules/npm/node_modules/http-proxy-agent": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "../node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "../node_modules/npm/node_modules/iconv-lite": { - "version": "0.6.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/npm/node_modules/ieee754": { - "version": "1.2.1", - "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" - } - ], - "inBundle": true, - "license": "BSD-3-Clause" - }, - "../node_modules/npm/node_modules/ignore-walk": { - "version": "6.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "../node_modules/npm/node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/ini": { - "version": "4.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/init-package-json": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-package-arg": "^11.0.0", - "promzard": "^1.0.0", - "read": "^2.0.0", - "read-package-json": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^5.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/ip": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/ip-regex": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/npm/node_modules/is-cidr": { - "version": "5.0.3", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "cidr-regex": "4.0.3" - }, - "engines": { - "node": ">=14" - } - }, - "../node_modules/npm/node_modules/is-core-module": { - "version": "2.13.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/is-lambda": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "../node_modules/npm/node_modules/jackspeak": { - "version": "2.3.6", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "../node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/json-stringify-nice": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/npm/node_modules/jsonparse": { - "version": "1.3.1", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/just-diff": { - "version": "6.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/just-diff-apply": { - "version": "5.5.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/libnpmaccess": { - "version": "8.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-package-arg": "^11.0.1", - "npm-registry-fetch": "^16.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/libnpmdiff": { - "version": "6.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^7.2.1", - "@npmcli/disparity-colors": "^3.0.0", - "@npmcli/installed-package-contents": "^2.0.2", - "binary-extensions": "^2.2.0", - "diff": "^5.1.0", - "minimatch": "^9.0.0", - "npm-package-arg": "^11.0.1", - "pacote": "^17.0.4", - "tar": "^6.2.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/libnpmexec": { - "version": "7.0.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^7.2.1", - "@npmcli/run-script": "^7.0.2", - "ci-info": "^4.0.0", - "npm-package-arg": "^11.0.1", - "npmlog": "^7.0.1", - "pacote": "^17.0.4", - "proc-log": "^3.0.0", - "read": "^2.0.0", - "read-package-json-fast": "^3.0.2", - "semver": "^7.3.7", - "walk-up-path": "^3.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/libnpmfund": { - "version": "5.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^7.2.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/libnpmhook": { - "version": "10.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/libnpmorg": { - "version": "6.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/libnpmpack": { - "version": "6.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^7.2.1", - "@npmcli/run-script": "^7.0.2", - "npm-package-arg": "^11.0.1", - "pacote": "^17.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/libnpmpublish": { - "version": "9.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ci-info": "^4.0.0", - "normalize-package-data": "^6.0.0", - "npm-package-arg": "^11.0.1", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.7", - "sigstore": "^2.1.0", - "ssri": "^10.0.5" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/libnpmsearch": { - "version": "7.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^16.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/libnpmteam": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/libnpmversion": { - "version": "5.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^5.0.3", - "@npmcli/run-script": "^7.0.2", - "json-parse-even-better-errors": "^3.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/lru-cache": { - "version": "10.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } - }, - "../node_modules/npm/node_modules/make-fetch-happen": { - "version": "13.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", - "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", - "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/minimatch": { - "version": "9.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/npm/node_modules/minipass": { - "version": "7.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "../node_modules/npm/node_modules/minipass-collect": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "../node_modules/npm/node_modules/minipass-fetch": { - "version": "3.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "../node_modules/npm/node_modules/minipass-flush": { - "version": "1.0.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/minipass-json-stream": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "../node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/minipass-pipeline": { - "version": "1.2.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/minizlib": { - "version": "2.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/npm/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/npm/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/mute-stream": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/negotiator": { - "version": "0.6.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/npm/node_modules/node-gyp": { - "version": "10.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^4.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/nopt": { - "version": "7.2.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/normalize-package-data": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/npm-audit-report": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/npm-bundled": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/npm-install-checks": { - "version": "6.3.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/npm-package-arg": { - "version": "11.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/npm-packlist": { - "version": "8.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^6.0.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/npm-pick-manifest": { - "version": "9.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/npm-profile": { - "version": "9.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/npm-registry-fetch": { - "version": "16.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "make-fetch-happen": "^13.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/npm-user-validate": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/npmlog": { - "version": "7.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^4.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^5.0.0", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/p-map": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/npm/node_modules/pacote": { - "version": "17.0.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.0", - "cacache": "^18.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^7.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^2.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "lib/bin.js" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/parse-conflict-json": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "just-diff": "^6.0.0", - "just-diff-apply": "^5.2.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/path-scurry": { - "version": "1.10.1", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.0.13", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/npm/node_modules/proc-log": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/process": { - "version": "0.11.10", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, - "../node_modules/npm/node_modules/promise-all-reject-late": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/npm/node_modules/promise-call-limit": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "../node_modules/npm/node_modules/promise-retry": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/npm/node_modules/promzard": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "read": "^2.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/qrcode-terminal": { - "version": "0.12.0", - "dev": true, - "inBundle": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" - } - }, - "../node_modules/npm/node_modules/read": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "mute-stream": "~1.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/read-cmd-shim": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/read-package-json": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/read-package-json-fast": { - "version": "3.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/readable-stream": { - "version": "4.4.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "../node_modules/npm/node_modules/retry": { - "version": "0.12.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "../node_modules/npm/node_modules/safe-buffer": { - "version": "5.2.1", - "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" - } - ], - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/safer-buffer": { - "version": "2.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true - }, - "../node_modules/npm/node_modules/semver": { - "version": "7.5.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/npm/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/npm/node_modules/set-blocking": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "../node_modules/npm/node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/npm/node_modules/sigstore": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^2.1.0", - "@sigstore/protobuf-specs": "^0.2.1", - "@sigstore/sign": "^2.1.0", - "@sigstore/tuf": "^2.1.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/smart-buffer": { - "version": "4.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "../node_modules/npm/node_modules/socks": { - "version": "2.7.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "../node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "socks": "^2.7.1" - }, - "engines": { - "node": ">= 14" - } - }, - "../node_modules/npm/node_modules/spdx-correct": { - "version": "3.2.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "../node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.3.0", - "dev": true, - "inBundle": true, - "license": "CC-BY-3.0" - }, - "../node_modules/npm/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "../node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.16", - "dev": true, - "inBundle": true, - "license": "CC0-1.0" - }, - "../node_modules/npm/node_modules/ssri": { - "version": "10.0.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/string_decoder": { - "version": "1.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "../node_modules/npm/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/strip-ansi": { - "version": "7.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "../node_modules/npm/node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/supports-color": { - "version": "9.4.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "../node_modules/npm/node_modules/tar": { - "version": "6.2.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/npm/node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/text-table": { - "version": "0.2.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/treeverse": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/tuf-js": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "@tufjs/models": "2.0.0", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/unique-filename": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^4.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/unique-slug": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "../node_modules/npm/node_modules/validate-npm-package-name": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "builtins": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/walk-up-path": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "../node_modules/npm/node_modules/wcwidth": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "../node_modules/npm/node_modules/which": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/which/node_modules/isexe": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16" - } - }, - "../node_modules/npm/node_modules/wide-align": { - "version": "1.1.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "../node_modules/npm/node_modules/wrap-ansi": { - "version": "8.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "../node_modules/npm/node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "dev": true, - "inBundle": 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/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "../node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/npm/node_modules/write-file-atomic": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/npm/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "../node_modules/npmlog": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/object-inspect": { - "version": "1.13.1", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/on-exit-leak-free": { - "version": "2.1.2", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "../node_modules/on-finished": { - "version": "2.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/once": { - "version": "1.4.0", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "../node_modules/onetime": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/open": { - "version": "8.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/optionator": { - "version": "0.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/ospath": { - "version": "1.2.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/p-each-series": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-filter": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-map": "^5.1.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-filter/node_modules/aggregate-error": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^4.0.0", - "indent-string": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-filter/node_modules/clean-stack": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-filter/node_modules/escape-string-regexp": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-filter/node_modules/indent-string": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-filter/node_modules/p-map": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-is-promise": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-map": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-reduce": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/p-try": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/pacote": { - "version": "15.2.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^4.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^6.0.1", - "@npmcli/run-script": "^6.0.0", - "cacache": "^17.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^5.0.0", - "npm-package-arg": "^10.0.0", - "npm-packlist": "^7.0.0", - "npm-pick-manifest": "^8.0.0", - "npm-registry-fetch": "^14.0.0", - "proc-log": "^3.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^6.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^1.3.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "lib/bin.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/parent-module": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "../node_modules/parse-json": { - "version": "5.2.0", - "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", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/path-is-absolute": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/path-parse": { - "version": "1.0.7", - "dev": true, - "license": "MIT" - }, - "../node_modules/path-scurry": { - "version": "1.10.1", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } - }, - "../node_modules/path-to-regexp": { - "version": "0.1.7", - "dev": true, - "license": "MIT" - }, - "../node_modules/path-type": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/pend": { - "version": "1.2.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/performance-now": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/picocolors": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/picomatch": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "../node_modules/pify": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/pino": { - "version": "8.17.1", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "v1.1.0", - "pino-std-serializers": "^6.0.0", - "process-warning": "^2.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^3.7.0", - "thread-stream": "^2.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "../node_modules/pino-abstract-transport": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "readable-stream": "^4.0.0", - "split2": "^4.0.0" - } - }, - "../node_modules/pino-pretty": { - "version": "10.3.0", - "license": "MIT", - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^3.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.0.0", - "pump": "^3.0.0", - "readable-stream": "^4.0.0", - "secure-json-parse": "^2.4.0", - "sonic-boom": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "../node_modules/pino-std-serializers": { - "version": "6.2.2", - "license": "MIT" - }, - "../node_modules/pirates": { - "version": "4.0.6", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "../node_modules/pkg-conf": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^2.0.0", - "load-json-file": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/pkg-conf/node_modules/find-up": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/pkg-conf/node_modules/locate-path": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/pkg-conf/node_modules/p-limit": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/pkg-conf/node_modules/p-locate": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/pkg-conf/node_modules/p-try": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/pkg-conf/node_modules/path-exists": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/pkg-dir": { - "version": "4.2.0", - "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", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/prettier": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "../node_modules/pretty-bytes": { - "version": "5.6.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/pretty-format": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "../node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../node_modules/proc-log": { - "version": "3.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/process": { - "version": "0.11.10", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, - "../node_modules/process-nextick-args": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/process-warning": { - "version": "2.3.2", - "license": "MIT" - }, - "../node_modules/promise-inflight": { - "version": "1.0.1", - "dev": true, - "license": "ISC" - }, - "../node_modules/promise-retry": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/prompts": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/proto-list": { - "version": "1.2.4", - "dev": true, - "license": "ISC" - }, - "../node_modules/proxy-addr": { - "version": "2.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "../node_modules/proxy-from-env": { - "version": "1.1.0", - "license": "MIT" - }, - "../node_modules/psl": { - "version": "1.9.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/pump": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "../node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/pure-rand": { - "version": "6.0.4", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "../node_modules/qs": { - "version": "6.10.4", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/querystringify": { - "version": "2.2.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/queue-microtask": { - "version": "1.2.3", - "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/quick-format-unescaped": { - "version": "4.0.4", - "license": "MIT" - }, - "../node_modules/quick-lru": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/randombytes": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "../node_modules/range-parser": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/raw-body": { - "version": "2.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/rc": { - "version": "1.2.8", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "../node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "dev": true, - "license": "ISC" - }, - "../node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/react-is": { - "version": "18.2.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/read-package-json": { - "version": "6.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^5.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/read-package-json-fast": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/read-package-json/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/read-package-json/node_modules/glob": { - "version": "10.3.10", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/read-package-json/node_modules/json-parse-even-better-errors": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/read-package-json/node_modules/minimatch": { - "version": "9.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/read-pkg": { - "version": "9.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.3", - "normalize-package-data": "^6.0.0", - "parse-json": "^8.0.0", - "type-fest": "^4.6.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/read-pkg-up": { - "version": "11.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up-simple": "^1.0.0", - "read-pkg": "^9.0.0", - "type-fest": "^4.6.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/read-pkg-up/node_modules/type-fest": { - "version": "4.8.3", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "7.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/read-pkg/node_modules/lru-cache": { - "version": "10.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } - }, - "../node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "6.0.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/read-pkg/node_modules/parse-json": { - "version": "8.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.22.13", - "index-to-position": "^0.1.2", - "type-fest": "^4.7.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/read-pkg/node_modules/type-fest": { - "version": "4.8.3", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/readable-stream": { - "version": "4.4.2", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "../node_modules/real-require": { - "version": "0.2.0", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "../node_modules/redent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/redeyed": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "esprima": "~4.0.0" - } - }, - "../node_modules/regenerator-runtime": { - "version": "0.14.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/registry-auth-token": { - "version": "5.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@pnpm/npm-conf": "^2.1.0" - }, - "engines": { - "node": ">=14" - } - }, - "../node_modules/request-progress": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "throttleit": "^1.0.0" - } - }, - "../node_modules/require-directory": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/require-from-string": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/requires-port": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/resolve": { - "version": "1.22.8", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/resolve-cwd": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/resolve-global": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "global-dirs": "^0.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/resolve-global/node_modules/global-dirs": { - "version": "0.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/resolve-global/node_modules/ini": { - "version": "1.3.8", - "dev": true, - "license": "ISC" - }, - "../node_modules/resolve.exports": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "../node_modules/restore-cursor": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/retry": { - "version": "0.12.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "../node_modules/reusify": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "../node_modules/rfdc": { - "version": "1.3.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/rimraf": { - "version": "3.0.2", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/rollup": { - "version": "4.9.1", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.1", - "@rollup/rollup-android-arm64": "4.9.1", - "@rollup/rollup-darwin-arm64": "4.9.1", - "@rollup/rollup-darwin-x64": "4.9.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.1", - "@rollup/rollup-linux-arm64-gnu": "4.9.1", - "@rollup/rollup-linux-arm64-musl": "4.9.1", - "@rollup/rollup-linux-riscv64-gnu": "4.9.1", - "@rollup/rollup-linux-x64-gnu": "4.9.1", - "@rollup/rollup-linux-x64-musl": "4.9.1", - "@rollup/rollup-win32-arm64-msvc": "4.9.1", - "@rollup/rollup-win32-ia32-msvc": "4.9.1", - "@rollup/rollup-win32-x64-msvc": "4.9.1", - "fsevents": "~2.3.2" - } - }, - "../node_modules/rollup-plugin-filesize": { - "version": "10.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.8", - "boxen": "^5.0.0", - "brotli-size": "4.0.0", - "colors": "1.4.0", - "filesize": "^6.1.0", - "gzip-size": "^6.0.0", - "pacote": "^15.1.1", - "terser": "^5.6.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "../node_modules/rollup-plugin-ignore": { - "version": "1.0.10", - "dev": true, - "license": "MIT" - }, - "../node_modules/rollup-plugin-polyfill-node": { - "version": "0.13.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/plugin-inject": "^5.0.4" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" - } - }, - "../node_modules/rollup-plugin-visualizer": { - "version": "5.12.0", - "dev": true, - "license": "MIT", - "dependencies": { - "open": "^8.4.0", - "picomatch": "^2.3.1", - "source-map": "^0.7.4", - "yargs": "^17.5.1" - }, - "bin": { - "rollup-plugin-visualizer": "dist/bin/cli.js" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "rollup": "2.x || 3.x || 4.x" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "../node_modules/rollup-plugin-visualizer/node_modules/source-map": { - "version": "0.7.4", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, - "../node_modules/run-parallel": { - "version": "1.2.0", - "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", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "../node_modules/rxjs": { - "version": "7.8.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "../node_modules/safe-buffer": { - "version": "5.2.1", - "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.4.3", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "../node_modules/safer-buffer": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/schema-utils": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "../node_modules/schema-utils/node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "peer": true, - "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/schema-utils/node_modules/ajv-keywords": { - "version": "3.5.2", - "dev": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "../node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "peer": true - }, - "../node_modules/secure-json-parse": { - "version": "2.7.0", - "license": "BSD-3-Clause" - }, - "../node_modules/semantic-release": { - "version": "22.0.12", - "dev": true, - "license": "MIT", - "dependencies": { - "@semantic-release/commit-analyzer": "^11.0.0", - "@semantic-release/error": "^4.0.0", - "@semantic-release/github": "^9.0.0", - "@semantic-release/npm": "^11.0.0", - "@semantic-release/release-notes-generator": "^12.0.0", - "aggregate-error": "^5.0.0", - "cosmiconfig": "^8.0.0", - "debug": "^4.0.0", - "env-ci": "^10.0.0", - "execa": "^8.0.0", - "figures": "^6.0.0", - "find-versions": "^5.1.0", - "get-stream": "^6.0.0", - "git-log-parser": "^1.2.0", - "hook-std": "^3.0.0", - "hosted-git-info": "^7.0.0", - "import-from-esm": "^1.3.1", - "lodash-es": "^4.17.21", - "marked": "^9.0.0", - "marked-terminal": "^6.0.0", - "micromatch": "^4.0.2", - "p-each-series": "^3.0.0", - "p-reduce": "^3.0.0", - "read-pkg-up": "^11.0.0", - "resolve-from": "^5.0.0", - "semver": "^7.3.2", - "semver-diff": "^4.0.0", - "signale": "^1.2.1", - "yargs": "^17.5.1" - }, - "bin": { - "semantic-release": "bin/semantic-release.js" - }, - "engines": { - "node": "^18.17 || >=20.6.1" - } - }, - "../node_modules/semantic-release/node_modules/aggregate-error": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^5.2.0", - "indent-string": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/clean-stack": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "5.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/escape-string-regexp": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/execa": { - "version": "8.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "../node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { - "version": "8.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/figures": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/hosted-git-info": { - "version": "7.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "../node_modules/semantic-release/node_modules/human-signals": { - "version": "5.0.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "../node_modules/semantic-release/node_modules/indent-string": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/is-stream": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/is-unicode-supported": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/lru-cache": { - "version": "10.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } - }, - "../node_modules/semantic-release/node_modules/mimic-fn": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/npm-run-path": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/onetime": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/path-key": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semantic-release/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/semantic-release/node_modules/strip-final-newline": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semver": { - "version": "7.5.4", - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/semver-diff": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semver-regex": { - "version": "4.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "license": "ISC" - }, - "../node_modules/send": { - "version": "0.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/send/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "../node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/send/node_modules/mime": { - "version": "1.6.0", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/send/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/serialize-javascript": { - "version": "6.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "../node_modules/serve-static": { - "version": "1.15.0", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/set-blocking": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/set-function-length": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../node_modules/setprototypeof": { - "version": "1.2.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/shiki": { - "version": "0.14.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, - "../node_modules/side-channel": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "license": "ISC" - }, - "../node_modules/signale": { - "version": "1.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.3.2", - "figures": "^2.0.0", - "pkg-conf": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "../node_modules/signale/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/signale/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/signale/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "../node_modules/signale/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/signale/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "../node_modules/signale/node_modules/figures": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/signale/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/signale/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../node_modules/sigstore": { - "version": "1.9.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^1.1.0", - "@sigstore/protobuf-specs": "^0.2.0", - "@sigstore/sign": "^1.0.0", - "@sigstore/tuf": "^1.0.3", - "make-fetch-happen": "^11.0.1" - }, - "bin": { - "sigstore": "bin/sigstore.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/sigstore/node_modules/lru-cache": { - "version": "7.18.3", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "../node_modules/sigstore/node_modules/make-fetch-happen": { - "version": "11.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/sigstore/node_modules/minipass-fetch": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "../node_modules/sigstore/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "../node_modules/sisteransi": { - "version": "1.0.5", - "dev": true, - "license": "MIT" - }, - "../node_modules/skin-tone": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "unicode-emoji-modifier-base": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/slash": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/slice-ansi": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/smart-buffer": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "../node_modules/smob": { - "version": "1.4.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/socks": { - "version": "2.7.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "../node_modules/socks-proxy-agent": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "../node_modules/sonic-boom": { - "version": "3.7.0", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "../node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/source-map-support": { - "version": "0.5.13", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "../node_modules/spawn-error-forwarder": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/spdx-correct": { - "version": "3.2.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "../node_modules/spdx-exceptions": { - "version": "2.3.0", - "dev": true, - "license": "CC-BY-3.0" - }, - "../node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "../node_modules/spdx-license-ids": { - "version": "3.0.16", - "dev": true, - "license": "CC0-1.0" - }, - "../node_modules/split2": { - "version": "4.2.0", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "../node_modules/sprintf-js": { - "version": "1.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, - "../node_modules/sshpk": { - "version": "1.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/ssri": { - "version": "10.0.5", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/ssri/node_modules/minipass": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "../node_modules/stack-utils": { - "version": "2.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/statuses": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/stream-browserify": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "../node_modules/stream-browserify/node_modules/readable-stream": { - "version": "3.6.2", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/stream-combiner2": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - } - }, - "../node_modules/stream-combiner2/node_modules/readable-stream": { - "version": "2.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "../node_modules/stream-combiner2/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/stream-combiner2/node_modules/string_decoder": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "../node_modules/string_decoder": { - "version": "1.3.0", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "../node_modules/string-length": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/strip-bom": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/strip-final-newline": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/strip-indent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/strip-json-comments": { - "version": "3.1.1", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/superagent": { - "version": "8.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.4", - "debug": "^4.3.4", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.1.2", - "methods": "^1.1.2", - "mime": "2.6.0", - "qs": "^6.11.0", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=6.4.0 <13 || >=14" - } - }, - "../node_modules/superagent/node_modules/mime": { - "version": "2.6.0", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "../node_modules/superagent/node_modules/qs": { - "version": "6.11.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/supertest": { - "version": "6.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "methods": "^1.1.2", - "superagent": "^8.0.5" - }, - "engines": { - "node": ">=6.4.0" - } - }, - "../node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/supports-hyperlinks": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=14.18" - } - }, - "../node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/tapable": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/tar": { - "version": "6.2.0", - "dev": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "../node_modules/temp-dir": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, - "../node_modules/tempy": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^3.0.0", - "temp-dir": "^3.0.0", - "type-fest": "^2.12.2", - "unique-string": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/tempy/node_modules/is-stream": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/tempy/node_modules/type-fest": { - "version": "2.19.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/terser": { - "version": "5.26.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "../node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "../node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "../node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "../node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "dev": true, - "license": "MIT" - }, - "../node_modules/terser/node_modules/source-map-support": { - "version": "0.5.21", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "../node_modules/test-exclude": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/text-extensions": { - "version": "2.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/text-table": { - "version": "0.2.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/thread-stream": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - } - }, - "../node_modules/throttleit": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/through": { - "version": "2.3.8", - "dev": true, - "license": "MIT" - }, - "../node_modules/through2": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "3" - } - }, - "../node_modules/through2/node_modules/readable-stream": { - "version": "3.6.2", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "../node_modules/tmp": { - "version": "0.2.1", - "license": "MIT", - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "../node_modules/tmpl": { - "version": "1.0.5", - "dev": true, - "license": "BSD-3-Clause" - }, - "../node_modules/to-fast-properties": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/to-regex-range": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "../node_modules/toidentifier": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "../node_modules/tough-cookie": { - "version": "4.1.3", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "../node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "../node_modules/traverse": { - "version": "0.6.7", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../node_modules/trim-newlines": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/ts-api-utils": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.13.0" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "../node_modules/ts-jest": { - "version": "29.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "../node_modules/ts-loader": { - "version": "9.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4", - "source-map": "^0.7.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "../node_modules/ts-loader/node_modules/source-map": { - "version": "0.7.4", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, - "../node_modules/ts-node": { - "version": "10.9.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "../node_modules/tsconfig-paths": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "../node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/tslib": { - "version": "2.6.2", - "dev": true, - "license": "0BSD" - }, - "../node_modules/tslint-config-prettier": { - "version": "1.18.0", - "dev": true, - "license": "MIT", - "bin": { - "tslint-config-prettier-check": "bin/check.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "../node_modules/tuf-js": { - "version": "1.1.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@tufjs/models": "1.0.4", - "debug": "^4.3.4", - "make-fetch-happen": "^11.1.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/tuf-js/node_modules/lru-cache": { - "version": "7.18.3", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "../node_modules/tuf-js/node_modules/make-fetch-happen": { - "version": "11.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/tuf-js/node_modules/minipass-fetch": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "../node_modules/tuf-js/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "../node_modules/tunnel-agent": { - "version": "0.6.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "../node_modules/tweetnacl": { - "version": "0.14.5", - "dev": true, - "license": "Unlicense" - }, - "../node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "../node_modules/type-detect": { - "version": "4.0.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/type-fest": { - "version": "0.20.2", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/type-is": { - "version": "1.6.18", - "dev": true, - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "../node_modules/typedoc": { - "version": "0.25.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.3", - "shiki": "^0.14.1" - }, - "bin": { - "typedoc": "bin/typedoc" - }, - "engines": { - "node": ">= 16" - }, - "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x" - } - }, - "../node_modules/typedoc-plugin-markdown": { - "version": "3.17.1", - "dev": true, - "license": "MIT", - "dependencies": { - "handlebars": "^4.7.7" - }, - "peerDependencies": { - "typedoc": ">=0.24.0" - } - }, - "../node_modules/typedoc-plugin-merge-modules": { - "version": "5.1.0", - "dev": true, - "license": "ISC", - "peerDependencies": { - "typedoc": "0.24.x || 0.25.x" - } - }, - "../node_modules/typedoc-theme-hierarchy": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "fs-extra": "11.1.1" - }, - "peerDependencies": { - "typedoc": "^0.24.0 || ^0.25.0" - } - }, - "../node_modules/typedoc-theme-hierarchy/node_modules/fs-extra": { - "version": "11.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "../node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "../node_modules/typedoc/node_modules/marked": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "../node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../node_modules/typescript": { - "version": "5.3.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "../node_modules/uglify-js": { - "version": "3.17.4", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "../node_modules/undici-types": { - "version": "5.26.5", - "dev": true, - "license": "MIT" - }, - "../node_modules/unicode-emoji-modifier-base": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../node_modules/unicorn-magic": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/unique-filename": { - "version": "3.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^4.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/unique-slug": { - "version": "4.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/unique-string": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "crypto-random-string": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../node_modules/universal-user-agent": { - "version": "6.0.1", - "dev": true, - "license": "ISC" - }, - "../node_modules/universalify": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "../node_modules/unpipe": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/untildify": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../node_modules/update-browserslist-db": { - "version": "1.0.13", - "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.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "../node_modules/uri-js": { - "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "../node_modules/url-join": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "../node_modules/url-parse": { - "version": "1.5.10", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "../node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "../node_modules/utils-merge": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "../node_modules/uuid": { - "version": "9.0.1", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "../node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/v8-to-istanbul": { - "version": "9.2.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "../node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "../node_modules/validate-npm-package-name": { - "version": "5.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "builtins": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "../node_modules/vary": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "../node_modules/verror": { - "version": "1.10.0", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "../node_modules/vscode-oniguruma": { - "version": "1.7.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/vscode-textmate": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/walker": { - "version": "1.0.8", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "../node_modules/watchpack": { - "version": "2.4.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "../node_modules/webpack": { - "version": "5.89.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "../node_modules/webpack-sources": { - "version": "3.2.3", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.13.0" - } - }, - "../node_modules/webpack/node_modules/eslint-scope": { - "version": "5.1.1", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "../node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "../node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "../node_modules/wide-align": { - "version": "1.1.5", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "../node_modules/widest-line": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../node_modules/wordwrap": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "../node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "../node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "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", - "license": "ISC" - }, - "../node_modules/write-file-atomic": { - "version": "4.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "../node_modules/ws": { - "version": "6.2.2", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "../node_modules/xml": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "../node_modules/xtend": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "../node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "../node_modules/ya-ts-client": { - "version": "0.5.3", - "license": "LGPL-3.0-or-later", - "dependencies": { - "axios": "^0.21.4" - } - }, - "../node_modules/ya-ts-client/node_modules/axios": { - "version": "0.21.4", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "../node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, - "../node_modules/yargs": { - "version": "17.7.2", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "../node_modules/yargs-parser": { - "version": "21.1.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "../node_modules/yauzl": { - "version": "2.10.0", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "../node_modules/yn": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@golem-sdk/golem-js": { - "resolved": "..", - "link": true - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "18.17.11", - "license": "MIT" - }, - "node_modules/accepts": { - "version": "1.3.8", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.10.0", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "license": "MIT" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.1", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/commander": { - "version": "9.5.0", - "license": "MIT", - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/debug": { - "version": "2.6.9", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.18.2", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has": { - "version": "1.0.3", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "license": "ISC" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "license": "MIT" - }, - "node_modules/methods": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "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/safer-buffer": { - "version": "2.1.2", - "license": "MIT" - }, - "node_modules/send": { - "version": "0.18.0", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.15.0", - "license": "MIT", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ts-node": { - "version": "10.9.1", - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "license": "MIT" - }, - "node_modules/vary": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - } - } -} diff --git a/examples/package.json b/examples/package.json index 9e4462354..f8f908bee 100644 --- a/examples/package.json +++ b/examples/package.json @@ -5,40 +5,46 @@ "type": "module", "repository": "https://github.com/golemfactory/golem-js", "scripts": { - "hello": "ts-node ./hello-world/hello.ts", - "run": "ts-node ./simple-usage/run.ts", - "spawn": "ts-node ./simple-usage/spawn.ts", - "map": "ts-node ./simple-usage/map.ts", - "forEach": "ts-node ./simple-usage/forEach.ts", - "fileTransfer": "ts-node ./simple-usage/fileTransfer.ts", - "batchPromise": "ts-node ./simple-usage/batchPromise.ts", - "batchStream": "ts-node ./simple-usage/batchStream.ts", - "logger-hello": "ts-node simple-usage/logger.ts", - "blender": "ts-node ./blender/blender.ts", - "yacat": "ts-node ./yacat/yacat.ts", - "fibonacci": "ts-node fibonacci/fibonacci.ts", - "external-request": "ts-node ./external-request/request.ts", - "experimental-reputation": "ts-node ./experimental/reputation/basic.ts", - "ssh": "ts-node ./ssh/ssh.ts", - "tag": "ts-node ./simple-usage/tag.ts", - "web": "node ./web/app.mjs", - "server": "ts-node ./express/server.ts", - "proxy": "ts-node ./proxy/proxy.ts", + "basic-one-of": "tsx basic/one-of.ts", + "basic-many-of": "tsx basic/many-of.ts", + "basic-vpn": "tsx basic/vpn.ts", + "basic-transfer": "tsx basic/transfer.ts", + "basic-events": "tsx basic/events.ts", + "basic-run-and-stream": "tsx basic/run-and-stream.ts", + "advanced-hello-world": "tsx advanced/hello-world.ts", + "advanced-manual-pools": "tsx advanced/manual-pools.ts", + "advanced-step-by-step": "tsx advanced/step-by-step.ts", + "advanced-payment-filters": "tsx advanced/payment-filters.ts", + "advanced-proposal-filters": "tsx advanced/proposal-filter.ts", + "advanced-proposal-predefined-filter": "tsx advanced/proposal-predefined-filter.ts", + "advanced-scan": "tsx advanced/scan.ts", + "advanced-setup-and-teardown": "tsx advanced/setup-and-teardown.ts", + "local-image": "tsx advanced/local-image/serveLocalGvmi.ts", + "deployment": "tsx experimental/deployment/new-api.ts", + "market-scan": "tsx market/scan.ts", + "preweb": "cp -r ../dist/ web/dist/", + "postweb": "rm -rf web/dist/", + "web": "serve web/", + "experimental-server": "tsx experimental/express/server.ts", + "lint": "npm run lint:ts", "lint:ts": "tsc --project tsconfig.json --noEmit" }, "author": "GolemFactory ", "license": "LGPL-3.0", "engines": { - "node": ">=16.19.0" + "node": ">=18.0.0" }, "dependencies": { "@golem-sdk/golem-js": "file:..", - "commander": "^9.1.0", + "@golem-sdk/pino-logger": "^1.0.1", + "commander": "^12.0.0", "express": "^4.18.2", - "ts-node": "^10.9.2" + "tsx": "^4.7.1" }, "devDependencies": { - "@types/node": "18", + "@types/node": "20", + "cypress": "^13.11.0", + "serve": "^14.2.3", "typescript": "^5.3.3" } } diff --git a/examples/proxy/proxy.ts b/examples/proxy/proxy.ts deleted file mode 100644 index 77b186893..000000000 --- a/examples/proxy/proxy.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -/** - * An example demonstrating the use of a proxy server to send and receive http requests to the provider. - * After starting the proxy server, you can make any http request - * that will be processed by the http server on the provider's machine, eg. `curl http://localhost` - */ -(async function main() { - const executor = await TaskExecutor.create({ - package: "golem/node:20-alpine", - capabilities: ["vpn"], - networkIp: "192.168.0.0/24", - skipProcessSignals: true, - - // Restart the HTTP server up to 5 times - maxTaskRetries: 5, - - // If you're using TaskExecutor, you want the "task" to last long in that case - taskTimeout: 60 * 60 * 1000, - activityExecuteTimeout: 60 * 60 * 1000, - }); - - try { - await executor.run(async (ctx) => { - const PORT_ON_PROVIDER = 80; - const PORT_ON_REQUESTOR = 8080; - - // Install the server script - await ctx.uploadFile(`./proxy/server.js`, "/golem/work/server.js"); - - // Start the server process on the provider - const server = await ctx.runAndStream(`PORT=${PORT_ON_PROVIDER} node /golem/work/server.js`); - - server.stdout.on("data", (data) => console.log("provider>", data)); - server.stderr.on("data", (data) => console.error("provider>", data)); - - // Create a proxy instance - const proxy = ctx.createTcpProxy(PORT_ON_PROVIDER); - proxy.events.on("error", (error) => console.error("TcpProxy reported an error:", error)); - - // Start listening and expose the port on your requestor machine - - await proxy.listen(PORT_ON_REQUESTOR); - - console.log(`Server Proxy listen at http://localhost:${PORT_ON_REQUESTOR}`); - - // Prepare and register shutdown handlers for graceful termination - let isClosing = false; - const stopTask = async () => { - if (isClosing) { - console.log("Already closing, ignoring subsequent shutdown request"); - return; - } - - isClosing = true; - - console.log("Shutting down gracefully"); - await proxy.close(); - // It's OK to end here as once the task is finished, the TaskExecutor will terminate the underling activity on the provider - // leading to a shutdown of the server. - }; - - return new Promise((res) => { - console.log("Registered handlers..."); - process.on("SIGINT", () => stopTask().then(() => res())); - }); - }); - } catch (error) { - console.error("Proxy example failed:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/proxy/server.js b/examples/proxy/server.js deleted file mode 100644 index a1cb57ab1..000000000 --- a/examples/proxy/server.js +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable */ -const http = require("http"); - -(async function main() { - const PORT = parseInt(process.env["PORT"] ?? "80"); - - // Increase the value if you want to test long response/liveliness scenarios - const SIMULATE_DELAY_SEC = parseInt(process.env["SIMULATE_DELAY_SEC"] ?? "0"); - - const respond = (res) => { - res.writeHead(200); - res.end("Hello Golem!"); - }; - - const app = http.createServer((req, res) => { - if (SIMULATE_DELAY_SEC > 0) { - setTimeout(() => { - respond(res); - }, SIMULATE_DELAY_SEC * 1000); - } else { - respond(res); - } - }); - - const server = app.listen(PORT, () => console.log(`HTTP server started at "http://localhost:${PORT}"`)); - - const shutdown = () => { - server.close((err) => { - if (err) { - console.error("Server close encountered an issue", err); - } else { - console.log("Server closed successfully"); - } - }); - }; - - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); -})(); diff --git a/examples/simple-usage/batchPromise.ts b/examples/simple-usage/batchPromise.ts deleted file mode 100644 index 2a06f4c33..000000000 --- a/examples/simple-usage/batchPromise.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async function main() { - const executor = await TaskExecutor.create("golem/alpine:latest"); - try { - await executor.run(async (ctx) => { - const res = await ctx.beginBatch().run('echo "Hello Golem"').run('echo "Hello World"').end(); - res?.map(({ stdout }) => console.log(stdout)); - }); - } catch (error) { - console.log("Error while running the task:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/simple-usage/batchStream.ts b/examples/simple-usage/batchStream.ts deleted file mode 100644 index 7ec47eabe..000000000 --- a/examples/simple-usage/batchStream.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async function main() { - const executor = await TaskExecutor.create("golem/alpine:latest"); - try { - await executor.run(async (ctx) => { - const results = await ctx.beginBatch().run('echo "Hello Golem"').run('echo "Hello World"').endStream(); - results.on("data", ({ stdout }) => console.log(stdout)); - results.on("error", (error) => console.error(error.toString())); - results.on("close", () => console.log("END")); - }); - } catch (error) { - console.error("Computation failed:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/simple-usage/fileTransfer.ts b/examples/simple-usage/fileTransfer.ts deleted file mode 100644 index 4d2db59cb..000000000 --- a/examples/simple-usage/fileTransfer.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -import { readFileSync } from "fs"; - -(async function main() { - const executor = await TaskExecutor.create("golem/alpine:latest"); - try { - await executor.run(async (ctx) => { - await ctx.uploadJson({ test: "1234" }, "/golem/work/test.json"); - const res = await ctx.downloadFile("/golem/work/test.json", "new_test.json"); - console.log(`Result=${res.result}`); - console.log("File new_test.json: ", readFileSync("new_test.json", "utf-8")); - }); - } catch (err) { - console.error("Execution failed", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/simple-usage/logger.ts b/examples/simple-usage/logger.ts deleted file mode 100644 index 4aadd69ca..000000000 --- a/examples/simple-usage/logger.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { TaskExecutor, jsonLogger, nullLogger, pinoLogger } from "@golem-sdk/golem-js"; -import { program, Option } from "commander"; - -// Create command-line configuration. -program - .addOption(new Option("-l, --log ", "Set logger to use").default("text").choices(["text", "json", "null"])) - .option("-o, --output ", "log output file"); - -// Parse command-line arguments. -program.parse(); -const options = program.opts(); - -// Create logger based on configuration. -function createLogger(options) { - if (options.log === "text") { - return pinoLogger(options?.output); - } else if (options.log === "json") { - return jsonLogger(options?.output); - } else { - return nullLogger(); - } -} - -(async function main(options) { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger: createLogger(options), - }); - - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (err) { - console.error("Error while running the task:", err); - } finally { - await executor.shutdown(); - } -})(options); diff --git a/examples/simple-usage/map.ts b/examples/simple-usage/map.ts deleted file mode 100644 index 4a3278ee2..000000000 --- a/examples/simple-usage/map.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async function main() { - const executor = await TaskExecutor.create("golem/alpine:latest"); - const data = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]; - - try { - const futureResults = data.map((x) => - executor.run(async (ctx) => { - const res = await ctx.run(`echo "${x}"`); - return res.stdout?.toString().trim(); - }), - ); - - const results = await Promise.all(futureResults); - console.log(results); - } catch (err) { - console.error("An error occurred:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/simple-usage/run.ts b/examples/simple-usage/run.ts deleted file mode 100644 index c5fffb05e..000000000 --- a/examples/simple-usage/run.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async function main() { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - midAgreementPaymentTimeoutSec: 10, - debitNotesAcceptanceTimeoutSec: 10, - }); - - try { - const results = await executor.run(async (ctx) => { - const res1 = await ctx.run('echo "Hello"'); - const res2 = await ctx.run('echo "World"'); - return `${res1.stdout}${res2.stdout}`; - }); - console.log(results); - } catch (err) { - console.error("An error occurred during execution:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/simple-usage/spawn.ts b/examples/simple-usage/spawn.ts deleted file mode 100644 index e99a2c9c2..000000000 --- a/examples/simple-usage/spawn.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -const executor = await TaskExecutor.create("golem/alpine:latest"); -const finalResult = await executor.run(async (ctx) => { - const remoteProcess = await ctx.runAndStream("sleep 1 && echo 'Hello World' && echo 'Hello Golem' >&2"); - remoteProcess.stdout.on("data", (data) => console.log("stdout>", data)); - remoteProcess.stderr.on("data", (data) => console.error("stderr>", data)); - - const finalResult = await remoteProcess.waitForExit(); - return finalResult; -}); - -console.log(finalResult); - -await executor.shutdown(); diff --git a/examples/simple-usage/tag.ts b/examples/simple-usage/tag.ts deleted file mode 100644 index 66e7930f6..000000000 --- a/examples/simple-usage/tag.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; - -(async function main() { - const executor = await TaskExecutor.create("golem/alpine:latest"); - - try { - const results = await executor.run(async (ctx) => { - const res1 = await ctx.run('echo "Hello"'); - const res2 = await ctx.run('echo "World"'); - return `${res1.stdout}${res2.stdout}`; - }); - console.log(results); - } catch (err) { - console.error("Task execution failed:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/ssh/Dockerfile b/examples/ssh/Dockerfile deleted file mode 100644 index 10a07efb0..000000000 --- a/examples/ssh/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM alpine:latest - -RUN apk add --no-cache --update bash openssh iproute2 tcpdump net-tools screen -RUN echo "UseDNS no" >> /etc/ssh/sshd_config && \ - echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && \ - echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config - -VOLUME /golem/input /golem/output /golem/work \ No newline at end of file diff --git a/examples/ssh/ssh.ts b/examples/ssh/ssh.ts deleted file mode 100644 index 019bf1825..000000000 --- a/examples/ssh/ssh.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -import { program } from "commander"; -import crypto from "crypto"; - -async function main(subnetTag, driver, network, count = 2, sessionTimeout = 100) { - const executor = await TaskExecutor.create({ - package: "golem/examples-ssh:latest", - capabilities: ["vpn"], - networkIp: "192.168.0.0/24", - maxParallelTasks: count, - subnetTag, - payment: { driver, network }, - }); - const appKey = process.env["YAGNA_APPKEY"]; - const runningTasks: Promise[] = []; - for (let i = 0; i < count; i++) { - runningTasks.push( - executor.run(async (ctx) => { - const password = crypto.randomBytes(3).toString("hex"); - try { - const results = await ctx - .beginBatch() - .run("syslogd") - .run("ssh-keygen -A") - .run(`echo -e "${password}\n${password}" | passwd`) - .run("/usr/sbin/sshd") - .end(); - if (!results) return; - - console.log("\n------------------------------------------"); - console.log(`Connect via ssh to provider "${ctx.provider?.name}" with:`); - console.log( - `ssh -o ProxyCommand='websocat asyncstdio: ${ctx.getWebsocketUri( - 22, - )} --binary -H=Authorization:"Bearer ${appKey}"' root@${crypto.randomBytes(10).toString("hex")}`, - ); - console.log(`Password: ${password}`); - console.log("------------------------------------------\n"); - await new Promise((res) => setTimeout(res, sessionTimeout * 1000)); - console.log(`Task completed. Session SSH closed after ${sessionTimeout} secs timeout.`); - } catch (error) { - console.error("Computation failed:", error); - } - }), - ); - } - try { - await Promise.all(runningTasks); - } finally { - await executor.shutdown(); - } -} - -program - .option("--subnet-tag ", "set subnet name, for example 'public'") - .option("--payment-driver ", "payment driver name, for example 'erc20'") - .option("--payment-network ", "network name, for example 'holesky'") - .option("--task-count, --count ", "task count", (val) => parseInt(val)) - .option("-t, --timeout ", "ssh session timeout (in seconds)", (val) => parseInt(val)); -program.parse(); -const options = program.opts(); -main(options.subnetTag, options.paymentDriver, options.paymentNetwork, options.count, options.timeout); diff --git a/examples/strategy/bestProvidersIds.ts b/examples/strategy/bestProvidersIds.ts deleted file mode 100644 index b32d34c1b..000000000 --- a/examples/strategy/bestProvidersIds.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TaskExecutor, AgreementSelectors } from "@golem-sdk/golem-js"; - -/** - * Example demonstrating how to use predefined selector `bestAgreementSelector`, - * which choose the best provider based on scores provided as object: [providerId]: score - * A higher score rewards the provider. - */ -const scores = { - "0x79bcfdc92af492c9b15ce9f690c3ccae53437179": 100, - "0x3c6a3f59518a0da1e75ea4351713bfe908e6642c": 50, - "0x1c1c0b14e321c258f7057e29533cba0081df8bb8": 25, -}; - -(async function main() { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - agreementSelector: AgreementSelectors.bestAgreementSelector(scores), - }); - - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (err) { - console.error("Task execution failed:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/strategy/bestProvidersNames.ts b/examples/strategy/bestProvidersNames.ts deleted file mode 100644 index 4fea3e654..000000000 --- a/examples/strategy/bestProvidersNames.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { TaskExecutor, AgreementCandidate } from "@golem-sdk/golem-js"; - -/** - * Example demonstrating how to write a selector which choose the best provider based on scores provided as object: [providerName]: score - * A higher score rewards the provider. - */ -const scores = { - "provider-1": 100, - "golem-provider": 50, - "super-provider": 25, -}; - -const bestProviderSelector = - (scores: { [providerName: string]: number }) => async (candidates: AgreementCandidate[]) => { - candidates.sort((a, b) => - (scores?.[a.proposal.provider.name] || 0) >= (scores?.[b.proposal.provider.name] || 0) ? 1 : -1, - ); - return candidates[0]; - }; - -(async function main() { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - agreementSelector: bestProviderSelector(scores), - }); - - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (err) { - console.error("Task execution failed:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/strategy/blackListProvidersIds.ts b/examples/strategy/blackListProvidersIds.ts deleted file mode 100644 index 9c414fb2d..000000000 --- a/examples/strategy/blackListProvidersIds.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TaskExecutor, ProposalFilterFactory } from "@golem-sdk/golem-js"; - -/** - * Example demonstrating how to use the predefined filter `disallowProvidersById`, - * which blocking any proposal coming from a provider whose id is in the array - */ - -const blackListProvidersIds = [ - "0x79bcfdc92af492c9b15ce9f690c3ccae53437179", - "0x3c6a3f59518a0da1e75ea4351713bfe908e6642c", - "0x1c1c0b14e321c258f7057e29533cba0081df8bb8", -]; - -(async function main() { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - proposalFilter: ProposalFilterFactory.disallowProvidersById(blackListProvidersIds), - }); - - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (error) { - console.error("Computation failed:", error); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/strategy/blackListProvidersNames.ts b/examples/strategy/blackListProvidersNames.ts deleted file mode 100644 index 5686b7064..000000000 --- a/examples/strategy/blackListProvidersNames.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { TaskExecutor, ProposalFilterFactory } from "@golem-sdk/golem-js"; - -/** - * Example demonstrating how to use the predefined filter `disallowProvidersByName`, - * which blocking any proposal coming from a provider whose name is in the array - */ - -const blackListProvidersNames = ["provider-1", "golem-provider", "super-provider"]; - -(async function main() { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - proposalFilter: ProposalFilterFactory.disallowProvidersByName(blackListProvidersNames), - }); - - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (err) { - console.error("Task execution failed:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/strategy/blackListProvidersRegexp.ts b/examples/strategy/blackListProvidersRegexp.ts deleted file mode 100644 index b889196fe..000000000 --- a/examples/strategy/blackListProvidersRegexp.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { TaskExecutor, ProposalFilterFactory } from "@golem-sdk/golem-js"; - -/** - * Example demonstrating how to use the predefined selector `disallowProvidersByNameRegex`, - * which blocking any proposal coming from a provider whose name match to the regexp - */ - -(async function main() { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - proposalFilter: ProposalFilterFactory.disallowProvidersByNameRegex(/bad-provider*./), - }); - - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (err) { - console.error("Task execution failed:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/strategy/customProviderFilter.ts b/examples/strategy/customProviderFilter.ts deleted file mode 100644 index 3c0608e61..000000000 --- a/examples/strategy/customProviderFilter.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ProposalFilter, TaskExecutor } from "@golem-sdk/golem-js"; - -/** - * Example demonstrating how to write a custom proposal filter. - * In this case the proposal must include VPN access and must not be from "bad-provider" - */ -const myFilter: ProposalFilter = (proposal) => { - return ( - proposal.provider.name !== "bad-provider" || !proposal.properties["golem.runtime.capabilities"]?.includes("vpn") - ); -}; - -(async function main() { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - proposalFilter: myFilter, - }); - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (err) { - console.error("Task execution failed:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/strategy/debitNotesFilter.ts b/examples/strategy/debitNotesFilter.ts deleted file mode 100644 index bdee79d4d..000000000 --- a/examples/strategy/debitNotesFilter.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TaskExecutor, PaymentFilters } from "@golem-sdk/golem-js"; - -/** - * Example demonstrating how to use the predefined payment filter `acceptMaxAmountDebitNoteFilter`, - * which only accept debit notes below 0.00001 GLM. - */ -(async function main() { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - debitNotesFilter: PaymentFilters.acceptMaxAmountDebitNoteFilter(0.00001), - }); - - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (err) { - console.error("Task execution failed:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/strategy/dynamicProposalFilter.ts b/examples/strategy/dynamicProposalFilter.ts deleted file mode 100644 index f25721b60..000000000 --- a/examples/strategy/dynamicProposalFilter.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Example demonstrating how to write a custom dynamic proposal filter. - * - * By dynamic, we understand that the filter behaviour might change over time due to some conditions - */ - -import { ProposalFilter, ProposalFilterFactory, TaskExecutor } from "@golem-sdk/golem-js"; - -const makeDynamicFilter: () => { - filter: ProposalFilter; - stopPolling: () => void; -} = () => { - let partnerProviderIds = []; - - const loadPartners = () => - fetch("https://provider-health.golem.network/v1/provider-whitelist") - .then((res) => res.json()) - .then((list) => (partnerProviderIds = list)) - .catch((err) => console.error("Issue when loading list of partners", err)); - - // Update your list of partners each 10s - const interval = setInterval(loadPartners, 10_000); - - // Fire the load immediately - void loadPartners(); - - // Return the filter that will be called synchronously - return { - filter: ProposalFilterFactory.allowProvidersById(partnerProviderIds), - stopPolling: () => clearInterval(interval), - }; -}; - -(async function main() { - const { filter, stopPolling } = makeDynamicFilter(); - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - proposalFilter: filter, - }); - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (err) { - console.error("Task execution failed:", err); - } finally { - await executor.shutdown(); - stopPolling(); - } -})(); diff --git a/examples/strategy/invoicesFilter.ts b/examples/strategy/invoicesFilter.ts deleted file mode 100644 index 847e4a9c2..000000000 --- a/examples/strategy/invoicesFilter.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { TaskExecutor, PaymentFilters } from "@golem-sdk/golem-js"; - -/** - * Example demonstrating how to use the predefined payment filter `acceptMaxAmountInvoiceFilter`, - * which only accept invoices below 0.00001 GLM. - */ -(async function main() { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - invoiceFilter: PaymentFilters.acceptMaxAmountInvoiceFilter(0.00001), - }); - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (err) { - console.error("Task execution failed:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/strategy/whiteListProvidersIds.ts b/examples/strategy/whiteListProvidersIds.ts deleted file mode 100644 index c1d942d62..000000000 --- a/examples/strategy/whiteListProvidersIds.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TaskExecutor, ProposalFilterFactory } from "@golem-sdk/golem-js"; - -/** - * Example demonstrating how to use the predefined filter `allowProvidersById`, - * which only allows offers from a provider whose id is in the array - */ - -const whiteListIds = [ - "0x79bcfdc92af492c9b15ce9f690c3ccae53437179", - "0x3c6a3f59518a0da1e75ea4351713bfe908e6642c", - "0x1c1c0b14e321c258f7057e29533cba0081df8bb8", -]; - -(async function main() { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - proposalFilter: ProposalFilterFactory.allowProvidersById(whiteListIds), - }); - - try { - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (err) { - console.error("Task execution failed:", err); - } finally { - await executor.shutdown(); - } -})(); diff --git a/examples/tsconfig.json b/examples/tsconfig.json index 1e0b439cf..b09c90abe 100644 --- a/examples/tsconfig.json +++ b/examples/tsconfig.json @@ -15,9 +15,5 @@ "outDir": "dist", "typeRoots": ["node_modules/@types"] }, - "exclude": ["dist", "node_modules"], - "ts-node": { - "esm": true, - "experimentalSpecifierResolution": "node" - } + "exclude": ["dist", "node_modules"] } diff --git a/examples/web/app.mjs b/examples/web/app.mjs deleted file mode 100644 index 3deddcdc4..000000000 --- a/examples/web/app.mjs +++ /dev/null @@ -1,44 +0,0 @@ -import http from "http"; -import fs from "fs"; -import { fileURLToPath } from "url"; -const __dirname = fileURLToPath(new URL(".", import.meta.url)); - -const server = http.createServer((req, res) => { - if (req.url === "/") { - res.writeHead(200, { "content-type": "text/html" }); - fs.createReadStream(`${__dirname}/index.html`).pipe(res); - } else if (req.url === "/hello") { - res.writeHead(200, { "content-type": "text/html" }); - fs.createReadStream(`${__dirname}/hello.html`).pipe(res); - } else if (req.url === "/image") { - res.writeHead(200, { "content-type": "text/html" }); - fs.createReadStream(`${__dirname}/image.html`).pipe(res); - } else if (req.url === "/docs-example-transfer-data") { - res.writeHead(200, { "content-type": "text/html" }); - fs.createReadStream(`${__dirname}/../docs-examples/examples/transferring-data/transfer-data-in-browser.html`).pipe( - res, - ); - } else if (req.url === "/docs-example-transfer-json") { - res.writeHead(200, { "content-type": "text/html" }); - fs.createReadStream(`${__dirname}/../docs-examples/examples/transferring-data/upload-json-in-browser.html`).pipe( - res, - ); - } else if (req.url === "/docs-tutorial") { - res.writeHead(200, { "content-type": "text/html" }); - fs.createReadStream(`${__dirname}/../docs-examples/tutorials/running-from-browser/index.html`).pipe(res); - } else if (req.url === "/docs-quickstart") { - res.writeHead(200, { "content-type": "text/html" }); - fs.createReadStream(`${__dirname}/../docs-examples/quickstarts/web-quickstart/index.html`).pipe(res); - } else if (req.url === "/requestor.mjs") { - res.writeHead(200, { "content-type": "text/javascript" }); - fs.createReadStream(`${__dirname}/../docs-examples/quickstarts/web-quickstart/requestor.mjs`).pipe(res); - } else if (req.url === "/golem-js.min.js") { - res.writeHead(200, { "content-type": "text/javascript" }); - fs.createReadStream(`${__dirname}/../../dist/golem-js.min.js`).pipe(res); - } else if (req.url === "/css/main.css") { - res.writeHead(200, { "content-type": "text/css" }); - fs.createReadStream(`${__dirname}/css/main.css`).pipe(res); - } -}); - -server.listen(3000, () => console.log(`Server listen at http://localhost:3000`)); diff --git a/examples/web/css/main.css b/examples/web/css/main.css deleted file mode 100644 index 72b96fa4b..000000000 --- a/examples/web/css/main.css +++ /dev/null @@ -1,187 +0,0 @@ -body { - font-family: -apple-system, system-ui, "Segoe UI", "Liberation Sans", sans-serif; -} -h1 { - font-size: 1.7rem; - padding: 10px; - margin: 0; -} -h3 { - border-bottom: 1px solid #ddd; - font-size: 1rem; - padding: 6px; - margin-right: 25px; -} -.container { - display: flex; - padding: 0 10px; -} -.col-6 { - width: 50%; -} -#options { - display: flex; -} -#options .row { - padding: 0; - padding-right: 6px; -} -.clear { - float: right; - margin-top: 47px; - margin-right: 29px; - font-size: 0.7rem; -} -.clear-results { - margin-top: 14px; -} -.clear:hover { - cursor: pointer; -} -.border-left { - border-left: 1px solid #ddd; - padding-left: 20px; -} -.inline { - display: flex; - align-items: center; -} -.fieldset { - display: flex; - flex-direction: column; -} -.fieldset label { - margin: 0 5px; -} -.fieldset > div, -.fieldset > div > span { - display: inline-flex; - align-items: center; -} -.fieldset .inline label { - min-width: 80px; -} -.inline input[type="text"] { - min-width: 200px; -} -#inputs { - margin-left: 30px; -} - -label { - font-style: italic; - font-size: 0.7rem; - margin: 0; - padding-right: 2rem; - box-sizing: content-box; -} -input { - width: 95%; - border: 1px solid #ccc; - border-radius: 4px; - box-sizing: border-box; - resize: vertical; - margin: 0; - padding: 5px; - height: 35px; -} -input[type="radio"] { - width: inherit; - border: inherit; - border-radius: 4px; - box-sizing: border-box; - resize: vertical; - margin: 0; - padding: 5px; - height: 35px; -} -button { - background-color: #064779; - border: 1px solid transparent; - border-radius: 3px; - box-shadow: rgba(255, 255, 255, 0.4) 0 1px 0 0 inset; - box-sizing: border-box; - color: #fff; - cursor: pointer; - display: inline-block; - font-family: -apple-system, system-ui, "Segoe UI", "Liberation Sans", sans-serif; - font-size: 13px; - font-weight: 400; - line-height: 1.15385; - margin: 0 10px; - outline: none; - padding: 8px 0.8em; - position: relative; - text-align: center; - text-decoration: none; - user-select: none; - -webkit-user-select: none; - touch-action: manipulation; - vertical-align: baseline; - white-space: nowrap; -} -button:hover { - background-color: #07c; -} -button:disabled { - background-color: #ddd; - cursor: default; -} -.row { - display: flex; - padding: 8px; -} -.vertical { - flex-direction: column; -} -.padding-0 { - padding: 0; -} -.logs { - min-height: 30vh; - white-space: pre-line; -} -.events { - min-height: 20vh; -} -.results { - min-height: 30vh; -} -.commands button { - width: 140px; - margin: 0 30px 0 0; -} -#logs { - white-space: pre-line; -} -#results li { - font-size: 1.2rem; -} -.console ul { - list-style: none; - font-family: monospace; - padding: 0; - font-size: 0.82rem; - white-space: pre; -} - -.script h3 { - display: flex; - align-items: flex-end; - justify-content: space-between; - padding-right: 0; - margin-right: 20px; -} - -.script #execute { - margin-right: 0; - width: 120px; -} - -.script ul { - padding-left: 20px; -} - -.small { - width: 120px; -} diff --git a/examples/web/hello.html b/examples/web/hello.html index af4c22917..c68002505 100644 --- a/examples/web/hello.html +++ b/examples/web/hello.html @@ -3,102 +3,132 @@ WebRequestor Task API - - + -

                WebRequestor - Hello World

                -
                +

                Hello Golem

                +

                Options

                -
                -
                -
                -
                - - -
                -
                - - -
                -
                -
                -
                - - -
                -
                - - -
                -
                - - -
                -
                +
                +
                + + +
                +
                + +
                -

                Actions

                -
                -
                - +
                +
                + + +
                +
                + + +
                +
                + +
                -
                -

                Results

                -
                  +
                  +
                  +

                  Actions

                  +
                  +
                  -
                  -
                  -

                  Logs

                  -
                    +
                    + +

                    Results

                    +
                    +
                      diff --git a/examples/web/image.html b/examples/web/image.html deleted file mode 100644 index 872e16e0b..000000000 --- a/examples/web/image.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - WebRequestor Task API - - - -

                      WebRequestor - Meme Example

                      -
                      -
                      -

                      Credentials

                      -
                      -
                      - - -
                      -
                      - - -
                      -
                      - - -
                      -
                      - - -
                      -
                      - - -
                      -
                      - - -
                      -
                      -

                      Actions

                      -
                      -
                      - -
                      -
                      -
                      -

                      Result Meme

                      - -
                      -
                      -
                      -
                      -

                      Logs

                      -
                        -
                        -
                        -
                        - - - - diff --git a/examples/web/imagemagick.Dockerfile b/examples/web/imagemagick.Dockerfile deleted file mode 100644 index 3add9dc3c..000000000 --- a/examples/web/imagemagick.Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM dpokidov/imagemagick - -VOLUME /golem/input /golem/output /golem/work -WORKDIR /golem/work diff --git a/examples/web/index.html b/examples/web/index.html deleted file mode 100644 index 48ba24f1a..000000000 --- a/examples/web/index.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Golem Application Examples - - - -

                        Golem Application Examples

                        - - - diff --git a/examples/yacat/README.md b/examples/yacat/README.md deleted file mode 100644 index 3f6e35661..000000000 --- a/examples/yacat/README.md +++ /dev/null @@ -1,17 +0,0 @@ -To run the example use the following command: - -``` -npm run yacat --mask '?a?a?a' --hash '$P$5ZDzPE45CLLhEx/72qt3NehVzwN2Ry/' -``` - -you can also try with a heavier password: - -``` -npm run yacat --mask '?a?a?a?a' --hash '$H$5ZDzPE45C.e3TjJ2Qi58Aaozha6cs30' --number-of-providers 4 -``` - -or a lighter one: - -``` -npm run yacat --mask '?a?a' --hash '$P$5ZDzPE45CigTC6EY4cXbyJSLj/pGee0' -``` diff --git a/examples/yacat/yacat.Dockerfile b/examples/yacat/yacat.Dockerfile deleted file mode 100644 index 56a3266ee..000000000 --- a/examples/yacat/yacat.Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM ubuntu -WORKDIR /golem/work -RUN apt-get update -RUN apt-get install -y hashcat diff --git a/examples/yacat/yacat.ts b/examples/yacat/yacat.ts deleted file mode 100644 index 99e61e34a..000000000 --- a/examples/yacat/yacat.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { TaskExecutor } from "@golem-sdk/golem-js"; -import { program } from "commander"; - -type MainOptions = { - numberOfProviders: number; - subnetTag: string; - paymentNetwork: string; - mask: string; - paymentDriver: string; - hash: string; -}; - -program - .option("--subnet-tag ", "set subnet name, for example 'public'") - .option("--payment-driver, --driver ", "payment driver name, for example 'erc20'") - .option("--payment-network, --network ", "network name, for example 'holesky'") - .option("--number-of-providers ", "number of providers", (value) => parseInt(value), 2) - .option("--mask ") - .requiredOption("--hash ") - .action(async (args: MainOptions) => { - const executor = await TaskExecutor.create({ - package: "golem/examples-hashcat:latest", - maxParallelTasks: args.numberOfProviders, - minMemGib: 0.5, - minStorageGib: 2, - budget: 10, - subnetTag: args.subnetTag, - taskTimeout: 1000 * 60 * 8, // 8 min - payment: { driver: args.paymentDriver, network: args.paymentNetwork }, - }); - - const keyspace = await executor.run(async (ctx) => { - const result = await ctx.run(`hashcat --keyspace -a 3 ${args.mask} -m 400`); - return parseInt(result.stdout?.toString().trim() || ""); - }); - - if (!keyspace) throw new Error(`Cannot calculate keyspace`); - const step = Math.floor(keyspace / args.numberOfProviders); - const range = [...Array(Math.floor(keyspace / step)).keys()].map((i) => i * step); - console.log(`Keyspace size computed. Keyspace size = ${keyspace}. Tasks to compute = ${range.length}`); - - const findPasswordInRange = async (skip: number) => { - const password = await executor.run(async (ctx) => { - const [, potfileResult] = await ctx - .beginBatch() - .run( - `hashcat -a 3 -m 400 '${args.hash}' '${args.mask}' --skip=${skip} --limit=${ - skip + step - } -o pass.potfile || true`, - ) - .run("cat pass.potfile || true") - .end(); - if (!potfileResult.stdout) return false; - // potfile format is: hash:password - return potfileResult.stdout.toString().trim().split(":")[1]; - }); - if (!password) { - throw new Error(`Cannot find password in range ${skip} - ${skip + step}`); - } - return password; - }; - - try { - const password = await Promise.any(range.map(findPasswordInRange)); - console.log(`Password found: ${password}`); - } catch (err) { - console.log(`Password not found`); - } finally { - await executor.shutdown(); - } - }); - -program.parse(); diff --git a/package-lock.json b/package-lock.json index bd7d26fc2..d484ec3c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,27 +13,26 @@ ], "dependencies": { "async-lock": "^1.4.1", + "async-retry": "^1.3.3", "axios": "^1.6.7", "bottleneck": "^2.19.5", - "collect.js": "^4.36.1", "debug": "^4.3.4", "decimal.js-light": "^2.5.1", "eventemitter3": "^5.0.1", "eventsource": "^2.0.2", - "flatbuffers": "^23.5.26", + "flatbuffers": "^24.3.7", "ip-num": "^1.5.1", "js-sha3": "^0.9.3", - "pino": "^8.19.0", - "pino-pretty": "^10.3.1", + "rxjs": "^7.8.1", "semver": "^7.5.4", "tmp": "^0.2.2", - "uuid": "^9.0.1", + "uuid": "^10.0.0", "ws": "^8.16.0", - "ya-ts-client": "^0.5.3" + "ya-ts-client": "^1.1.3" }, "devDependencies": { - "@commitlint/cli": "^18.6.1", - "@commitlint/config-conventional": "^18.6.2", + "@commitlint/cli": "^19.0.3", + "@commitlint/config-conventional": "^19.0.3", "@johanblumenberg/ts-mockito": "^1.0.41", "@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-commonjs": "^25.0.7", @@ -42,19 +41,21 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@types/async-lock": "^1.4.2", + "@types/async-retry": "^1.4.8", "@types/debug": "^4.1.12", "@types/eventsource": "^1.1.15", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", - "@types/node": "^20.11.21", + "@types/node": "^20.11.20", "@types/semver": "^7.5.8", "@types/supertest": "^6.0.2", "@types/tmp": "^0.2.6", - "@types/uuid": "^9.0.8", + "@types/uuid": "^10.0.0", "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^7.1.0", "@typescript-eslint/parser": "^7.1.0", "buffer": "^6.0.3", + "cross-env": "^7.0.3", "cypress": "^13.6.6", "cypress-log-to-output": "^1.1.2", "eslint": "^8.57.0", @@ -70,13 +71,12 @@ "rollup-plugin-polyfill-node": "^0.13.0", "rollup-plugin-visualizer": "^5.12.0", "semantic-release": "^23.0.2", - "stream-browserify": "^3.0.0", "supertest": "^6.3.4", "ts-jest": "^29.1.2", "ts-loader": "^9.5.1", - "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "tslint-config-prettier": "^1.18.0", + "tsx": "^4.7.1", "typedoc": "^0.25.9", "typedoc-plugin-markdown": "^3.17.1", "typedoc-plugin-merge-modules": "^5.1.0", @@ -98,52 +98,36 @@ "license": "LGPL-3.0", "dependencies": { "@golem-sdk/golem-js": "file:..", - "commander": "^9.1.0", + "@golem-sdk/pino-logger": "^1.0.1", + "commander": "^12.0.0", "express": "^4.18.2", - "ts-node": "^10.9.2" + "tsx": "^4.7.1" }, "devDependencies": { - "@types/node": "18", + "@types/node": "20", + "cypress": "^13.11.0", + "serve": "^14.2.3", "typescript": "^5.3.3" }, "engines": { - "node": ">=16.19.0" - } - }, - "examples/node_modules/@types/node": { - "version": "18.19.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.19.tgz", - "integrity": "sha512-qqV6hSy9zACEhQUy5CEGeuXAZN0fNjqLWRIvOXOwdFYhFoKBiY08VKR5kgchr90+TitLVhpUEb54hk4bYaArUw==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "examples/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "engines": { - "node": "^12.20.0 || >=14" + "node": ">=18.0.0" } }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -151,9 +135,8 @@ }, "node_modules/@babel/code-frame": { "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" @@ -164,9 +147,8 @@ }, "node_modules/@babel/code-frame/node_modules/ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -176,9 +158,8 @@ }, "node_modules/@babel/code-frame/node_modules/chalk": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -188,44 +169,26 @@ "node": ">=4" } }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/@babel/code-frame/node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/@babel/code-frame/node_modules/supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -235,18 +198,16 @@ }, "node_modules/@babel/compat-data": { "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", @@ -274,18 +235,16 @@ }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", @@ -298,9 +257,8 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.23.5", "@babel/helper-validator-option": "^7.23.5", @@ -314,27 +272,24 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.22.15", "@babel/types": "^7.23.0" @@ -345,9 +300,8 @@ }, "node_modules/@babel/helper-hoist-variables": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, @@ -357,9 +311,8 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.15" }, @@ -369,9 +322,8 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -388,18 +340,16 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, @@ -409,9 +359,8 @@ }, "node_modules/@babel/helper-split-export-declaration": { "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, @@ -421,36 +370,32 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", - "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.24.0", "@babel/traverse": "^7.24.0", @@ -462,9 +407,8 @@ }, "node_modules/@babel/highlight": { "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -476,9 +420,8 @@ }, "node_modules/@babel/highlight/node_modules/ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -488,9 +431,8 @@ }, "node_modules/@babel/highlight/node_modules/chalk": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -500,44 +442,26 @@ "node": ">=4" } }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/@babel/highlight/node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/@babel/highlight/node_modules/supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -547,9 +471,8 @@ }, "node_modules/@babel/parser": { "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", "dev": true, + "license": "MIT", "bin": { "parser": "bin/babel-parser.js" }, @@ -559,9 +482,8 @@ }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -571,9 +493,8 @@ }, "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -583,9 +504,8 @@ }, "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" }, @@ -595,9 +515,8 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -607,9 +526,8 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -619,9 +537,8 @@ }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -634,9 +551,8 @@ }, "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" }, @@ -646,9 +562,8 @@ }, "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" }, @@ -658,9 +573,8 @@ }, "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" }, @@ -670,9 +584,8 @@ }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -682,9 +595,8 @@ }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -694,9 +606,8 @@ }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -706,9 +617,8 @@ }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -721,9 +631,8 @@ }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -736,9 +645,8 @@ }, "node_modules/@babel/runtime": { "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", - "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", "dev": true, + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -748,9 +656,8 @@ }, "node_modules/@babel/template": { "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/parser": "^7.24.0", @@ -762,9 +669,8 @@ }, "node_modules/@babel/traverse": { "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", - "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", @@ -783,18 +689,16 @@ }, "node_modules/@babel/traverse/node_modules/globals": { "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/@babel/types": { "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -806,35 +710,29 @@ }, "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 + "dev": true, + "license": "MIT" }, "node_modules/@colors/colors": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.1.90" } }, "node_modules/@commitlint/cli": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-18.6.1.tgz", - "integrity": "sha512-5IDE0a+lWGdkOvKH892HHAZgbAjcj1mT5QrfA/SVbLJV/BbBMGyKN0W5mhgjekPJJwEQdVNvhl9PwUacY58Usw==", + "version": "19.2.1", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/format": "^18.6.1", - "@commitlint/lint": "^18.6.1", - "@commitlint/load": "^18.6.1", - "@commitlint/read": "^18.6.1", - "@commitlint/types": "^18.6.1", - "execa": "^5.0.0", - "lodash.isfunction": "^3.0.9", - "resolve-from": "5.0.0", - "resolve-global": "1.0.0", + "@commitlint/format": "^19.0.3", + "@commitlint/lint": "^19.1.0", + "@commitlint/load": "^19.2.0", + "@commitlint/read": "^19.2.1", + "@commitlint/types": "^19.0.3", + "execa": "^8.0.1", "yargs": "^17.0.0" }, "bin": { @@ -845,12 +743,11 @@ } }, "node_modules/@commitlint/config-conventional": { - "version": "18.6.2", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-18.6.2.tgz", - "integrity": "sha512-PcgSYg1AKGQIwDQKbaHtJsfqYy4uJTC7crLVZ83lfjcPaec4Pry2vLeaWej7ao2KsT20l9dWoMPpEGg8LWdUuA==", + "version": "19.1.0", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/types": "^18.6.1", + "@commitlint/types": "^19.0.3", "conventional-changelog-conventionalcommits": "^7.0.2" }, "engines": { @@ -858,12 +755,11 @@ } }, "node_modules/@commitlint/config-validator": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-18.6.1.tgz", - "integrity": "sha512-05uiToBVfPhepcQWE1ZQBR/Io3+tb3gEotZjnI4tTzzPk16NffN6YABgwFQCLmzZefbDcmwWqJWc2XT47q7Znw==", + "version": "19.0.3", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/types": "^18.6.1", + "@commitlint/types": "^19.0.3", "ajv": "^8.11.0" }, "engines": { @@ -871,12 +767,11 @@ } }, "node_modules/@commitlint/ensure": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-18.6.1.tgz", - "integrity": "sha512-BPm6+SspyxQ7ZTsZwXc7TRQL5kh5YWt3euKmEIBZnocMFkJevqs3fbLRb8+8I/cfbVcAo4mxRlpTPfz8zX7SnQ==", + "version": "19.0.3", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/types": "^18.6.1", + "@commitlint/types": "^19.0.3", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", "lodash.snakecase": "^4.1.1", @@ -888,93 +783,85 @@ } }, "node_modules/@commitlint/execute-rule": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-18.6.1.tgz", - "integrity": "sha512-7s37a+iWyJiGUeMFF6qBlyZciUkF8odSAnHijbD36YDctLhGKoYltdvuJ/AFfRm6cBLRtRk9cCVPdsEFtt/2rg==", + "version": "19.0.0", "dev": true, + "license": "MIT", "engines": { "node": ">=v18" } }, "node_modules/@commitlint/format": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-18.6.1.tgz", - "integrity": "sha512-K8mNcfU/JEFCharj2xVjxGSF+My+FbUHoqR+4GqPGrHNqXOGNio47ziiR4HQUPKtiNs05o8/WyLBoIpMVOP7wg==", + "version": "19.0.3", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/types": "^18.6.1", - "chalk": "^4.1.0" + "@commitlint/types": "^19.0.3", + "chalk": "^5.3.0" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/is-ignored": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-18.6.1.tgz", - "integrity": "sha512-MOfJjkEJj/wOaPBw5jFjTtfnx72RGwqYIROABudOtJKW7isVjFe9j0t8xhceA02QebtYf4P/zea4HIwnXg8rvA==", + "version": "19.0.3", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/types": "^18.6.1", - "semver": "7.6.0" + "@commitlint/types": "^19.0.3", + "semver": "^7.6.0" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/lint": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-18.6.1.tgz", - "integrity": "sha512-8WwIFo3jAuU+h1PkYe5SfnIOzp+TtBHpFr4S8oJWhu44IWKuVx6GOPux3+9H1iHOan/rGBaiacicZkMZuluhfQ==", + "version": "19.1.0", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/is-ignored": "^18.6.1", - "@commitlint/parse": "^18.6.1", - "@commitlint/rules": "^18.6.1", - "@commitlint/types": "^18.6.1" + "@commitlint/is-ignored": "^19.0.3", + "@commitlint/parse": "^19.0.3", + "@commitlint/rules": "^19.0.3", + "@commitlint/types": "^19.0.3" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/load": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-18.6.1.tgz", - "integrity": "sha512-p26x8734tSXUHoAw0ERIiHyW4RaI4Bj99D8YgUlVV9SedLf8hlWAfyIFhHRIhfPngLlCe0QYOdRKYFt8gy56TA==", + "version": "19.2.0", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^18.6.1", - "@commitlint/execute-rule": "^18.6.1", - "@commitlint/resolve-extends": "^18.6.1", - "@commitlint/types": "^18.6.1", - "chalk": "^4.1.0", - "cosmiconfig": "^8.3.6", + "@commitlint/config-validator": "^19.0.3", + "@commitlint/execute-rule": "^19.0.0", + "@commitlint/resolve-extends": "^19.1.0", + "@commitlint/types": "^19.0.3", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", "cosmiconfig-typescript-loader": "^5.0.0", "lodash.isplainobject": "^4.0.6", "lodash.merge": "^4.6.2", - "lodash.uniq": "^4.5.0", - "resolve-from": "^5.0.0" + "lodash.uniq": "^4.5.0" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/message": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-18.6.1.tgz", - "integrity": "sha512-VKC10UTMLcpVjMIaHHsY1KwhuTQtdIKPkIdVEwWV+YuzKkzhlI3aNy6oo1eAN6b/D2LTtZkJe2enHmX0corYRw==", + "version": "19.0.0", "dev": true, + "license": "MIT", "engines": { "node": ">=v18" } }, "node_modules/@commitlint/parse": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-18.6.1.tgz", - "integrity": "sha512-eS/3GREtvVJqGZrwAGRwR9Gdno3YcZ6Xvuaa+vUF8j++wsmxrA2En3n0ccfVO2qVOLJC41ni7jSZhQiJpMPGOQ==", + "version": "19.0.3", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/types": "^18.6.1", + "@commitlint/types": "^19.0.3", "conventional-changelog-angular": "^7.0.0", "conventional-commits-parser": "^5.0.0" }, @@ -983,111 +870,86 @@ } }, "node_modules/@commitlint/read": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-18.6.1.tgz", - "integrity": "sha512-ia6ODaQFzXrVul07ffSgbZGFajpe8xhnDeLIprLeyfz3ivQU1dIoHp7yz0QIorZ6yuf4nlzg4ZUkluDrGN/J/w==", + "version": "19.2.1", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/top-level": "^18.6.1", - "@commitlint/types": "^18.6.1", - "git-raw-commits": "^2.0.11", - "minimist": "^1.2.6" + "@commitlint/top-level": "^19.0.0", + "@commitlint/types": "^19.0.3", + "execa": "^8.0.1", + "git-raw-commits": "^4.0.0", + "minimist": "^1.2.8" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/resolve-extends": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-18.6.1.tgz", - "integrity": "sha512-ifRAQtHwK+Gj3Bxj/5chhc4L2LIc3s30lpsyW67yyjsETR6ctHAHRu1FSpt0KqahK5xESqoJ92v6XxoDRtjwEQ==", + "version": "19.1.0", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^18.6.1", - "@commitlint/types": "^18.6.1", - "import-fresh": "^3.0.0", + "@commitlint/config-validator": "^19.0.3", + "@commitlint/types": "^19.0.3", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", "lodash.mergewith": "^4.6.2", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" + "resolve-from": "^5.0.0" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/rules": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-18.6.1.tgz", - "integrity": "sha512-kguM6HxZDtz60v/zQYOe0voAtTdGybWXefA1iidjWYmyUUspO1zBPQEmJZ05/plIAqCVyNUTAiRPWIBKLCrGew==", + "version": "19.0.3", "dev": true, + "license": "MIT", "dependencies": { - "@commitlint/ensure": "^18.6.1", - "@commitlint/message": "^18.6.1", - "@commitlint/to-lines": "^18.6.1", - "@commitlint/types": "^18.6.1", - "execa": "^5.0.0" + "@commitlint/ensure": "^19.0.3", + "@commitlint/message": "^19.0.0", + "@commitlint/to-lines": "^19.0.0", + "@commitlint/types": "^19.0.3", + "execa": "^8.0.1" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/to-lines": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-18.6.1.tgz", - "integrity": "sha512-Gl+orGBxYSNphx1+83GYeNy5N0dQsHBQ9PJMriaLQDB51UQHCVLBT/HBdOx5VaYksivSf5Os55TLePbRLlW50Q==", + "version": "19.0.0", "dev": true, + "license": "MIT", "engines": { "node": ">=v18" } }, "node_modules/@commitlint/top-level": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-18.6.1.tgz", - "integrity": "sha512-HyiHQZUTf0+r0goTCDs/bbVv/LiiQ7AVtz6KIar+8ZrseB9+YJAIo8HQ2IC2QT1y3N1lbW6OqVEsTHjbT6hGSw==", + "version": "19.0.0", "dev": true, + "license": "MIT", "dependencies": { - "find-up": "^5.0.0" + "find-up": "^7.0.0" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/types": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-18.6.1.tgz", - "integrity": "sha512-gwRLBLra/Dozj2OywopeuHj2ac26gjGkz2cZ+86cTJOdtWfiRRr4+e77ZDAGc6MDWxaWheI+mAV5TLWWRwqrFg==", + "version": "19.0.3", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.1.0" + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" }, "engines": { "node": ">=v18" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@cypress/request": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", - "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -1114,9 +976,8 @@ }, "node_modules/@cypress/request/node_modules/form-data": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -1128,18 +989,16 @@ }, "node_modules/@cypress/request/node_modules/uuid": { "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, "node_modules/@cypress/xvfb": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", - "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.1.0", "lodash.once": "^4.1.1" @@ -1147,1525 +1006,2107 @@ }, "node_modules/@cypress/xvfb/node_modules/debug": { "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=12" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "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/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "*" + "node": ">=12" } }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=12" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true - }, - "node_modules/@golem-sdk/golem-js": { - "resolved": "", - "link": true + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.10.0" + "node": ">=12" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "*" + "node": ">=12" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=12" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } }, - "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==", - "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" - }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } }, - "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==", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "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==", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "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" - }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config/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, - "dependencies": { - "sprintf-js": "~1.0.2" + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "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==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", "dev": true, + "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "eslint-visitor-keys": "^3.3.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.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/@eslint-community/regexpp": { + "version": "4.10.0", "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.0.0 || ^14.0.0 || >=16.0.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/@eslint/eslintrc": { + "version": "2.1.4", "dev": true, + "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "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/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^2.2.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" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@istanbuljs/load-nyc-config/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 - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", "dev": true, + "license": "ISC", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "*" } }, - "node_modules/@jest/console/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/@eslint/js": { + "version": "8.57.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "node_modules/@gar/promisify": { + "version": "1.1.3", "dev": true, + "license": "MIT" + }, + "node_modules/@golem-sdk/golem-js": { + "resolved": "", + "link": true + }, + "node_modules/@golem-sdk/pino-logger": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@golem-sdk/pino-logger/-/pino-logger-1.0.3.tgz", + "integrity": "sha512-P9BMJ+QUlWx7C+4iku/SOnNnjzGGBEhaHtOf4IDXrtvpEg8zqxEuyw5mM7PXpAr7HU/5C/f2BcBG39i7QYfwsw==", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "pino": "^8.20.0", + "pino-pretty": "^11.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "@golem-sdk/golem-js": "<4" } }, - "node_modules/@jest/core/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, "engines": { - "node": ">=8" + "node": ">=10.10.0" } }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", "dev": true, + "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", "dev": true, + "license": "ISC", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "*" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", "dev": true, + "license": "ISC", "dependencies": { - "jest-get-type": "^29.6.3" + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "license": "MIT", + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", "dev": true, + "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", "dev": true, + "license": "MIT", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": ">=12" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", "dev": true, + "license": "MIT", "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" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/@jest/reporters/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", "dev": true, + "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "p-locate": "^4.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "p-try": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" + "p-limit": "^2.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/test-sequencer/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { + "version": "4.0.0", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/@jest/transform": { + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", + "@types/node": "*", "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", + "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", "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/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@johanblumenberg/ts-mockito": { - "version": "1.0.41", - "resolved": "https://registry.npmjs.org/@johanblumenberg/ts-mockito/-/ts-mockito-1.0.41.tgz", - "integrity": "sha512-LkmjBIPz8i6iGe4euAF1WTRNTa9o817f9diMv74pZ1dRGpapwzHQhkJ1ING/8tfB1bPc8ucTe4XypqsP9E/n4Q==", - "dev": true, - "dependencies": { - "lodash": "^4.17.20" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz", - "integrity": "sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==", + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "color-name": "~1.1.4" }, "engines": { - "node": ">=6.0.0" + "node": ">=7.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "engines": { - "node": ">=6.0.0" - } + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz", - "integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==", + "node_modules/@jest/core": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 8" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@npmcli/fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", - "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "semver": "^7.3.5" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@npmcli/git": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", - "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "@npmcli/promise-spawn": "^6.0.0", - "lru-cache": "^7.4.4", - "npm-pick-manifest": "^8.0.0", - "proc-log": "^3.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^3.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=7.0.0" } }, - "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/core/node_modules/slash": { + "version": "3.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" + "has-flag": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@npmcli/installed-package-contents": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", - "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", + "node_modules/@jest/environment": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "bin": { - "installed-package-contents": "lib/index.js" + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", + "node_modules/@jest/expect": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@npmcli/promise-spawn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", - "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "which": "^3.0.0" + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "node_modules/@jest/globals": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@npmcli/run-script": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", - "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", + "node_modules/@jest/reporters": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^6.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^3.0.0" + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" + "color-convert": "^2.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@octokit/auth-token": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", - "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "1.1.11", "dev": true, - "engines": { - "node": ">= 18" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@octokit/core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.1.0.tgz", - "integrity": "sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==", + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/auth-token": "^4.0.0", - "@octokit/graphql": "^7.0.0", - "@octokit/request": "^8.0.2", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/endpoint": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.4.tgz", - "integrity": "sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==", - "dev": true, - "dependencies": { - "@octokit/types": "^12.0.0", - "universal-user-agent": "^6.0.0" + "node": ">=10" }, - "engines": { - "node": ">= 18" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@octokit/graphql": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", - "integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/request": "^8.0.1", - "@octokit/types": "^12.0.0", - "universal-user-agent": "^6.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 18" + "node": ">=7.0.0" } }, - "node_modules/@octokit/openapi-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", - "dev": true + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.0.tgz", - "integrity": "sha512-NKi0bJEZqOSbBLMv9kdAcuocpe05Q2xAXNLTGi0HN2GSMFJHNZuSoPNa0tcQFTOFCKe+ZaYBZ3lpXh1yxgUDCA==", + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", "dev": true, + "license": "ISC", "dependencies": { - "@octokit/types": "^12.6.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">= 18" + "node": "*" }, - "peerDependencies": { - "@octokit/core": ">=5" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@octokit/plugin-retry": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz", - "integrity": "sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==", + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.2", "dev": true, + "license": "ISC", "dependencies": { - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", - "bottleneck": "^2.15.3" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=5" + "node": "*" } }, - "node_modules/@octokit/plugin-throttling": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", - "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "node_modules/@jest/reporters/node_modules/slash": { + "version": "3.0.0", "dev": true, - "dependencies": { - "@octokit/types": "^12.2.0", - "bottleneck": "^2.15.3" - }, + "license": "MIT", "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "^5.0.0" + "node": ">=8" } }, - "node_modules/@octokit/request": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.2.0.tgz", - "integrity": "sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==", + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/endpoint": "^9.0.0", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", - "universal-user-agent": "^6.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 18" + "node": ">=8" } }, - "node_modules/@octokit/request-error": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", - "integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", + "node_modules/@jest/schemas": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^12.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">= 18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@octokit/types": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", - "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "node_modules/@jest/source-map": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^20.0.0" - } - }, - "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==", - "optional": true, + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, "engines": { - "node": ">=14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "node_modules/@jest/test-result": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, "engines": { - "node": ">=12.22.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "4.2.10" + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=12.22.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "node_modules/@jest/test-sequencer/node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/@pnpm/npm-conf": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", - "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", + "node_modules/@jest/transform": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { - "node": ">=12" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@rollup/plugin-alias": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.0.tgz", - "integrity": "sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==", + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "slash": "^4.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + "node": ">=8" }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@rollup/plugin-commonjs": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz", - "integrity": "sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==", + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^8.0.3", - "is-reference": "1.2.1", - "magic-string": "^0.30.3" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0||^4.0.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@rollup/plugin-inject": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", - "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.3" + "color-name": "~1.1.4" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "node": ">=7.0.0" } }, - "node_modules/@rollup/plugin-json": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", - "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.1.0" - }, + "license": "MIT" + }, + "node_modules/@jest/transform/node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "node": ">=8" } }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", - "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "node": ">=8" } }, - "node_modules/@rollup/plugin-terser": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", - "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "node_modules/@jest/types": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "serialize-javascript": "^6.0.1", - "smob": "^1.0.0", - "terser": "^5.17.4" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@rollup/plugin-typescript": { - "version": "11.1.6", - "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.6.tgz", - "integrity": "sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==", + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "@rollup/pluginutils": "^5.1.0", - "resolve": "^1.22.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.14.0||^3.0.0||^4.0.0", - "tslib": "*", - "typescript": ">=3.7.0" + "node": ">=8" }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - }, - "tslib": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", - "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", - "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", - "cpu": [ - "arm" - ], + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", "dev": true, - "optional": true, - "os": [ - "android" - ] + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", - "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", - "cpu": [ - "arm64" - ], + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "optional": true, - "os": [ - "android" - ] + "license": "MIT" }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", - "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", - "cpu": [ - "arm64" - ], + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", "dev": true, - "optional": true, - "os": [ - "darwin" - ] + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", - "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@johanblumenberg/ts-mockito": { + "version": "1.0.41", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", - "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", - "cpu": [ - "arm" - ], + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", - "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", - "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", - "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", - "cpu": [ - "riscv64" - ], + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", - "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", - "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", "dev": true, - "optional": true, - "os": [ - "linux" - ] + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", - "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ] + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", - "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", - "cpu": [ - "ia32" - ], + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", "dev": true, - "optional": true, - "os": [ - "win32" - ] + "license": "MIT", + "engines": { + "node": ">= 8" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", - "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "7.18.3", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "3.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "3.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "3.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.0.0", + "@octokit/request": "^9.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^12.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^9.0.0", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "10.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^6.0.0", + "@octokit/types": "^12.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "9.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^6.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "9.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "12.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "dev": true, + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/plugin-alias": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "slash": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "25.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-inject": { + "version": "5.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "11.1.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz", + "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz", + "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.13.2", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz", + "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz", + "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz", + "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz", + "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==", + "cpu": [ + "ppc64le" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz", + "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz", + "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz", + "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz", + "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz", + "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", "cpu": [ "x64" ], @@ -2674,1593 +3115,2389 @@ "win32" ] }, - "node_modules/@semantic-release/commit-analyzer": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-11.1.0.tgz", - "integrity": "sha512-cXNTbv3nXR2hlzHjAMgbuiQVtvWHTlwwISt60B+4NZv01y/QRY7p2HcJm8Eh2StzcTJoNnflvKjHH/cjFS7d5g==", + "node_modules/@semantic-release/commit-analyzer": { + "version": "12.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "debug": "^4.0.0", + "import-from-esm": "^1.0.3", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/error": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/github": { + "version": "10.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^6.0.0", + "@octokit/plugin-paginate-rest": "^10.0.0", + "@octokit/plugin-retry": "^7.0.0", + "@octokit/plugin-throttling": "^9.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "globby": "^14.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^7.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "url-join": "^5.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/github/node_modules/agent-base": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@semantic-release/github/node_modules/aggregate-error": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/github/node_modules/clean-stack": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/github/node_modules/escape-string-regexp": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/github/node_modules/globby": { + "version": "14.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/github/node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@semantic-release/github/node_modules/https-proxy-agent": { + "version": "7.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@semantic-release/github/node_modules/indent-string": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/github/node_modules/path-type": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/github/node_modules/slash": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm": { + "version": "12.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "execa": "^8.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^8.0.0", + "npm": "^10.5.0", + "rc": "^1.2.8", + "read-pkg": "^9.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/aggregate-error": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/clean-stack": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/escape-string-regexp": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/fs-extra": { + "version": "11.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@semantic-release/npm/node_modules/indent-string": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/release-notes-generator": { + "version": "13.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-angular": "^7.0.0", + "conventional-changelog-writer": "^7.0.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^1.0.3", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-pkg-up": "^11.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sigstore/bundle": { + "version": "1.1.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "1.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^1.1.0", + "@sigstore/protobuf-specs": "^0.2.0", + "make-fetch-happen": "^11.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign/node_modules/lru-cache": { + "version": "7.18.3", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@sigstore/sign/node_modules/make-fetch-happen": { + "version": "11.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign/node_modules/minipass-fetch": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/@sigstore/sign/node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.0.4", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@sigstore/tuf": { + "version": "1.0.3", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.0", + "tuf-js": "^1.1.7" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tufjs/canonical-json": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "1.0.0", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@types/async-lock": { + "version": "1.4.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/async-retry": { + "version": "1.4.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "*" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "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.6.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/eventsource": { + "version": "1.1.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.43", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.12", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.12.2", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.9.12", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", "dev": true, + "license": "MIT", "dependencies": { - "conventional-changelog-angular": "^7.0.0", - "conventional-commits-filter": "^4.0.0", - "conventional-commits-parser": "^5.0.0", - "debug": "^4.0.0", - "import-from-esm": "^1.0.3", - "lodash-es": "^4.17.21", - "micromatch": "^4.0.2" - }, - "engines": { - "node": "^18.17 || >=20.6.1" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" + "@types/mime": "^1", + "@types/node": "*" } }, - "node_modules/@semantic-release/error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", - "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "node_modules/@types/serve-static": { + "version": "1.15.5", "dev": true, - "engines": { - "node": ">=18" + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" } }, - "node_modules/@semantic-release/github": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-9.2.6.tgz", - "integrity": "sha512-shi+Lrf6exeNZF+sBhK+P011LSbhmIAoUEgEY6SsxF8irJ+J2stwI5jkyDQ+4gzYyDImzV6LCKdYB9FXnQRWKA==", + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sizzle": { + "version": "2.3.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/superagent": { + "version": "8.1.4", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/core": "^5.0.0", - "@octokit/plugin-paginate-rest": "^9.0.0", - "@octokit/plugin-retry": "^6.0.0", - "@octokit/plugin-throttling": "^8.0.0", - "@semantic-release/error": "^4.0.0", - "aggregate-error": "^5.0.0", - "debug": "^4.3.4", - "dir-glob": "^3.0.1", - "globby": "^14.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "issue-parser": "^6.0.0", - "lodash-es": "^4.17.21", - "mime": "^4.0.0", - "p-filter": "^4.0.0", - "url-join": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*" } }, - "node_modules/@semantic-release/github/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "node_modules/@types/supertest": { + "version": "6.0.2", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" } }, - "node_modules/@semantic-release/github/node_modules/aggregate-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "node_modules/@types/tmp": { + "version": "0.2.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.10", "dev": true, + "license": "MIT", "dependencies": { - "clean-stack": "^5.2.0", - "indent-string": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/node": "*" } }, - "node_modules/@semantic-release/github/node_modules/clean-stack": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", - "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "node_modules/@types/yargs": { + "version": "17.0.32", "dev": true, + "license": "MIT", "dependencies": { - "escape-string-regexp": "5.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/yargs-parser": "*" } }, - "node_modules/@semantic-release/github/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" } }, - "node_modules/@semantic-release/github/node_modules/globby": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz", - "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.4.0", "dev": true, + "license": "MIT", "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/type-utils": "7.4.0", + "@typescript-eslint/utils": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": ">=18" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@semantic-release/github/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "node_modules/@typescript-eslint/parser": { + "version": "7.4.0", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "agent-base": "^7.1.0", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4" }, "engines": { - "node": ">= 14" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@semantic-release/github/node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.4.0", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/@semantic-release/github/node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true, - "engines": { - "node": ">=12" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@semantic-release/github/node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "node_modules/@typescript-eslint/type-utils": { + "version": "7.4.0", "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/utils": "7.4.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, "engines": { - "node": ">=12" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@semantic-release/github/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "node_modules/@typescript-eslint/types": { + "version": "7.4.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=14.16" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@semantic-release/npm": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-11.0.2.tgz", - "integrity": "sha512-owtf3RjyPvRE63iUKZ5/xO4uqjRpVQDUB9+nnXj0xwfIeM9pRl+cG+zGDzdftR4m3f2s4Wyf3SexW+kF5DFtWA==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.4.0", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@semantic-release/error": "^4.0.0", - "aggregate-error": "^5.0.0", - "execa": "^8.0.0", - "fs-extra": "^11.0.0", - "lodash-es": "^4.17.21", - "nerf-dart": "^1.0.0", - "normalize-url": "^8.0.0", - "npm": "^10.0.0", - "rc": "^1.2.8", - "read-pkg": "^9.0.0", - "registry-auth-token": "^5.0.0", - "semver": "^7.1.2", - "tempy": "^3.0.0" + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^18.17 || >=20" + "node": "^18.18.0 || >=20.0.0" }, - "peerDependencies": { - "semantic-release": ">=20.1.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@semantic-release/npm/node_modules/aggregate-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "node_modules/@typescript-eslint/utils": { + "version": "7.4.0", "dev": true, + "license": "MIT", "dependencies": { - "clean-stack": "^5.2.0", - "indent-string": "^5.0.0" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=18" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" } }, - "node_modules/@semantic-release/npm/node_modules/clean-stack": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", - "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.4.0", "dev": true, + "license": "MIT", "dependencies": { - "escape-string-regexp": "5.0.0" + "@typescript-eslint/types": "7.4.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">=14.16" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@semantic-release/npm/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "ISC" }, - "node_modules/@semantic-release/npm/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, + "peer": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, - "node_modules/@semantic-release/npm/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, + "peer": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" } }, - "node_modules/@semantic-release/npm/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "peer": true }, - "node_modules/@semantic-release/npm/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, - "engines": { - "node": ">=16.17.0" + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" } }, - "node_modules/@semantic-release/npm/node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" } }, - "node_modules/@semantic-release/npm/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" } }, - "node_modules/@semantic-release/npm/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, - "node_modules/@semantic-release/npm/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, + "peer": true, "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, - "node_modules/@semantic-release/npm/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, + "peer": true, "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, - "node_modules/@semantic-release/npm/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, - "node_modules/@semantic-release/npm/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==", + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" } }, - "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true, - "engines": { - "node": ">=12" + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "peer": true + }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=6.5" } }, - "node_modules/@semantic-release/release-notes-generator": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-12.1.0.tgz", - "integrity": "sha512-g6M9AjUKAZUZnxaJZnouNBeDNTCUrJ5Ltj+VJ60gJeDaRRahcHsry9HW8yKrnKkKNkx5lbWiEP1FPMqVNQz8Kg==", - "dev": true, + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", "dependencies": { - "conventional-changelog-angular": "^7.0.0", - "conventional-changelog-writer": "^7.0.0", - "conventional-commits-filter": "^4.0.0", - "conventional-commits-parser": "^5.0.0", - "debug": "^4.0.0", - "get-stream": "^7.0.0", - "import-from-esm": "^1.0.3", - "into-stream": "^7.0.0", - "lodash-es": "^4.17.21", - "read-pkg-up": "^11.0.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": "^18.17 || >=20.6.1" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" + "node": ">= 0.6" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", - "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "node_modules/acorn": { + "version": "8.11.3", "dev": true, - "engines": { - "node": ">=16" + "license": "MIT", + "bin": { + "acorn": "bin/acorn" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=0.4.0" } }, - "node_modules/@sigstore/bundle": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.1.0.tgz", - "integrity": "sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==", + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "dev": true, - "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "peer": true, + "peerDependencies": { + "acorn": "^8" } }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz", - "integrity": "sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A==", + "node_modules/acorn-jsx": { + "version": "5.3.2", "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@sigstore/sign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-1.0.0.tgz", - "integrity": "sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA==", + "node_modules/agent-base": { + "version": "6.0.2", "dev": true, + "license": "MIT", "dependencies": { - "@sigstore/bundle": "^1.1.0", - "@sigstore/protobuf-specs": "^0.2.0", - "make-fetch-happen": "^11.0.1" + "debug": "4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 6.0.0" } }, - "node_modules/@sigstore/sign/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/agentkeepalive": { + "version": "4.5.0", "dev": true, + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, "engines": { - "node": ">=12" + "node": ">= 8.0.0" } }, - "node_modules/@sigstore/sign/node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "node_modules/aggregate-error": { + "version": "3.1.0", "dev": true, + "license": "MIT", "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@sigstore/sign/node_modules/minipass-fetch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "node_modules/ajv": { + "version": "8.12.0", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, - "optionalDependencies": { - "encoding": "^0.1.13" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@sigstore/sign/node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "node_modules/ansi-align": { + "version": "3.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", "dev": true, + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=6" } }, - "node_modules/@sigstore/tuf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz", - "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==", + "node_modules/ansi-escapes": { + "version": "4.3.2", "dev": true, + "license": "MIT", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0", - "tuf-js": "^1.1.7" + "type-fest": "^0.21.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "node_modules/ansi-regex": { + "version": "5.0.1", "dev": true, + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=8" + } + }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "node_modules/any-promise": { + "version": "1.3.0", "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } + "license": "MIT" }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "node_modules/anymatch": { + "version": "3.1.3", "dev": true, + "license": "ISC", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@tootallnate/once": { + "node_modules/aproba": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true, + "license": "ISC" + }, + "node_modules/arch": { + "version": "2.2.0", + "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/are-we-there-yet": { + "version": "3.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, "engines": { - "node": ">= 10" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + "node_modules/argv-formatter": { + "version": "1.0.0", + "dev": true, + "license": "MIT" }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + "node_modules/array-flatten": { + "version": "1.1.1", + "license": "MIT" }, - "node_modules/@tufjs/canonical-json": { + "node_modules/array-ify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", - "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "license": "MIT" }, - "node_modules/@tufjs/models": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", - "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", + "node_modules/array-union": { + "version": "2.1.0", "dev": true, - "dependencies": { - "@tufjs/canonical-json": "1.0.0", - "minimatch": "^9.0.0" - }, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@types/async-lock": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@types/async-lock/-/async-lock-1.4.2.tgz", - "integrity": "sha512-HlZ6Dcr205BmNhwkdXqrg2vkFMN2PluI7Lgr8In3B3wE5PiQHhjRqtW/lGdVU9gw+sM0JcIDx2AN+cW8oSWIcw==", - "dev": true - }, - "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==", + "node_modules/asap": { + "version": "2.0.6", "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } + "license": "MIT" }, - "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "node_modules/asn1": { + "version": "0.2.6", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.0.0" + "safer-buffer": "~2.1.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==", + "node_modules/assert-plus": { + "version": "1.0.0", "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "license": "MIT", + "engines": { + "node": ">=0.8" } }, - "node_modules/@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "node_modules/astral-regex": { + "version": "2.0.0", "dev": true, - "dependencies": { - "@babel/types": "^7.20.7" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "node_modules/async": { + "version": "3.2.5", + "dev": true, + "license": "MIT" + }, + "node_modules/async-limiter": { + "version": "1.0.1", "dev": true, + "license": "MIT" + }, + "node_modules/async-lock": { + "version": "1.4.1", + "license": "MIT" + }, + "node_modules/async-retry": { + "version": "1.3.3", + "license": "MIT", "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "retry": "0.13.1" } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", "dev": true, - "dependencies": { - "@types/node": "*" + "license": "ISC", + "engines": { + "node": ">= 4.0.0" } }, - "node_modules/@types/cookiejar": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", - "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", - "dev": true + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "node_modules/aws-sign2": { + "version": "0.7.0", "dev": true, - "dependencies": { - "@types/ms": "*" + "license": "Apache-2.0", + "engines": { + "node": "*" } }, - "node_modules/@types/eslint": { - "version": "8.56.4", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.4.tgz", - "integrity": "sha512-lG1GLUnL5vuRBGb3MgWUWLdGMH2Hps+pERuyQXCfWozuGKdnhf9Pbg4pkcrVUHjKrU7Rl+GCZ/299ObBXZFAxg==", + "node_modules/aws4": { + "version": "1.12.0", "dev": true, - "peer": true, + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "node_modules/babel-jest": { + "version": "29.7.0", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" } }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/eventsource": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@types/eventsource/-/eventsource-1.1.15.tgz", - "integrity": "sha512-XQmGcbnxUNa06HR3VBVkc9+A2Vpi9ZyLJcdS5dwaQQ/4ZMWFO+5c90FnMUpbtMZwB/FChoYHwuVg8TvkECacTA==", - "dev": true - }, - "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.43", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", - "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true - }, - "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 + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "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==", + "node_modules/babel-jest/node_modules/slash": { + "version": "3.0.0", "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "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==", + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "@types/istanbul-lib-report": "*" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/jest": { - "version": "29.5.12", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", - "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/methods": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", - "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true - }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true - }, - "node_modules/@types/ms": { - "version": "0.7.34", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", - "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.11.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.21.tgz", - "integrity": "sha512-/ySDLGscFPNasfqStUuWWPfL78jompfIoVzLJPVVAHBh6rpG68+pI2Gk+fNLeI8/f1yPYL4s46EleVIc20F1Ow==", + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true - }, - "node_modules/@types/qs": { - "version": "6.9.12", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.12.tgz", - "integrity": "sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/serve-static": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", - "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", "dev": true, + "license": "MIT", "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@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-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", - "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true - }, - "node_modules/@types/sizzle": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", - "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", - "dev": true - }, - "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 - }, - "node_modules/@types/superagent": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.4.tgz", - "integrity": "sha512-uzSBYwrpal8y2X2Pul5ZSWpzRiDha2FLcquaN95qUPnOjYgm/zQ5LIdqeJpQJTRWNTN+Rhm0aC8H06Ds2rqCYw==", + "node_modules/babel-preset-jest": { + "version": "29.6.3", "dev": true, + "license": "MIT", "dependencies": { - "@types/cookiejar": "^2.1.5", - "@types/methods": "^1.1.4", - "@types/node": "*" + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@types/supertest": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", - "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "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/bcrypt-pbkdf": { + "version": "1.0.2", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@types/methods": "^1.1.4", - "@types/superagent": "^8.1.0" + "tweetnacl": "^0.14.3" } }, - "node_modules/@types/tmp": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz", - "integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==", - "dev": true + "node_modules/before-after-hook": { + "version": "3.0.2", + "dev": true, + "license": "Apache-2.0" }, - "node_modules/@types/uuid": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", - "dev": true + "node_modules/blob-util": { + "version": "2.0.2", + "dev": true, + "license": "Apache-2.0" }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "node_modules/bluebird": { + "version": "3.7.2", "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "license": "MIT", "dependencies": { - "@types/node": "*" + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "ms": "2.0.0" } }, - "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 + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "optional": true, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "license": "BSD-3-Clause", "dependencies": { - "@types/node": "*" + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.0.tgz", - "integrity": "sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==", + "node_modules/bottleneck": { + "version": "2.19.5", + "license": "MIT" + }, + "node_modules/boxen": { + "version": "5.1.2", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/type-utils": "7.1.0", - "@typescript-eslint/utils": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.0.tgz", - "integrity": "sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==", + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/typescript-estree": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", - "debug": "^4.3.4" + "color-convert": "^2.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "6.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, - "peerDependencies": { - "eslint": "^8.56.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz", - "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==", + "node_modules/boxen/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0" + "color-name": "~1.1.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=7.0.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.0.tgz", - "integrity": "sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==", + "node_modules/boxen/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.1.0", - "@typescript-eslint/utils": "7.1.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8" } }, - "node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "node_modules/brace-expansion": { + "version": "2.0.1", "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz", - "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==", + "node_modules/braces": { + "version": "3.0.2", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "fill-range": "^7.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8" } }, - "node_modules/@typescript-eslint/utils": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.0.tgz", - "integrity": "sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==", + "node_modules/brotli-size": { + "version": "4.0.0", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/typescript-estree": "7.1.0", - "semver": "^7.5.4" + "duplexer": "0.1.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">= 10.16.0" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "bin": { + "browserslist": "cli.js" }, - "peerDependencies": { - "eslint": "^8.56.0" + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", - "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", + "node_modules/bs-logger": { + "version": "0.2.6", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.1.0", - "eslint-visitor-keys": "^3.4.1" + "fast-json-stable-stringify": "2.x" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 6" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "node_modules/bser": { + "version": "2.1.1", "dev": true, - "peer": true, + "license": "Apache-2.0", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "node-int64": "^0.4.0" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "node_modules/buffer": { + "version": "6.0.3", + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", "dev": true, - "peer": true + "license": "MIT", + "engines": { + "node": "*" + } }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "node_modules/buffer-from": { + "version": "1.1.2", "dev": true, - "peer": true + "license": "MIT" }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "node_modules/builtin-modules": { + "version": "3.3.0", "dev": true, - "peer": true + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "node_modules/builtins": { + "version": "5.0.1", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" + "semver": "^7.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "17.1.4", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^7.7.1", + "minipass": "^7.0.3", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.3.10", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.18.3", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "node_modules/cacache/node_modules/minipass": { + "version": "7.0.4", "dev": true, - "peer": true + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "node_modules/cachedir": { + "version": "2.4.0", "dev": true, - "peer": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "node_modules/callsites": { + "version": "3.1.0", "dev": true, - "peer": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "node_modules/camelcase": { + "version": "5.3.1", "dev": true, - "peer": true, - "dependencies": { - "@xtuc/long": "4.2.2" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "node_modules/caniuse-lite": { + "version": "1.0.30001597", "dev": true, - "peer": 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/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "node_modules/caseless": { + "version": "0.12.0", "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "license": "Apache-2.0" + }, + "node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" } }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "node_modules/chalk-template/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, - "peer": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "node_modules/chalk-template/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, - "peer": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "node_modules/chalk-template/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, - "peer": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "node_modules/chalk-template/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, - "peer": true + "license": "MIT" }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "node_modules/chalk-template/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, - "peer": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=6.5" + "node": ">=8" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, + "node_modules/char-regex": { + "version": "1.0.2", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=10" } }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "bin": { - "acorn": "bin/acorn" - }, + "node_modules/check-more-types": { + "version": "2.24.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">= 0.8.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/chownr": { + "version": "2.0.0", "dev": true, - "peer": true, - "peerDependencies": { - "acorn": "^8" + "license": "ISC", + "engines": { + "node": ">=10" } }, - "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==", + "node_modules/chrome-remote-interface": { + "version": "0.27.2", "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "license": "MIT", + "dependencies": { + "commander": "2.11.x", + "ws": "^6.1.0" + }, + "bin": { + "chrome-remote-interface": "bin/client.js" } }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "engines": { - "node": ">=0.4.0" - } + "node_modules/chrome-remote-interface/node_modules/commander": { + "version": "2.11.0", + "dev": true, + "license": "MIT" }, - "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==", + "node_modules/chrome-remote-interface/node_modules/ws": { + "version": "6.2.2", "dev": true, + "license": "MIT", "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" + "async-limiter": "~1.0.0" } }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, + "peer": true, "engines": { - "node": ">= 8.0.0" + "node": ">=6.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/ci-info": { + "version": "3.9.0", "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "node_modules/cjs-module-lexer": { + "version": "1.2.3", "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } + "license": "MIT" }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "node_modules/clean-stack": { + "version": "2.2.0", "dev": true, - "dependencies": { - "string-width": "^4.1.0" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "node_modules/cli-boxes": { + "version": "2.2.1", "dev": true, + "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/cli-cursor": { + "version": "3.1.0", "dev": true, + "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" + "restore-cursor": "^3.1.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/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==", + "node_modules/cli-highlight": { + "version": "2.1.11", "dev": true, - "engines": { - "node": ">=10" + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" }, - "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==", "engines": { - "node": ">=8" + "node": ">=8.0.0", + "npm": ">=5.0.0" } }, - "node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "dev": true - }, - "node_modules/ansi-styles": { + "node_modules/cli-highlight/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" }, @@ -4271,1006 +5508,958 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, - "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==", + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", "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": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "node_modules/cli-highlight/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "color-name": "~1.1.4" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=7.0.0" } }, - "node_modules/are-we-there-yet/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==", + "node_modules/cli-highlight/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/argv-formatter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", - "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", - "dev": true - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "node_modules/cli-table3": { + "version": "0.6.3", "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, "engines": { - "node": ">=0.10.0" + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true + "node_modules/cli-truncate": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "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==", + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", "dev": true, + "license": "MIT", "dependencies": { - "safer-buffer": "~2.1.0" + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/clipboardy/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, "engines": { - "node": ">=0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "node_modules/clipboardy/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "node_modules/async-lock": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", - "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "node_modules/clipboardy/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">= 4.0.0" + "node": ">=10.17.0" } }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "node_modules/clipboardy/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": ">=8.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "node_modules/clipboardy/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": "*" + "node": ">=6" } }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true - }, - "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "node_modules/clipboardy/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.4", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "node_modules/clipboardy/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" }, - "peerDependencies": { - "@babel/core": "^7.8.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/babel-jest/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/clipboardy/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, + "license": "ISC" + }, + "node_modules/clipboardy/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": ">=8" + "node": ">=6" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "node_modules/cliui": { + "version": "8.0.1", "dev": true, + "license": "ISC", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "node_modules/co": { + "version": "4.6.0", "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" } }, - "node_modules/babel-plugin-istanbul/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/collect-v8-coverage": { + "version": "1.0.2", "dev": true, - "bin": { - "semver": "bin/semver.js" - } + "license": "MIT" }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "node_modules/color-convert": { + "version": "1.9.3", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "color-name": "1.1.3" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "node_modules/color-name": { + "version": "1.1.3", "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@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-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" } }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "node_modules/colorette": { + "version": "2.0.20", + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.4.0", "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">= 0.8" } }, - "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/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "engines": { + "node": ">=18" + } }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "node_modules/common-tags": { + "version": "1.8.2", "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" + "license": "MIT", + "engines": { + "node": ">=4.0.0" } }, - "node_modules/before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", - "dev": true + "node_modules/commondir": { + "version": "1.0.1", + "dev": true, + "license": "MIT" }, - "node_modules/blob-util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true + "node_modules/compare-func": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true + "node_modules/component-emitter": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">= 0.8.0" } }, - "node_modules/body-parser/node_modules/debug": { + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, - "node_modules/body-parser/node_modules/ms": { + "node_modules/compression/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "dev": true, + "license": "MIT", "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "ini": "^1.3.4", + "proto-list": "~1.2.1" } }, - "node_modules/bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "dev": true, + "license": "ISC" }, - "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "node_modules/console-control-strings": { + "version": "1.1.0", "dev": true, + "license": "ISC" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "license": "MIT", "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" + "safe-buffer": "5.2.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/boxen/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" + "node": ">= 0.6" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", "dev": true, + "license": "ISC", "dependencies": { - "fill-range": "^7.0.1" + "compare-func": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=16" } }, - "node_modules/brotli-size": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/brotli-size/-/brotli-size-4.0.0.tgz", - "integrity": "sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA==", + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", "dev": true, + "license": "ISC", "dependencies": { - "duplexer": "0.1.1" + "compare-func": "^2.0.0" }, "engines": { - "node": ">= 10.16.0" + "node": ">=16" } }, - "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "node_modules/conventional-changelog-writer": { + "version": "7.0.1", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "conventional-commits-filter": "^4.0.0", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "meow": "^12.0.1", + "semver": "^7.5.2", + "split2": "^4.0.0" }, "bin": { - "browserslist": "cli.js" + "conventional-changelog-writer": "cli.mjs" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=16" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "node_modules/conventional-commits-filter": { + "version": "4.0.0", "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=16" } }, - "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==", + "node_modules/conventional-commits-parser": { + "version": "5.0.0", "dev": true, + "license": "MIT", "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "node_modules/convert-source-map": { + "version": "2.0.0", "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "license": "MIT", "engines": { - "node": "*" + "node": ">= 0.6" } }, - "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 + "node_modules/cookie-signature": { + "version": "1.0.6", + "license": "MIT" }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "node_modules/cookiejar": { + "version": "2.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, "engines": { - "node": ">=6" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "node_modules/cosmiconfig-typescript-loader": { + "version": "5.0.0", "dev": true, + "license": "MIT", "dependencies": { - "semver": "^7.0.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "jiti": "^1.19.1" + }, "engines": { - "node": ">= 0.8" + "node": ">=v16" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=8.2", + "typescript": ">=4" } }, - "node_modules/cacache": { - "version": "17.1.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz", - "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==", + "node_modules/create-jest": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^7.7.1", - "minipass": "^7.0.3", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/cacache/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "node_modules/create-jest/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/create-jest/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cacache/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "node_modules/create-jest/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=7.0.0" } }, - "node_modules/cachedir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", - "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "node_modules/create-jest/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "engines": { - "node": ">=6" - } + "license": "MIT" }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/create-jest/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/cross-env": { + "version": "7.0.3", "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, "engines": { - "node": ">=6" + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/cross-spawn": { + "version": "7.0.3", "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "engines": { - "node": ">=6" + "node": ">= 8" } }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "node_modules/crypto-random-string": { + "version": "4.0.0", "dev": true, + "license": "MIT", "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" + "type-fest": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001591", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz", - "integrity": "sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==", + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", "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": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.11.0.tgz", + "integrity": "sha512-NXXogbAxVlVje4XHX+Cx5eMFZv4Dho/2rIcdBHg9CNPFUGZdM4cRdgIgM7USmNYsC12XY0bZENEQ+KBk72fl+A==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@cypress/request": "^3.0.0", + "@cypress/xvfb": "^1.2.4", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.7.1", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.1", + "commander": "^6.2.1", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.4", + "enquirer": "^2.3.6", + "eventemitter2": "6.4.7", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-ci": "^3.0.1", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.8", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "process": "^0.11.10", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.5.3", + "supports-color": "^8.1.1", + "tmp": "~0.2.1", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": "^16.0.0 || ^18.0.0 || >=20.0.0" + } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true + "node_modules/cypress-log-to-output": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2", + "chrome-remote-interface": "^0.27.1" + } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/cypress-log-to-output/node_modules/ansi-styles": { + "version": "3.2.1", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=4" } }, - "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==", + "node_modules/cypress-log-to-output/node_modules/chalk": { + "version": "2.4.2", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, "engines": { - "node": ">=10" + "node": ">=4" } }, - "node_modules/check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "node_modules/cypress-log-to-output/node_modules/escape-string-regexp": { + "version": "1.0.5", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=0.8.0" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "node_modules/cypress-log-to-output/node_modules/has-flag": { + "version": "3.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=4" } }, - "node_modules/chrome-remote-interface": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.27.2.tgz", - "integrity": "sha512-pVLljQ29SAx8KIv5tSa9sIf8GrEsAZdPJoeWOmY3/nrIzFmE+EryNNHvDkddGod0cmAFTv+GmPG0uvzxi2NWsA==", + "node_modules/cypress-log-to-output/node_modules/supports-color": { + "version": "5.5.0", "dev": true, + "license": "MIT", "dependencies": { - "commander": "2.11.x", - "ws": "^6.1.0" + "has-flag": "^3.0.0" }, - "bin": { - "chrome-remote-interface": "bin/client.js" + "engines": { + "node": ">=4" } }, - "node_modules/chrome-remote-interface/node_modules/commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", - "dev": true - }, - "node_modules/chrome-remote-interface/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "node_modules/cypress/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "peer": true, + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=6.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "node_modules/cypress/node_modules/buffer": { + "version": "5.7.1", "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } ], - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "node_modules/cypress/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "node_modules/cypress/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "restore-cursor": "^3.1.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "node_modules/cypress/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" + "color-name": "~1.1.4" }, "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "node": ">=7.0.0" } }, - "node_modules/cli-highlight/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/cypress/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, - "node_modules/cli-highlight/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "node_modules/cypress/node_modules/commander": { + "version": "6.2.1", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 6" } }, - "node_modules/cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "node_modules/cypress/node_modules/execa": { + "version": "4.1.0", "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^4.2.0" + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": "10.* || >= 12.*" + "node": ">=10" }, - "optionalDependencies": { - "@colors/colors": "1.5.0" + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "node_modules/cypress/node_modules/get-stream": { + "version": "5.2.0", "dev": true, + "license": "MIT", "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "pump": "^3.0.0" }, "engines": { "node": ">=8" @@ -5279,1902 +6468,1729 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "node_modules/cypress/node_modules/human-signals": { + "version": "1.1.1", "dev": true, + "license": "Apache-2.0", "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true - }, - "node_modules/collect.js": { - "version": "4.36.1", - "resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.36.1.tgz", - "integrity": "sha512-jd97xWPKgHn6uvK31V6zcyPd40lUJd7gpYxbN2VOVxGWO4tyvS9Li4EpsFjXepGTo2tYcOTC4a8YsbQXMJ4XUw==" - }, - "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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "node": ">=8.12.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==" - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "node_modules/cypress/node_modules/is-stream": { + "version": "2.0.1", "dev": true, - "bin": { - "color-support": "bin.js" + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "node_modules/cypress/node_modules/mimic-fn": { + "version": "2.1.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.1.90" + "node": ">=6" } }, - "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==", + "node_modules/cypress/node_modules/npm-run-path": { + "version": "4.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" + "path-key": "^3.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "node_modules/cypress/node_modules/onetime": { + "version": "5.1.2", "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, "engines": { - "node": ">= 6" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "node_modules/cypress/node_modules/proxy-from-env": { + "version": "1.0.0", "dev": true, - "engines": { - "node": ">=4.0.0" - } + "license": "MIT" }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true + "node_modules/cypress/node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" }, - "node_modules/compare-func": { + "node_modules/cypress/node_modules/strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "node_modules/dargs": { + "version": "8.1.0", "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "node_modules/dashdash": { + "version": "1.14.1", "dev": true, + "license": "MIT", "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" + "assert-plus": "^1.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=0.10" } }, - "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==", + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "engines": { - "node": ">= 0.6" + "node": "*" } }, - "node_modules/conventional-changelog-angular": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", - "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "node_modules/dayjs": { + "version": "1.11.10", "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dependencies": { - "compare-func": "^2.0.0" + "ms": "2.1.2" }, "engines": { - "node": ">=16" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", - "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "node_modules/decimal.js-light": { + "version": "2.5.1", + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.5.1", "dev": true, - "dependencies": { - "compare-func": "^2.0.0" + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" }, - "engines": { - "node": ">=16" + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/conventional-changelog-writer": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-7.0.1.tgz", - "integrity": "sha512-Uo+R9neH3r/foIvQ0MKcsXkX642hdm9odUp7TqgFS7BsalTcjzRlIfWZrZR1gbxOozKucaKt5KAbjW8J8xRSmA==", + "node_modules/deep-extend": { + "version": "0.6.0", "dev": true, - "dependencies": { - "conventional-commits-filter": "^4.0.0", - "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "meow": "^12.0.1", - "semver": "^7.5.2", - "split2": "^4.0.0" - }, - "bin": { - "conventional-changelog-writer": "cli.mjs" - }, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=4.0.0" } }, - "node_modules/conventional-commits-filter": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-4.0.0.tgz", - "integrity": "sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A==", + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=0.10.0" } }, - "node_modules/conventional-commits-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", - "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", - "dev": true, + "node_modules/define-data-property": { + "version": "1.1.4", + "license": "MIT", "dependencies": { - "is-text-path": "^2.0.0", - "JSONStream": "^1.3.5", - "meow": "^12.0.1", - "split2": "^4.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, - "bin": { - "conventional-commits-parser": "cli.mjs" + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=8" } }, - "node_modules/convert-source-map": { + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/depd": { "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", + "engines": { + "node": ">= 0.8" + } }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "node_modules/detect-newline": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true + "node_modules/dezalgo": { + "version": "1.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } }, - "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 + "node_modules/diff-sequences": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "node_modules/dir-glob": { + "version": "3.0.1", "dev": true, + "license": "MIT", "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8" } }, - "node_modules/cosmiconfig-typescript-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz", - "integrity": "sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==", + "node_modules/doctrine": { + "version": "3.0.0", "dev": true, + "license": "Apache-2.0", "dependencies": { - "jiti": "^1.19.1" + "esutils": "^2.0.2" }, "engines": { - "node": ">=v16" - }, - "peerDependencies": { - "@types/node": "*", - "cosmiconfig": ">=8.2", - "typescript": ">=4" + "node": ">=6.0.0" } }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "node_modules/dot-prop": { + "version": "5.3.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" + "is-obj": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + "node_modules/duplexer": { + "version": "0.1.1", + "dev": true }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/duplexer2": { + "version": "0.1.4", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "readable-stream": "^2.0.2" } }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", "dev": true, + "license": "MIT", "dependencies": { - "type-fest": "^1.0.1" - }, + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ecc-jsbn/node_modules/jsbn": { + "version": "0.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.701", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/cypress": { - "version": "13.6.6", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.6.tgz", - "integrity": "sha512-S+2S9S94611hXimH9a3EAYt81QM913ZVA03pUmGDfLTFa5gyp85NJ8dJGSlEAEmyRsYkioS1TtnWtbv/Fzt11A==", + "node_modules/encoding": { + "version": "0.1.13", "dev": true, - "hasInstallScript": true, + "license": "MIT", + "optional": true, "dependencies": { - "@cypress/request": "^3.0.0", - "@cypress/xvfb": "^1.2.4", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.7.1", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-ci": "^3.0.1", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.5.3", - "supports-color": "^8.1.1", - "tmp": "~0.2.1", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - }, - "bin": { - "cypress": "bin/cypress" + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": "^16.0.0 || ^18.0.0 || >=20.0.0" + "node": ">=0.10.0" } }, - "node_modules/cypress-log-to-output": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cypress-log-to-output/-/cypress-log-to-output-1.1.2.tgz", - "integrity": "sha512-C1+ECMc/XXc4HqAEHdlw0X2wFhcoZZ/4qXHZkOAU/rRXMQXnbiO7JJtpLCKrLfOXlxB+jFwDAIdlPxPMfV3cFw==", + "node_modules/end-of-stream": { + "version": "1.4.4", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.16.0", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^2.4.2", - "chrome-remote-interface": "^0.27.1" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" } }, - "node_modules/cypress-log-to-output/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/enquirer": { + "version": "2.4.1", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=4" + "node": ">=8.6" } }, - "node_modules/cypress-log-to-output/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/env-ci": { + "version": "11.0.0", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "execa": "^8.0.0", + "java-properties": "^1.0.2" }, "engines": { - "node": ">=4" + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/cypress-log-to-output/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "is-arrayish": "^0.2.1" } }, - "node_modules/cypress-log-to-output/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/cypress-log-to-output/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, + "node_modules/es-define-property": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, "engines": { - "node": ">=0.8.0" + "node": ">= 0.4" } }, - "node_modules/cypress-log-to-output/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.4" } }, - "node_modules/cypress-log-to-output/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/es-module-lexer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz", + "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==", "dev": true, - "dependencies": { - "has-flag": "^3.0.0" + "peer": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/cypress/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "node_modules/escalade": { + "version": "3.1.2", "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" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/cypress/node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, + "license": "MIT", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cypress/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/eslint": { + "version": "8.57.0", "dev": true, + "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/cypress/node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "node_modules/eslint-config-prettier": { + "version": "9.1.0", "dev": true, - "engines": { - "node": ">=8.12.0" + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/cypress/node_modules/proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true - }, - "node_modules/cypress/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/eslint-scope": { + "version": "7.2.2", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "has-flag": "^4.0.0" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/dargs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", "dev": true, + "license": "MIT", "dependencies": { - "assert-plus": "^1.0.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": ">=0.10" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": "*" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", - "dev": true + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=0.10.0" + "node": ">=7.0.0" } }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", "dev": true, + "license": "MIT", "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decimal.js-light": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + "license": "MIT" }, - "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", "dev": true, - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, "engines": { - "node": ">=4.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=0.10.0" + "node": "*" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "p-limit": "^3.0.2" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, - "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==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "dev": true - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "node_modules/espree": { + "version": "9.6.1", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">=0.3.1" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "node_modules/esprima": { + "version": "4.0.1", "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=4" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/esquery": { + "version": "1.5.0", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "path-type": "^4.0.0" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=8" + "node": ">=0.10" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/esrecurse": { + "version": "4.3.0", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "esutils": "^2.0.2" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=4.0" } }, - "node_modules/dot-prop": { + "node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">=8" + "node": ">=4.0" } }, - "node_modules/duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha512-sxNZ+ljy+RA1maXoUReeqBBpBC6RLKmg5ewzV+x+mSETmWNoKdZN6vcQjpFROemza23hGFskJtFNoUWUaQ+R4Q==", - "dev": true - }, - "node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "node_modules/estree-walker": { + "version": "2.0.2", "dev": true, - "dependencies": { - "readable-stream": "^2.0.2" - } + "license": "MIT" }, - "node_modules/duplexer2/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/esutils": { + "version": "2.0.3", "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/duplexer2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/duplexer2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" + "node_modules/etag": { + "version": "1.8.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "node_modules/eventemitter2": { + "version": "6.4.7", "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } + "license": "MIT" }, - "node_modules/ecc-jsbn/node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "node_modules/eventemitter3": { + "version": "5.0.1", + "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==" + "node_modules/events": { + "version": "3.3.0", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } }, - "node_modules/electron-to-chromium": { - "version": "1.4.685", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.685.tgz", - "integrity": "sha512-yDYeobbTEe4TNooEzOQO6xFqg9XnAkVy2Lod1C1B2it8u47JNLYvl9nLDWBamqUakWB8Jc1hhS1uHUNYTNQdfw==", - "dev": true + "node_modules/eventsource": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "node_modules/execa": { + "version": "8.0.1", "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, "engines": { - "node": ">=12" + "node": ">=16.17" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "url": "https://github.com/sindresorhus/execa?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==" - }, - "node_modules/emojilib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", - "dev": true + "node_modules/executable": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/exit": { + "version": "0.1.2", + "dev": true, "engines": { - "node": ">= 0.8" + "node": ">= 0.8.0" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "node_modules/expect": { + "version": "29.7.0", "dev": true, - "optional": true, + "license": "MIT", "dependencies": { - "iconv-lite": "^0.6.2" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/exponential-backoff": { + "version": "3.1.1", "dev": true, - "optional": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "4.19.2", + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.10.0" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", "dependencies": { - "once": "^1.4.0" + "ms": "2.0.0" } }, - "node_modules/enhanced-resolve": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.1.tgz", - "integrity": "sha512-3d3JRbwsCLJsYgvb6NuWEG44jjPSOMuS73L/6+7BZuoKm3W+qXnSoIYVHi8dG7Qcg4inAY4jbzkZ7MnskePeDg==", - "dev": true, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/express/node_modules/qs": { + "version": "6.11.0", + "license": "BSD-3-Clause", "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "side-channel": "^1.0.4" }, "engines": { - "node": ">=10.13.0" + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "node_modules/extend": { + "version": "3.0.2", "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } + "license": "MIT" }, - "node_modules/env-ci": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.0.0.tgz", - "integrity": "sha512-apikxMgkipkgTvMdRT9MNqWx5VLOci79F4VBd7Op/7OPjjoanjdAvn6fglMCCEf/1bAh8eOiuEVCUs4V3qP3nQ==", + "node_modules/extract-zip": { + "version": "2.0.1", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "execa": "^8.0.0", - "java-properties": "^1.0.2" + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" }, "engines": { - "node": "^18.17 || >=20.6.1" + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" } }, - "node_modules/env-ci/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", "dev": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "pump": "^3.0.0" }, "engines": { - "node": ">=16.17" + "node": ">=8" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/env-ci/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "node_modules/extsprintf": { + "version": "1.3.0", "dev": true, - "engines": { - "node": ">=16" + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8.6.0" } }, - "node_modules/env-ci/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, "engines": { - "node": ">=16.17.0" + "node": ">= 6" } }, - "node_modules/env-ci/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", "dev": true, + "license": "MIT" + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/env-ci/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "license": "MIT" + }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "MIT", + "dependencies": { + "punycode": "^1.3.2" } }, - "node_modules/env-ci/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "node_modules/fast-url-parser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", "dev": true, + "license": "ISC", "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "reusify": "^1.0.4" } }, - "node_modules/env-ci/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/fb-watchman": { + "version": "2.0.2", "dev": true, + "license": "Apache-2.0", "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bser": "2.1.1" } }, - "node_modules/env-ci/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "node_modules/fd-slicer": { + "version": "1.1.0", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" } }, - "node_modules/env-ci/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==", + "node_modules/figures": { + "version": "3.2.0", "dev": true, - "engines": { - "node": ">=14" + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/env-ci/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.8.0" } }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/file-entry-cache": { + "version": "6.0.1", "dev": true, + "license": "MIT", "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" + "flat-cache": "^3.0.4" }, "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==", - "engines": { - "node": ">= 0.4" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/es-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", - "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", - "dev": true, - "peer": true - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "node_modules/filesize": { + "version": "6.4.0", "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=6" + "node": ">= 0.4.0" } }, - "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==" - }, - "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==", + "node_modules/fill-range": { + "version": "7.0.1", "dev": true, - "engines": { - "node": ">=10" + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, + "node_modules/finalhandler": { + "version": "1.2.0", + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">= 0.8" } }, - "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "7.0.0", "dev": true, + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/find-up-simple": { + "version": "1.0.0", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/find-versions": { + "version": "5.1.0", "dev": true, + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "semver-regex": "^4.0.5" + }, + "engines": { + "node": ">=12" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/flat-cache": { + "version": "3.2.0", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "node_modules/flatbuffers": { + "version": "24.3.25", + "license": "Apache-2.0" }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/flatted": { + "version": "3.3.1", "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", "engines": { - "node": "*" + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/foreground-child": { + "version": "3.1.1", "dev": true, + "license": "ISC", "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=14" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/isaacs" } }, - "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/forever-agent": { + "version": "0.6.1", "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, + "license": "Apache-2.0", "engines": { - "node": ">=4" + "node": "*" } }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, + "node_modules/form-data": { + "version": "4.0.0", + "license": "MIT", "dependencies": { - "estraverse": "^5.1.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" }, "engines": { - "node": ">=0.10" + "node": ">= 6" } }, - "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==", + "node_modules/formidable": { + "version": "2.1.2", "dev": true, + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" }, - "engines": { - "node": ">=4.0" + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/formidable/node_modules/qs": { + "version": "6.12.0", "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, "engines": { - "node": ">=4.0" + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, + "node_modules/forwarded": { + "version": "0.2.0", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "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/fresh": { + "version": "0.5.2", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" + "node_modules/from2": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" } }, - "node_modules/eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" + "node_modules/from2/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/eventsource": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", - "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", - "engines": { - "node": ">=12.0.0" + "node_modules/from2/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/from2/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/fs-extra": { + "version": "9.1.0", "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" + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "node_modules/fs-minipass": { + "version": "3.0.3", "dev": true, + "license": "ISC", "dependencies": { - "pify": "^2.2.0" + "minipass": "^7.0.3" }, "engines": { - "node": ">=4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/fs-minipass/node_modules/minipass": { + "version": "7.0.4", "dev": true, + "license": "ISC", "engines": { - "node": ">= 0.8.0" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "node_modules/fs.realpath": { + "version": "1.0.0", "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "dev": true + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "node_modules/gauge": { + "version": "4.0.4", + "dev": true, + "license": "ISC", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" }, "engines": { - "node": ">= 0.10.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, - "node_modules/express/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "node_modules/get-intrinsic": { + "version": "1.2.4", + "license": "MIT", "dependencies": { - "side-channel": "^1.0.4" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "engines": { - "node": ">=0.6" + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "node_modules/get-package-type": { + "version": "0.1.0", "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, + "license": "MIT", "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" + "node": ">=8.0.0" } }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/get-stream": { + "version": "8.0.1", "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fast-copy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz", - "integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==" - }, - "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==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "resolve-pkg-maps": "^1.0.0" }, - "engines": { - "node": ">=8.6.0" + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/getos": { + "version": "3.2.1", "dev": true, + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" + "async": "^3.2.0" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "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==", - "dev": true - }, - "node_modules/fast-redact": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz", - "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==", - "engines": { - "node": ">=6" + "node_modules/getpass": { + "version": "0.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" } }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "node_modules/git-log-parser": { + "version": "1.2.0", "dev": true, + "license": "MIT", "dependencies": { - "reusify": "^1.0.4" + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "~0.6.6" } }, - "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==", + "node_modules/git-log-parser/node_modules/split2": { + "version": "1.0.0", "dev": true, + "license": "ISC", "dependencies": { - "bser": "2.1.1" + "through2": "~2.0.0" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "node_modules/git-raw-commits": { + "version": "4.0.0", "dev": true, + "license": "MIT", "dependencies": { - "pend": "~1.2.0" + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.mjs" + }, + "engines": { + "node": ">=16" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "node_modules/glob": { + "version": "8.1.0", "dev": true, + "license": "ISC", "dependencies": { - "escape-string-regexp": "^1.0.5" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/glob-parent": { + "version": "6.0.2", "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, "engines": { - "node": ">=0.8.0" + "node": ">=10.13.0" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true, + "peer": true + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", "dependencies": { - "flat-cache": "^3.0.4" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=10" } }, - "node_modules/filesize": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.4.0.tgz", - "integrity": "sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ==", + "node_modules/global-directory": { + "version": "4.0.1", "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, "engines": { - "node": ">= 0.4.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "node_modules/global-dirs": { + "version": "3.0.1", "dev": true, + "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "ini": "2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "dev": true, + "license": "ISC", "engines": { - "node": ">= 0.8" + "node": ">=10" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "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/globby": { + "version": "11.1.0", "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, "engines": { "node": ">=10" @@ -7183,517 +8199,570 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-up-simple": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", - "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", + "node_modules/globby/node_modules/slash": { + "version": "3.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=8" + } + }, + "node_modules/golem-js-examples": { + "resolved": "examples", + "link": true + }, + "node_modules/gopd": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-versions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", - "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gzip-size": { + "version": "6.0.0", "dev": true, + "license": "MIT", "dependencies": { - "semver-regex": "^4.0.5" + "duplexer": "^0.1.2" }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "node_modules/gzip-size/node_modules/duplexer": { + "version": "0.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.8", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/flatbuffers": { - "version": "23.5.26", - "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-23.5.26.tgz", - "integrity": "sha512-vE+SI9vrJDwi1oETtTIFldC/o9GsVKRM+s6EL0nQgxXlYV1Vc4Tk30hj4xGICftInKQKj1F3up2n8UbIVobISQ==" - }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "node": ">=8" } }, - "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" + "es-define-property": "^1.0.0" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/foreground-child/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==", + "node_modules/has-symbols": { + "version": "1.0.3", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "node_modules/has-unicode": { + "version": "2.0.1", "dev": true, - "engines": { - "node": "*" - } + "license": "ISC" }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "function-bind": "^1.1.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" } }, - "node_modules/formidable": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", - "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, + "node_modules/hexoid": { + "version": "1.0.0", "dev": true, - "dependencies": { - "dezalgo": "^1.0.4", - "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hook-std": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/formidable/node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "node_modules/hosted-git-info": { + "version": "6.1.1", "dev": true, + "license": "ISC", "dependencies": { - "side-channel": "^1.0.4" + "lru-cache": "^7.5.1" }, "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "7.18.3", + "dev": true, + "license": "ISC", "engines": { - "node": ">= 0.6" + "node": ">=12" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "node_modules/http-proxy-agent": { + "version": "5.0.0", "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/from2/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/http-signature": { + "version": "1.3.6", "dev": true, + "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/from2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "node_modules/human-signals": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } }, - "node_modules/from2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/humanize-ms": { + "version": "1.2.1", "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "ms": "^2.0.0" } }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "node_modules/husky": { + "version": "9.0.11", "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "license": "MIT", + "bin": { + "husky": "bin.mjs" }, "engines": { - "node": ">=10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" } }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "dev": true, + "node_modules/iconv-lite": { + "version": "0.4.24", + "license": "MIT", "dependencies": { - "minipass": "^7.0.3" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "node_modules/ieee754": { + "version": "1.2.1", + "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": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.1", "dev": true, + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">= 4" } }, - "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 - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/ignore-walk": { + "version": "6.0.4", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "node_modules/import-fresh": { + "version": "3.3.0", "dev": true, + "license": "MIT", "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=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/import-from-esm": { + "version": "1.3.3", "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "import-meta-resolve": "^4.0.0" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=16.20" } }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "node_modules/import-local": { + "version": "3.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "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==", + "node_modules/import-meta-resolve": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", "dev": true, + "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">=0.8.19" } }, - "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==", + "node_modules/indent-string": { + "version": "4.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" + } + }, + "node_modules/index-to-position": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/getos": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", - "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "node_modules/infer-owner": { + "version": "1.0.4", "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", "dependencies": { - "async": "^3.2.0" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.1", "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/git-log-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", - "integrity": "sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==", + "node_modules/into-stream": { + "version": "7.0.0", "dev": true, + "license": "MIT", "dependencies": { - "argv-formatter": "~1.0.0", - "spawn-error-forwarder": "~1.0.0", - "split2": "~1.0.0", - "stream-combiner2": "~1.1.1", - "through2": "~2.0.0", - "traverse": "~0.6.6" + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/git-log-parser/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/ip-address": { + "version": "9.0.5", "dev": true, + "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-num": { + "version": "1.5.1", + "license": "MIT" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" } }, - "node_modules/git-log-parser/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "node_modules/is-arrayish": { + "version": "0.2.1", + "dev": true, + "license": "MIT" }, - "node_modules/git-log-parser/node_modules/split2": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", - "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "node_modules/is-builtin-module": { + "version": "3.2.1", "dev": true, + "license": "MIT", "dependencies": { - "through2": "~2.0.0" + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/git-log-parser/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/is-ci": { + "version": "3.0.1", "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" } }, - "node_modules/git-log-parser/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "node_modules/is-core-module": { + "version": "2.13.1", "dev": true, + "license": "MIT", "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/git-raw-commits": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", - "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", + "node_modules/is-docker": { + "version": "2.2.1", "dev": true, - "dependencies": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, + "license": "MIT", "bin": { - "git-raw-commits": "cli.js" + "is-docker": "cli.js" }, "engines": { - "node": ">=10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/git-raw-commits/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/is-extglob": { + "version": "2.1.1", "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/git-raw-commits/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/git-raw-commits/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/is-generator-fn": { + "version": "2.1.0", "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/git-raw-commits/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/is-glob": { + "version": "4.0.3", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/git-raw-commits/node_modules/meow": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", - "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "node_modules/is-installed-globally": { + "version": "0.4.0", "dev": true, + "license": "MIT", "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" }, "engines": { "node": ">=10" @@ -7702,1800 +8771,1635 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/git-raw-commits/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "node_modules/is-lambda": { + "version": "1.0.1", "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, - "node_modules/git-raw-commits/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/is-module": { + "version": "1.0.0", "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.12.0" } }, - "node_modules/git-raw-commits/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/is-obj": { + "version": "2.0.0", "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/git-raw-commits/node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "node_modules/is-path-inside": { + "version": "3.0.3", "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/git-raw-commits/node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/git-raw-commits/node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/is-reference": { + "version": "1.2.1", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "@types/estree": "*" } }, - "node_modules/git-raw-commits/node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true + "node_modules/is-stream": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/git-raw-commits/node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "node_modules/is-text-path": { + "version": "2.0.0", "dev": true, + "license": "MIT", "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/git-raw-commits/node_modules/read-pkg/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/is-typedarray": { + "version": "1.0.0", "dev": true, - "bin": { - "semver": "bin/semver" - } + "license": "MIT" }, - "node_modules/git-raw-commits/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/git-raw-commits/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==", + "node_modules/is-wsl": { + "version": "2.2.0", "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "is-docker": "^2.0.0" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/git-raw-commits/node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "node_modules/isarray": { + "version": "1.0.0", "dev": true, - "dependencies": { - "readable-stream": "^3.0.0" - } + "license": "MIT" }, - "node_modules/git-raw-commits/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "node_modules/isexe": { + "version": "2.0.0", "dev": true, - "engines": { - "node": ">=10" + "license": "ISC" + }, + "node_modules/isstream": { + "version": "0.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/issue-parser": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^18.17 || >=20.6.1" } }, - "node_modules/git-raw-commits/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/git-raw-commits/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/istanbul-lib-instrument": { + "version": "6.0.2", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "@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": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10" } }, - "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/istanbul-lib-report": { + "version": "3.0.1", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "is-glob": "^4.0.3" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=10" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true - }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "ini": "2.0.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/istanbul-reports": { + "version": "3.1.7", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "type-fest": "^0.20.2" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/jackspeak": { + "version": "2.3.6", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/globby/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/java-properties": { + "version": "1.0.2", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.6.0" } }, - "node_modules/golem-js-examples": { - "resolved": "examples", - "link": true - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "node_modules/jest": { + "version": "29.7.0", + "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3" + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/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 - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "node_modules/jest-changed-files": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "node_modules/jest-changed-files/node_modules/execa": { + "version": "5.1.1", "dev": true, + "license": "MIT", "dependencies": { - "duplexer": "^0.1.2" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/gzip-size/node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "node_modules/jest-changed-files/node_modules/get-stream": { + "version": "6.0.1", "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, + "license": "MIT", "engines": { - "node": ">=0.4.7" + "node": ">=10" }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hard-rejection": { + "node_modules/jest-changed-files/node_modules/human-signals": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6" + "node": ">=10.17.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==", + "node_modules/jest-changed-files/node_modules/is-stream": { + "version": "2.0.1", "dev": true, + "license": "MIT", "engines": { "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/jest-changed-files/node_modules/mimic-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=6" + } + }, + "node_modules/jest-changed-files/node_modules/npm-run-path": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/jest-changed-files/node_modules/onetime": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, "engines": { - "node": ">= 0.4" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true + "node_modules/jest-changed-files/node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" }, - "node_modules/hasown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", - "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", - "dependencies": { - "function-bind": "^1.1.2" - }, + "node_modules/jest-changed-files/node_modules/strip-final-newline": { + "version": "2.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=6" } }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" - }, - "node_modules/hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "node_modules/jest-circus": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": "*" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/hook-std": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", - "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==", + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^7.5.1" + "color-name": "~1.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=7.0.0" } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "engines": { - "node": ">=12" - } - }, - "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 - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true + "license": "MIT" }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, + "node_modules/jest-circus/node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "node_modules/jest-cli": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.14.1" + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">=0.10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/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/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/husky": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", - "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", - "dev": true, - "bin": { - "husky": "bin.mjs" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/typicode" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=7.0.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "engines": { - "node": ">= 4" - } + "license": "MIT" }, - "node_modules/ignore-walk": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.4.tgz", - "integrity": "sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==", + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "minimatch": "^9.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/jest-config": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-from-esm": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.3.tgz", - "integrity": "sha512-U3Qt/CyfFpTUv6LOP2jRTLYjphH6zg3okMfHbyqRa/W2w6hr8OsJWVggNlR4jxuojQy81TgTJTxgSkyoteRGMQ==", - "dev": true, - "dependencies": { - "debug": "^4.3.4", - "import-meta-resolve": "^4.0.0" + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" }, - "engines": { - "node": ">=16.20" + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" }, "engines": { "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/import-meta-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", - "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "1.1.11", "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=0.8.19" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=8" + "node": ">=7.0.0" } }, - "node_modules/index-to-position": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz", - "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==", + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", "dev": true, + "license": "MIT" + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { - "node": ">=18" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "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==", + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.2", "dev": true, + "license": "ISC", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "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/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "node_modules/jest-config/node_modules/slash": { + "version": "3.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/into-stream": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", - "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "node_modules/jest-diff": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 12" - } - }, - "node_modules/ip-num": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ip-num/-/ip-num-1.5.1.tgz", - "integrity": "sha512-QziFxgxq3mjIf5CuwlzXFYscHxgLqdEdJKRo2UJ5GurL5zrSRMzT/O+nK0ABimoFH8MWF8YwIiwECYsHc1LpUQ==" - }, - "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==", - "engines": { - "node": ">= 0.10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "builtin-modules": "^3.3.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "ci-info": "^3.2.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, - "bin": { - "is-ci": "bin.js" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "color-name": "~1.1.4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=7.0.0" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "bin": { - "is-docker": "cli.js" + "license": "MIT" + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/jest-docblock": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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/jest-each": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "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==", + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=0.12.0" + "node": ">=7.0.0" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/jest-environment-node": { + "version": "29.7.0", "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "node_modules/jest-get-type": { + "version": "29.6.3", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "node_modules/jest-haste-map": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "*" - } - }, - "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, + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/is-text-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", - "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "node_modules/jest-junit": { + "version": "16.0.0", "dev": true, + "license": "Apache-2.0", "dependencies": { - "text-extensions": "^2.0.0" + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">=10.12.0" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/jest-junit/node_modules/uuid": { + "version": "8.3.2", "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "is-docker": "^2.0.0" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "node_modules/issue-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", - "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "lodash.capitalize": "^4.2.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.uniqby": "^4.7.0" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=10.13" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", "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/istanbul-lib-instrument": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", - "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "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" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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==", + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=10" + "node": ">=7.0.0" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "node_modules/jest-message-util": { + "version": "29.7.0", "dev": true, + "license": "MIT", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=14" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/java-properties": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", - "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", - "dev": true, - "engines": { - "node": ">= 0.6.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" + "color-name": "~1.1.4" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=7.0.0" } }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, + "license": "MIT" + }, + "node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-circus/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/jest-cli": { + "node_modules/jest-mock": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "jest-resolve": "*" }, "peerDependenciesMeta": { - "node-notifier": { + "jest-resolve": { "optional": true } } }, - "node_modules/jest-config": { + "node_modules/jest-regex-util": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-config/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "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" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "*" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-config/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "color-name": "~1.1.4" }, "engines": { - "node": "*" + "node": ">=7.0.0" } }, - "node_modules/jest-config/node_modules/slash": { + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-resolve/node_modules/slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/jest-diff": { + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, + "license": "MIT", "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "detect-newline": "^3.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-environment-node": { + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "1.1.11", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-junit": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", - "integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==", + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "mkdirp": "^1.0.4", - "strip-ansi": "^6.0.1", - "uuid": "^8.3.2", - "xml": "^1.0.1" + "color-name": "~1.1.4" }, "engines": { - "node": ">=10.12.0" + "node": ">=7.0.0" } }, - "node_modules/jest-junit/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } + "license": "MIT" }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", "dev": true, + "license": "ISC", "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.2", "dev": true, + "license": "ISC", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "*" } }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "node_modules/jest-runtime/node_modules/slash": { + "version": "3.0.0", "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/jest-mock": { + "node_modules/jest-snapshot": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, + "license": "MIT", "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-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/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" + "color-name": "~1.1.4" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=7.0.0" } }, - "node_modules/jest-resolve/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "has-flag": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-runtime": { + "node_modules/jest-util": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-runtime/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "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" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "*" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-runtime/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "color-name": "~1.1.4" }, "engines": { - "node": "*" + "node": ">=7.0.0" } }, - "node_modules/jest-runtime/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" + "has-flag": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-util": { + "node_modules/jest-validate": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", - "@types/node": "*", + "camelcase": "^6.2.0", "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -9503,11 +10407,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "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/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-watcher": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, + "license": "MIT", "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -9522,41 +10467,80 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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/jest-watcher/node_modules/chalk": { + "version": "4.1.2", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jiti": { "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "dev": true, + "license": "MIT", "bin": { "jiti": "bin/jiti.js" } @@ -9571,20 +10555,17 @@ }, "node_modules/js-sha3": { "version": "0.9.3", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz", - "integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==" + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -9594,15 +10575,13 @@ }, "node_modules/jsbn": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jsesc": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, @@ -9612,51 +10591,43 @@ }, "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==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-parse-better-errors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "dev": 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 + "dev": true, + "license": "MIT" }, "node_modules/json-schema": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-traverse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "dev": 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==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/json5": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -9666,15 +10637,13 @@ }, "node_modules/jsonc-parser": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jsonfile": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -9684,18 +10653,16 @@ }, "node_modules/jsonparse": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "dev": true, "engines": [ "node >= 0.2.0" - ] + ], + "license": "MIT" }, "node_modules/JSONStream": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "dev": true, + "license": "(MIT OR Apache-2.0)", "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -9709,12 +10676,11 @@ }, "node_modules/jsprim": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", "dev": true, "engines": [ "node >=0.6.0" ], + "license": "MIT", "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -9724,54 +10690,40 @@ }, "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==", "dev": true, + "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "json-buffer": "3.0.1" } }, "node_modules/kleur": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/lazy-ass": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", "dev": true, + "license": "MIT", "engines": { "node": "> 0.8" } }, "node_modules/leven": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -9782,15 +10734,13 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/listr2": { "version": "3.14.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", - "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", "dev": true, + "license": "MIT", "dependencies": { "cli-truncate": "^2.1.0", "colorette": "^2.0.16", @@ -9815,9 +10765,8 @@ }, "node_modules/load-json-file": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -9830,9 +10779,8 @@ }, "node_modules/load-json-file/node_modules/parse-json": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, + "license": "MIT", "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -9843,18 +10791,16 @@ }, "node_modules/load-json-file/node_modules/pify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/load-json-file/node_modules/strip-bom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -9870,15 +10816,14 @@ } }, "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "version": "7.2.0", "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "p-locate": "^6.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9886,117 +10831,93 @@ }, "node_modules/lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash-es": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.camelcase": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.capitalize": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", - "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.escaperegexp": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", - "dev": true - }, - "node_modules/lodash.isfunction": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.kebabcase": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.mergewith": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.snakecase": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.startcase": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.uniqby": { "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", - "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.upperfirst": { "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -10008,11 +10929,66 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "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/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "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/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/log-update": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", @@ -10026,11 +11002,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "4.3.0", + "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/log-update/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-update/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, "node_modules/log-update/node_modules/slice-ansi": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -10045,9 +11050,8 @@ }, "node_modules/log-update/node_modules/wrap-ansi": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -10059,24 +11063,21 @@ }, "node_modules/lru-cache": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, "node_modules/lunr": { "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "version": "0.30.8", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -10086,9 +11087,8 @@ }, "node_modules/make-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -10101,14 +11101,13 @@ }, "node_modules/make-error": { "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + "dev": true, + "license": "ISC" }, "node_modules/make-fetch-happen": { "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", "dev": true, + "license": "ISC", "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", @@ -10133,9 +11132,8 @@ }, "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", "dev": true, + "license": "ISC", "dependencies": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" @@ -10146,9 +11144,8 @@ }, "node_modules/make-fetch-happen/node_modules/cacache": { "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", "dev": true, + "license": "ISC", "dependencies": { "@npmcli/fs": "^2.1.0", "@npmcli/move-file": "^2.0.0", @@ -10175,9 +11172,8 @@ }, "node_modules/make-fetch-happen/node_modules/fs-minipass": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -10187,18 +11183,16 @@ }, "node_modules/make-fetch-happen/node_modules/lru-cache": { "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/make-fetch-happen/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10208,9 +11202,8 @@ }, "node_modules/make-fetch-happen/node_modules/ssri": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.1.1" }, @@ -10220,9 +11213,8 @@ }, "node_modules/make-fetch-happen/node_modules/unique-filename": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", "dev": true, + "license": "ISC", "dependencies": { "unique-slug": "^3.0.0" }, @@ -10232,9 +11224,8 @@ }, "node_modules/make-fetch-happen/node_modules/unique-slug": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -10244,36 +11235,21 @@ }, "node_modules/make-fetch-happen/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/makeerror": { "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/marked": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.0.tgz", - "integrity": "sha512-Vkwtq9rLqXryZnWaQc86+FHLC6tr/fycMfYAhiOIXkrNmeGAyhSxjqu0Rs1i0bBqw5u0S7+lV9fdH2ZSVaoa0w==", + "version": "12.0.1", "dev": true, + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -10283,9 +11259,8 @@ }, "node_modules/marked-terminal": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.0.0.tgz", - "integrity": "sha512-sNEx8nn9Ktcm6pL0TnRz8tnXq/mSS0Q1FRSwJOAqw4lAB4l49UeDf85Gm1n9RPFm5qurCPjwi1StAQT2XExhZw==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^6.2.0", "chalk": "^5.3.0", @@ -10303,9 +11278,8 @@ }, "node_modules/marked-terminal/node_modules/ansi-escapes": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", - "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^3.0.0" }, @@ -10316,23 +11290,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/marked-terminal/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/marked-terminal/node_modules/type-fest": { "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=14.16" }, @@ -10342,17 +11303,15 @@ }, "node_modules/media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/meow": { "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", "dev": true, + "license": "MIT", "engines": { "node": ">=16.10" }, @@ -10362,37 +11321,32 @@ }, "node_modules/merge-descriptors": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/micromatch": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -10403,12 +11357,11 @@ }, "node_modules/mime": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.1.tgz", - "integrity": "sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==", "dev": true, "funding": [ "https://github.com/sponsors/broofa" ], + "license": "MIT", "bin": { "mime": "bin/cli.js" }, @@ -10418,16 +11371,14 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -10436,27 +11387,20 @@ } }, "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, - "engines": { - "node": ">=6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "version": "4.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimatch": { "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -10469,39 +11413,23 @@ }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/minipass": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", "engines": { "node": ">=8" } }, "node_modules/minipass-collect": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -10511,9 +11439,8 @@ }, "node_modules/minipass-collect/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10523,15 +11450,13 @@ }, "node_modules/minipass-collect/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/minipass-fetch": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", "dev": true, + "license": "MIT", "dependencies": { "minipass": "^3.1.6", "minipass-sized": "^1.0.3", @@ -10546,9 +11471,8 @@ }, "node_modules/minipass-fetch/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10558,15 +11482,13 @@ }, "node_modules/minipass-fetch/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/minipass-flush": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -10576,9 +11498,8 @@ }, "node_modules/minipass-flush/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10588,15 +11509,13 @@ }, "node_modules/minipass-flush/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/minipass-json-stream": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", "dev": true, + "license": "MIT", "dependencies": { "jsonparse": "^1.3.1", "minipass": "^3.0.0" @@ -10604,9 +11523,8 @@ }, "node_modules/minipass-json-stream/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10616,15 +11534,13 @@ }, "node_modules/minipass-json-stream/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/minipass-pipeline": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -10634,9 +11550,8 @@ }, "node_modules/minipass-pipeline/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10646,15 +11561,13 @@ }, "node_modules/minipass-pipeline/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/minipass-sized": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -10664,9 +11577,8 @@ }, "node_modules/minipass-sized/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10676,15 +11588,13 @@ }, "node_modules/minipass-sized/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/minizlib": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, + "license": "MIT", "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -10695,9 +11605,8 @@ }, "node_modules/minizlib/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10707,15 +11616,13 @@ }, "node_modules/minizlib/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/mkdirp": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -10725,14 +11632,12 @@ }, "node_modules/ms": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "license": "MIT" }, "node_modules/mz": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, + "license": "MIT", "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -10741,35 +11646,30 @@ }, "node_modules/natural-compare": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/neo-async": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/nerf-dart": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", - "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-emoji": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", - "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", @@ -10782,9 +11682,8 @@ }, "node_modules/node-gyp": { "version": "9.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", - "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", "dev": true, + "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", @@ -10807,9 +11706,8 @@ }, "node_modules/node-gyp/node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10817,9 +11715,8 @@ }, "node_modules/node-gyp/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -10837,9 +11734,8 @@ }, "node_modules/node-gyp/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -10849,21 +11745,18 @@ }, "node_modules/node-int64": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/nopt": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", "dev": true, + "license": "ISC", "dependencies": { "abbrev": "^1.0.0" }, @@ -10876,9 +11769,8 @@ }, "node_modules/normalize-package-data": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", - "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^6.0.0", "is-core-module": "^2.8.1", @@ -10891,18 +11783,16 @@ }, "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/normalize-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", - "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "version": "8.0.1", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -10911,9 +11801,7 @@ } }, "node_modules/npm": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.4.0.tgz", - "integrity": "sha512-RS7Mx0OVfXlOcQLRePuDIYdFCVBPCNapWHplDK+mh7GDdP/Tvor4ocuybRRPSvfcRb2vjRJt1fHCqw3cr8qACQ==", + "version": "10.5.0", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -10987,6 +11875,14 @@ "write-file-atomic" ], "dev": true, + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/arborist": "^7.2.1", @@ -10996,7 +11892,7 @@ "@npmcli/package-json": "^5.0.0", "@npmcli/promise-spawn": "^7.0.1", "@npmcli/run-script": "^7.0.4", - "@sigstore/tuf": "^2.3.0", + "@sigstore/tuf": "^2.3.1", "abbrev": "^2.0.0", "archy": "~1.0.0", "cacache": "^18.0.2", @@ -11047,7 +11943,7 @@ "proc-log": "^3.0.0", "qrcode-terminal": "^0.12.0", "read": "^2.1.0", - "semver": "^7.5.4", + "semver": "^7.6.0", "spdx-expression-parse": "^3.0.1", "ssri": "^10.0.5", "supports-color": "^9.4.0", @@ -11069,9 +11965,8 @@ }, "node_modules/npm-bundled": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", - "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", "dev": true, + "license": "ISC", "dependencies": { "npm-normalize-package-bin": "^3.0.0" }, @@ -11081,9 +11976,8 @@ }, "node_modules/npm-install-checks": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, @@ -11093,18 +11987,16 @@ }, "node_modules/npm-normalize-package-bin": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm-package-arg": { "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", "dev": true, + "license": "ISC", "dependencies": { "hosted-git-info": "^6.0.0", "proc-log": "^3.0.0", @@ -11117,9 +12009,8 @@ }, "node_modules/npm-packlist": { "version": "7.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", - "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", "dev": true, + "license": "ISC", "dependencies": { "ignore-walk": "^6.0.0" }, @@ -11129,9 +12020,8 @@ }, "node_modules/npm-pick-manifest": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz", - "integrity": "sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==", "dev": true, + "license": "ISC", "dependencies": { "npm-install-checks": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", @@ -11144,9 +12034,8 @@ }, "node_modules/npm-registry-fetch": { "version": "14.0.5", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", - "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", "dev": true, + "license": "ISC", "dependencies": { "make-fetch-happen": "^11.0.0", "minipass": "^5.0.0", @@ -11162,18 +12051,16 @@ }, "node_modules/npm-registry-fetch/node_modules/lru-cache": { "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", "dev": true, + "license": "ISC", "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^17.0.0", @@ -11197,9 +12084,8 @@ }, "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", "dev": true, + "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", @@ -11214,23 +12100,35 @@ }, "node_modules/npm-registry-fetch/node_modules/minipass-fetch/node_modules/minipass": { "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "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==", + "version": "5.3.0", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "path-key": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm/node_modules/@colors/colors": { @@ -11317,7 +12215,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/agent": { - "version": "2.2.0", + "version": "2.2.1", "dev": true, "inBundle": true, "license": "ISC", @@ -11333,7 +12231,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "7.3.1", + "version": "7.4.0", "dev": true, "inBundle": true, "license": "ISC", @@ -11346,7 +12244,7 @@ "@npmcli/name-from-folder": "^2.0.0", "@npmcli/node-gyp": "^3.0.0", "@npmcli/package-json": "^5.0.0", - "@npmcli/query": "^3.0.1", + "@npmcli/query": "^3.1.0", "@npmcli/run-script": "^7.0.2", "bin-links": "^4.0.1", "cacache": "^18.0.0", @@ -11380,7 +12278,7 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "8.1.0", + "version": "8.2.0", "dev": true, "inBundle": true, "license": "ISC", @@ -11551,7 +12449,7 @@ } }, "node_modules/npm/node_modules/@npmcli/query": { - "version": "3.0.1", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -11589,19 +12487,19 @@ } }, "node_modules/npm/node_modules/@sigstore/bundle": { - "version": "2.1.1", + "version": "2.2.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1" + "@sigstore/protobuf-specs": "^0.3.0" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@sigstore/core": { - "version": "0.2.0", + "version": "1.0.0", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -11610,7 +12508,7 @@ } }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", + "version": "0.3.0", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -11619,14 +12517,14 @@ } }, "node_modules/npm/node_modules/@sigstore/sign": { - "version": "2.2.1", + "version": "2.2.3", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.1", - "@sigstore/core": "^0.2.0", - "@sigstore/protobuf-specs": "^0.2.1", + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0", "make-fetch-happen": "^13.0.0" }, "engines": { @@ -11634,12 +12532,12 @@ } }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "2.3.0", + "version": "2.3.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1", + "@sigstore/protobuf-specs": "^0.3.0", "tuf-js": "^2.2.0" }, "engines": { @@ -11647,14 +12545,14 @@ } }, "node_modules/npm/node_modules/@sigstore/verify": { - "version": "0.1.0", + "version": "1.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.1", - "@sigstore/core": "^0.2.0", - "@sigstore/protobuf-specs": "^0.2.1" + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -12061,7 +12959,7 @@ } }, "node_modules/npm/node_modules/diff": { - "version": "5.1.0", + "version": "5.2.0", "dev": true, "inBundle": true, "license": "BSD-3-Clause", @@ -12212,7 +13110,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/hasown": { - "version": "2.0.0", + "version": "2.0.1", "dev": true, "inBundle": true, "license": "MIT", @@ -12242,7 +13140,7 @@ "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/http-proxy-agent": { - "version": "7.0.0", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "MIT", @@ -12255,7 +13153,7 @@ } }, "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.2", + "version": "7.0.4", "dev": true, "inBundle": true, "license": "MIT", @@ -12337,11 +13235,24 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/ip": { - "version": "2.0.0", + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause" }, "node_modules/npm/node_modules/ip-regex": { "version": "5.0.0", @@ -12418,6 +13329,12 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, "node_modules/npm/node_modules/json-parse-even-better-errors": { "version": "3.0.1", "dev": true, @@ -12471,7 +13388,7 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "6.0.6", + "version": "6.0.7", "dev": true, "inBundle": true, "license": "ISC", @@ -12491,7 +13408,7 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "7.0.7", + "version": "7.0.8", "dev": true, "inBundle": true, "license": "ISC", @@ -12513,7 +13430,7 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "5.0.4", + "version": "5.0.5", "dev": true, "inBundle": true, "license": "ISC", @@ -12551,7 +13468,7 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "6.0.6", + "version": "6.0.7", "dev": true, "inBundle": true, "license": "ISC", @@ -12626,7 +13543,7 @@ } }, "node_modules/npm/node_modules/lru-cache": { - "version": "10.1.0", + "version": "10.2.0", "dev": true, "inBundle": true, "license": "ISC", @@ -13288,7 +14205,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.5.4", + "version": "7.6.0", "dev": true, "inBundle": true, "license": "ISC", @@ -13354,17 +14271,17 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "2.2.0", + "version": "2.2.2", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.1", - "@sigstore/core": "^0.2.0", - "@sigstore/protobuf-specs": "^0.2.1", - "@sigstore/sign": "^2.2.1", - "@sigstore/tuf": "^2.3.0", - "@sigstore/verify": "^0.1.0" + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0", + "@sigstore/sign": "^2.2.3", + "@sigstore/tuf": "^2.3.1", + "@sigstore/verify": "^1.1.0" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -13381,16 +14298,16 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.7.1", + "version": "2.8.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 16.0.0", "npm": ">= 3.0.0" } }, @@ -13419,7 +14336,7 @@ } }, "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.3.0", + "version": "2.5.0", "dev": true, "inBundle": true, "license": "CC-BY-3.0" @@ -13435,7 +14352,7 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.16", + "version": "3.0.17", "dev": true, "inBundle": true, "license": "CC0-1.0" @@ -13824,9 +14741,8 @@ }, "node_modules/npmlog": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", "dev": true, + "license": "ISC", "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", @@ -13839,17 +14755,15 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/object-inspect": { "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13864,8 +14778,7 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -13873,24 +14786,32 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "6.0.0", "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-fn": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -13898,9 +14819,8 @@ }, "node_modules/open": { "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, + "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -13915,9 +14835,8 @@ }, "node_modules/optionator": { "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, + "license": "MIT", "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", @@ -13932,15 +14851,13 @@ }, "node_modules/ospath": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", - "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/p-each-series": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", - "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -13950,9 +14867,8 @@ }, "node_modules/p-filter": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", - "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==", "dev": true, + "license": "MIT", "dependencies": { "p-map": "^7.0.1" }, @@ -13965,9 +14881,8 @@ }, "node_modules/p-filter/node_modules/p-map": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.1.tgz", - "integrity": "sha512-2wnaR0XL/FDOj+TgpDuRb2KTjLnu3Fma6b1ZUwGY7LcqenMcvP/YFpjpbPKY6WVGsbuJZRuoUz8iPrt8ORnAFw==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -13977,18 +14892,16 @@ }, "node_modules/p-is-promise": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", - "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/p-limit": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -14000,15 +14913,39 @@ } }, "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "version": "6.0.0", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/yocto-queue": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -14016,9 +14953,8 @@ }, "node_modules/p-map": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, + "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" }, @@ -14031,9 +14967,8 @@ }, "node_modules/p-reduce": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", - "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -14043,18 +14978,16 @@ }, "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/pacote": { "version": "15.2.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz", - "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==", "dev": true, + "license": "ISC", "dependencies": { "@npmcli/git": "^4.0.0", "@npmcli/installed-package-contents": "^2.0.1", @@ -14084,9 +15017,8 @@ }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -14096,9 +15028,8 @@ }, "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", @@ -14114,69 +15045,69 @@ }, "node_modules/parse5": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", "dev": true, + "license": "MIT", "dependencies": { "parse5": "^6.0.1" } }, "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "version": "5.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, "node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/path-scurry": { "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^9.1.1 || ^10.0.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -14190,49 +15121,43 @@ }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "license": "ISC", "engines": { "node": "14 || >=16.14" } }, "node_modules/path-to-regexp": { "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/pend": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/performance-now": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/picocolors": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -14242,22 +15167,21 @@ }, "node_modules/pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/pino": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.19.0.tgz", - "integrity": "sha512-oswmokxkav9bADfJ2ifrvfHUwad6MLp73Uat0IkQWY3iAw5xTRoznXbXksZs8oaOUMpmhVWD+PZogNzllWpJaA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.20.0.tgz", + "integrity": "sha512-uhIfMj5TVp+WynVASaVEJFTncTUe4dHBq6CWplu/vBgvGHhvBvQfxz+vcOrnnBQdORH3izaGEurLfNlq3YxdFQ==", "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "v1.1.0", + "pino-abstract-transport": "^1.1.0", "pino-std-serializers": "^6.0.0", "process-warning": "^3.0.0", "quick-format-unescaped": "^4.0.3", @@ -14271,18 +15195,33 @@ } }, "node_modules/pino-abstract-transport": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", - "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", "dependencies": { "readable-stream": "^4.0.0", "split2": "^4.0.0" } }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/pino-pretty": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.1.tgz", - "integrity": "sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.0.0.tgz", + "integrity": "sha512-YFJZqw59mHIY72wBnBs7XhLGG6qpJMa4pEQTRgEPEbjIYbng2LXEZZF1DoyDg9CfejEy8uZCyzpcBXXG0oOCwQ==", "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", @@ -14303,6 +15242,21 @@ "pino-pretty": "bin.js" } }, + "node_modules/pino-pretty/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/pino-std-serializers": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", @@ -14310,18 +15264,16 @@ }, "node_modules/pirates": { "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/pkg-conf": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", - "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", "dev": true, + "license": "MIT", "dependencies": { "find-up": "^2.0.0", "load-json-file": "^4.0.0" @@ -14332,9 +15284,8 @@ }, "node_modules/pkg-conf/node_modules/find-up": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^2.0.0" }, @@ -14344,9 +15295,8 @@ }, "node_modules/pkg-conf/node_modules/locate-path": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -14357,9 +15307,8 @@ }, "node_modules/pkg-conf/node_modules/p-limit": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^1.0.0" }, @@ -14369,9 +15318,8 @@ }, "node_modules/pkg-conf/node_modules/p-locate": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^1.1.0" }, @@ -14381,27 +15329,24 @@ }, "node_modules/pkg-conf/node_modules/p-try": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/pkg-conf/node_modules/path-exists": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "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" }, @@ -14411,9 +15356,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==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -14424,9 +15368,8 @@ }, "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" }, @@ -14436,9 +15379,8 @@ }, "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==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -14451,9 +15393,8 @@ }, "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==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -14461,20 +15402,26 @@ "node": ">=8" } }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -14487,9 +15434,8 @@ }, "node_modules/pretty-bytes": { "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -14499,9 +15445,8 @@ }, "node_modules/pretty-format": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -14511,40 +15456,25 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/proc-log": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/process": { "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", "engines": { "node": ">= 0.6.0" } }, "node_modules/process-nextick-args": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/process-warning": { "version": "3.0.0", @@ -14553,15 +15483,13 @@ }, "node_modules/promise-inflight": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/promise-retry": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, + "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -14570,11 +15498,18 @@ "node": ">=10" } }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/prompts": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, + "license": "MIT", "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -14585,14 +15520,12 @@ }, "node_modules/proto-list": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -14603,19 +15536,16 @@ }, "node_modules/proxy-from-env": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "license": "MIT" }, "node_modules/psl": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pump": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -14623,17 +15553,14 @@ }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/pure-rand": { "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", "dev": true, "funding": [ { @@ -14644,13 +15571,13 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ] + ], + "license": "MIT" }, "node_modules/qs": { "version": "6.10.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", - "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.4" }, @@ -14663,14 +15590,11 @@ }, "node_modules/querystringify": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -14685,43 +15609,32 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, - "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/randombytes": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } }, "node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -14734,9 +15647,8 @@ }, "node_modules/rc": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -14749,30 +15661,26 @@ }, "node_modules/rc/node_modules/ini": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-is": { "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/read-package-json": { "version": "6.0.4", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", - "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", "dev": true, + "license": "ISC", "dependencies": { "glob": "^10.2.2", "json-parse-even-better-errors": "^3.0.0", @@ -14785,9 +15693,8 @@ }, "node_modules/read-package-json-fast": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", "dev": true, + "license": "ISC", "dependencies": { "json-parse-even-better-errors": "^3.0.0", "npm-normalize-package-bin": "^3.0.0" @@ -14798,18 +15705,16 @@ }, "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", - "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", "dev": true, + "license": "MIT", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/read-package-json/node_modules/glob": { "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.3.5", @@ -14829,18 +15734,16 @@ }, "node_modules/read-package-json/node_modules/json-parse-even-better-errors": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", - "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", "dev": true, + "license": "MIT", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/read-pkg": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", - "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", "dev": true, + "license": "MIT", "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", @@ -14857,10 +15760,8 @@ }, "node_modules/read-pkg-up": { "version": "11.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-11.0.0.tgz", - "integrity": "sha512-LOVbvF1Q0SZdjClSefZ0Nz5z8u+tIE7mV5NibzmE9VYmDe9CaBbAVtz1veOSZbofrdsilxuDAYnFenukZVp8/Q==", - "deprecated": "Renamed to read-package-up", "dev": true, + "license": "MIT", "dependencies": { "find-up-simple": "^1.0.0", "read-pkg": "^9.0.0", @@ -14874,10 +15775,9 @@ } }, "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.3.tgz", - "integrity": "sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==", + "version": "4.14.0", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" }, @@ -14887,9 +15787,8 @@ }, "node_modules/read-pkg/node_modules/hosted-git-info": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", - "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", "dev": true, + "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" }, @@ -14899,18 +15798,16 @@ }, "node_modules/read-pkg/node_modules/lru-cache": { "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, + "license": "ISC", "engines": { "node": "14 || >=16.14" } }, "node_modules/read-pkg/node_modules/normalize-package-data": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", - "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^7.0.0", "is-core-module": "^2.8.1", @@ -14923,9 +15820,8 @@ }, "node_modules/read-pkg/node_modules/parse-json": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", - "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.22.13", "index-to-position": "^0.1.2", @@ -14939,10 +15835,9 @@ } }, "node_modules/read-pkg/node_modules/type-fest": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.3.tgz", - "integrity": "sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==", + "version": "4.12.0", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" }, @@ -14951,18 +15846,16 @@ } }, "node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "version": "3.6.2", + "dev": true, + "license": "MIT", "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 6" } }, "node_modules/real-require": { @@ -14973,30 +15866,15 @@ "node": ">= 12.13.0" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/regenerator-runtime": { "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/registry-auth-token": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", - "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", "dev": true, + "license": "MIT", "dependencies": { "@pnpm/npm-conf": "^2.1.0" }, @@ -15004,44 +15882,52 @@ "node": ">=14" } }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/request-progress": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", "dev": true, + "license": "MIT", "dependencies": { "throttleit": "^1.0.0" } }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/require-from-string": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/requires-port": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/resolve": { "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, + "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -15056,9 +15942,8 @@ }, "node_modules/resolve-cwd": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, + "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" }, @@ -15068,57 +15953,32 @@ }, "node_modules/resolve-from": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/resolve-global": { + "node_modules/resolve-pkg-maps": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", - "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", - "dev": true, - "dependencies": { - "global-dirs": "^0.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-global/node_modules/global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", - "dev": true, - "dependencies": { - "ini": "^1.3.4" - }, - "engines": { - "node": ">=4" + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/resolve-global/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, "node_modules/resolve.exports": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/restore-cursor": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, + "license": "MIT", "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -15127,20 +15987,44 @@ "node": ">=8" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", "dev": true, + "license": "ISC" + }, + "node_modules/retry": { + "version": "0.13.1", + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/reusify": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -15148,15 +16032,13 @@ }, "node_modules/rfdc": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/rimraf": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -15169,9 +16051,8 @@ }, "node_modules/rimraf/node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -15179,9 +16060,8 @@ }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -15199,9 +16079,8 @@ }, "node_modules/rimraf/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -15210,10 +16089,9 @@ } }, "node_modules/rollup": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", - "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", + "version": "4.13.2", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "1.0.5" }, @@ -15225,27 +16103,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.12.0", - "@rollup/rollup-android-arm64": "4.12.0", - "@rollup/rollup-darwin-arm64": "4.12.0", - "@rollup/rollup-darwin-x64": "4.12.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", - "@rollup/rollup-linux-arm64-gnu": "4.12.0", - "@rollup/rollup-linux-arm64-musl": "4.12.0", - "@rollup/rollup-linux-riscv64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-musl": "4.12.0", - "@rollup/rollup-win32-arm64-msvc": "4.12.0", - "@rollup/rollup-win32-ia32-msvc": "4.12.0", - "@rollup/rollup-win32-x64-msvc": "4.12.0", + "@rollup/rollup-android-arm-eabi": "4.13.2", + "@rollup/rollup-android-arm64": "4.13.2", + "@rollup/rollup-darwin-arm64": "4.13.2", + "@rollup/rollup-darwin-x64": "4.13.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.2", + "@rollup/rollup-linux-arm64-gnu": "4.13.2", + "@rollup/rollup-linux-arm64-musl": "4.13.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2", + "@rollup/rollup-linux-riscv64-gnu": "4.13.2", + "@rollup/rollup-linux-s390x-gnu": "4.13.2", + "@rollup/rollup-linux-x64-gnu": "4.13.2", + "@rollup/rollup-linux-x64-musl": "4.13.2", + "@rollup/rollup-win32-arm64-msvc": "4.13.2", + "@rollup/rollup-win32-ia32-msvc": "4.13.2", + "@rollup/rollup-win32-x64-msvc": "4.13.2", "fsevents": "~2.3.2" } }, "node_modules/rollup-plugin-filesize": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-filesize/-/rollup-plugin-filesize-10.0.0.tgz", - "integrity": "sha512-JAYYhzCcmGjmCzo3LEHSDE3RAPHKIeBdpqRhiyZSv5o/3wFhktUOzYAWg/uUKyEu5dEaVaql6UOmaqHx1qKrZA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/runtime": "^7.13.8", "boxen": "^5.0.0", @@ -15262,15 +16141,13 @@ }, "node_modules/rollup-plugin-ignore": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/rollup-plugin-ignore/-/rollup-plugin-ignore-1.0.10.tgz", - "integrity": "sha512-VsbnfwwaTv2Dxl2onubetX/3RnSnplNnjdix0hvF8y2YpqdzlZrjIq6zkcuVJ08XysS8zqW3gt3ORBndFDgsrg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/rollup-plugin-polyfill-node": { "version": "0.13.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-polyfill-node/-/rollup-plugin-polyfill-node-0.13.0.tgz", - "integrity": "sha512-FYEvpCaD5jGtyBuBFcQImEGmTxDTPbiHjJdrYIp+mFIwgXiXabxvKUK7ZT9P31ozu2Tqm9llYQMRWsfvTMTAOw==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/plugin-inject": "^5.0.4" }, @@ -15280,9 +16157,8 @@ }, "node_modules/rollup-plugin-visualizer": { "version": "5.12.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz", - "integrity": "sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==", "dev": true, + "license": "MIT", "dependencies": { "open": "^8.4.0", "picomatch": "^2.3.1", @@ -15304,19 +16180,55 @@ } } }, - "node_modules/rollup-plugin-visualizer/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "node_modules/rollup-plugin-visualizer/node_modules/source-map": { + "version": "0.7.4", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/rollup/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz", + "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz", + "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz", + "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==", + "cpu": [ + "x64" + ], "dev": true, - "engines": { - "node": ">= 8" - } + "optional": true, + "os": [ + "win32" + ] }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -15332,23 +16244,20 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, "node_modules/rxjs": { "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -15362,7 +16271,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safe-stable-stringify": { "version": "2.4.3", @@ -15374,8 +16284,7 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "license": "MIT" }, "node_modules/schema-utils": { "version": "3.3.0", @@ -15436,16 +16345,15 @@ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" }, "node_modules/semantic-release": { - "version": "23.0.2", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-23.0.2.tgz", - "integrity": "sha512-OnVYJ6Xgzwe1x8MKswba7RU9+5djS1MWRTrTn5qsq3xZYpslroZkV9Pt0dA2YcIuieeuSZWJhn+yUWoBUHO5Fw==", + "version": "23.0.6", "dev": true, + "license": "MIT", "dependencies": { - "@semantic-release/commit-analyzer": "^11.0.0", + "@semantic-release/commit-analyzer": "^12.0.0", "@semantic-release/error": "^4.0.0", - "@semantic-release/github": "^9.0.0", - "@semantic-release/npm": "^11.0.0", - "@semantic-release/release-notes-generator": "^12.0.0", + "@semantic-release/github": "^10.0.0", + "@semantic-release/npm": "^12.0.0", + "@semantic-release/release-notes-generator": "^13.0.0", "aggregate-error": "^5.0.0", "cosmiconfig": "^9.0.0", "debug": "^4.0.0", @@ -15480,9 +16388,8 @@ }, "node_modules/semantic-release/node_modules/aggregate-error": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, + "license": "MIT", "dependencies": { "clean-stack": "^5.2.0", "indent-string": "^5.0.0" @@ -15496,9 +16403,8 @@ }, "node_modules/semantic-release/node_modules/clean-stack": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", - "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "5.0.0" }, @@ -15509,87 +16415,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "node_modules/semantic-release/node_modules/escape-string-regexp": { + "version": "5.0.0", "dev": true, - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/d-fischer" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/figures": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" }, - "peerDependencies": { - "typescript": ">=4.9.5" + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "node_modules/semantic-release/node_modules/get-stream": { + "version": "6.0.1", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/semantic-release/node_modules/hosted-git-info": { + "version": "7.0.1", "dev": true, + "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "lru-cache": "^10.0.1" }, "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "node_modules/semantic-release/node_modules/indent-string": { + "version": "5.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/figures": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.0.1.tgz", - "integrity": "sha512-0oY/olScYD4IhQ8u//gCPA4F3mlTn2dacYmiDm/mbDQvpmLjV4uH+zhsQ5IyXRyvqkvtUkXkNdGvg5OFJTCsuQ==", + "node_modules/semantic-release/node_modules/is-unicode-supported": { + "version": "2.0.0", "dev": true, - "dependencies": { - "is-unicode-supported": "^2.0.0" - }, + "license": "MIT", "engines": { "node": ">=18" }, @@ -15597,32 +16484,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/hosted-git-info": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", - "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", + "node_modules/semantic-release/node_modules/lru-cache": { + "version": "10.2.0", "dev": true, - "dependencies": { - "lru-cache": "^10.0.1" - }, + "license": "ISC", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "14 || >=16.14" } }, - "node_modules/semantic-release/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=16.17.0" + "node": ">=10" } }, - "node_modules/semantic-release/node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "node_modules/semver-diff": { + "version": "4.0.0", "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, "engines": { "node": ">=12" }, @@ -15630,138 +16517,317 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/semver-regex": { + "version": "4.0.5", "dev": true, + "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/is-unicode-supported": { + "node_modules/send": { + "version": "0.18.0", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", - "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", + "license": "MIT" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.3.tgz", + "integrity": "sha512-VqUFMC7K3LDGeGnJM9h56D3XGKb6KGgOw0cVNtA26yYXHCcpxf3xwCTUaQoWlVS7i8Jdh3GjQkOB23qsXyjoyQ==", "dev": true, + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.12.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.7.4", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.5", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, "engines": { - "node": ">=18" + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", + "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/serve-handler/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-handler/node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/serve-handler/node_modules/path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-handler/node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/semantic-release/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", - "dev": true, + "node_modules/serve-static": { + "version": "1.15.0", + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, "engines": { - "node": "14 || >=16.14" + "node": ">= 0.8.0" } }, - "node_modules/semantic-release/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/serve/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/semantic-release/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "node_modules/serve/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/semantic-release/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/serve/node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^4.0.0" + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" }, "engines": { - "node": ">=12" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "node_modules/serve/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/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==", + "node_modules/serve/node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", "dev": true, + "license": "MIT", "engines": { - "node": ">=14" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/semantic-release/node_modules/strip-final-newline": { + "node_modules/serve/node_modules/cli-boxes": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } + "node_modules/serve/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/semver-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "node_modules/serve/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "license": "MIT", "dependencies": { - "semver": "^7.3.5" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { "node": ">=12" @@ -15770,126 +16836,84 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semver-regex": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", - "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", + "node_modules/serve/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/serve/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "node": ">=12.20" }, - "engines": { - "node": ">= 0.8.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/serve/node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" + "string-width": "^5.0.1" }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "node_modules/serve/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, + "license": "MIT", "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/set-function-length": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", - "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "version": "1.2.2", + "license": "MIT", "dependencies": { - "define-data-property": "^1.1.2", + "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -15897,13 +16921,12 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -15913,17 +16936,16 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/shiki": { "version": "0.14.7", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", - "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-sequence-parser": "^1.1.0", "jsonc-parser": "^3.2.0", @@ -15932,11 +16954,10 @@ } }, "node_modules/side-channel": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", - "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", + "version": "1.0.6", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.7", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", "object-inspect": "^1.13.1" @@ -15949,16 +16970,20 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/signale": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", - "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^2.3.2", "figures": "^2.0.0", @@ -15970,9 +16995,8 @@ }, "node_modules/signale/node_modules/ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -15982,9 +17006,8 @@ }, "node_modules/signale/node_modules/chalk": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -15994,35 +17017,18 @@ "node": ">=4" } }, - "node_modules/signale/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/signale/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, "node_modules/signale/node_modules/escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/signale/node_modules/figures": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -16032,18 +17038,16 @@ }, "node_modules/signale/node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/signale/node_modules/supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -16053,9 +17057,8 @@ }, "node_modules/sigstore": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.9.0.tgz", - "integrity": "sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^1.1.0", "@sigstore/protobuf-specs": "^0.2.0", @@ -16072,18 +17075,16 @@ }, "node_modules/sigstore/node_modules/lru-cache": { "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/sigstore/node_modules/make-fetch-happen": { "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", "dev": true, + "license": "ISC", "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^17.0.0", @@ -16107,9 +17108,8 @@ }, "node_modules/sigstore/node_modules/minipass-fetch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", "dev": true, + "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", @@ -16124,24 +17124,21 @@ }, "node_modules/sigstore/node_modules/minipass-fetch/node_modules/minipass": { "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/sisteransi": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/skin-tone": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", - "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", "dev": true, + "license": "MIT", "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" }, @@ -16151,35 +17148,62 @@ }, "node_modules/slash": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=8" + "node": ">=7.0.0" } }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, "node_modules/smart-buffer": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -16187,15 +17211,13 @@ }, "node_modules/smob": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz", - "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/socks": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", - "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", "dev": true, + "license": "MIT", "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -16207,9 +17229,8 @@ }, "node_modules/socks-proxy-agent": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", @@ -16220,27 +17241,25 @@ } }, "node_modules/sonic-boom": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.0.tgz", - "integrity": "sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", "dependencies": { "atomic-sleep": "^1.0.0" } }, "node_modules/source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-support": { "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -16248,15 +17267,13 @@ }, "node_modules/spawn-error-forwarder": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", - "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/spdx-correct": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -16264,15 +17281,13 @@ }, "node_modules/spdx-exceptions": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true + "dev": true, + "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, + "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -16280,29 +17295,25 @@ }, "node_modules/spdx-license-ids": { "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", - "dev": true + "dev": true, + "license": "CC0-1.0" }, "node_modules/split2": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", "engines": { "node": ">= 10.x" } }, "node_modules/sprintf-js": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/sshpk": { "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, + "license": "MIT", "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -16325,15 +17336,13 @@ }, "node_modules/sshpk/node_modules/jsbn": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ssri": { "version": "10.0.5", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz", - "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -16343,18 +17352,16 @@ }, "node_modules/ssri/node_modules/minipass": { "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/stack-utils": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -16364,50 +17371,23 @@ }, "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==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/statuses": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dev": true, - "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "node_modules/stream-browserify/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, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/stream-combiner2": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", "dev": true, + "license": "MIT", "dependencies": { "duplexer2": "~0.1.0", "readable-stream": "^2.0.2" @@ -16415,9 +17395,8 @@ }, "node_modules/stream-combiner2/node_modules/readable-stream": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -16430,32 +17409,28 @@ }, "node_modules/stream-combiner2/node_modules/safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/stream-combiner2/node_modules/string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, "node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, "node_modules/string-length": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, + "license": "MIT", "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -16466,8 +17441,8 @@ }, "node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -16480,8 +17455,8 @@ "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -16493,8 +17468,8 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -16505,8 +17480,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" }, @@ -16516,38 +17491,26 @@ }, "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, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-indent": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "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==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -16557,9 +17520,8 @@ }, "node_modules/superagent": { "version": "8.1.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", - "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", "dev": true, + "license": "MIT", "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.4", @@ -16578,9 +17540,8 @@ }, "node_modules/superagent/node_modules/mime": { "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -16589,12 +17550,11 @@ } }, "node_modules/superagent/node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.12.0", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -16605,9 +17565,8 @@ }, "node_modules/supertest": { "version": "6.3.4", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz", - "integrity": "sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==", "dev": true, + "license": "MIT", "dependencies": { "methods": "^1.1.2", "superagent": "^8.1.2" @@ -16617,22 +17576,23 @@ } }, "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==", + "version": "8.1.1", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/supports-hyperlinks": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", - "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -16641,11 +17601,21 @@ "node": ">=14.18" } }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -16655,18 +17625,16 @@ }, "node_modules/tapable": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/tar": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", "dev": true, + "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -16681,9 +17649,8 @@ }, "node_modules/tar/node_modules/fs-minipass": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -16693,9 +17660,8 @@ }, "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -16705,24 +17671,21 @@ }, "node_modules/tar/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/temp-dir": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" } }, "node_modules/tempy": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", - "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", "dev": true, + "license": "MIT", "dependencies": { "is-stream": "^3.0.0", "temp-dir": "^3.0.0", @@ -16736,23 +17699,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tempy/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tempy/node_modules/type-fest": { "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" }, @@ -16761,10 +17711,9 @@ } }, "node_modules/terser": { - "version": "5.28.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.28.1.tgz", - "integrity": "sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==", + "version": "5.29.1", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -16828,33 +17777,15 @@ "node": ">= 10.13.0" } }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/terser/node_modules/source-map-support": { "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -16862,9 +17793,8 @@ }, "node_modules/test-exclude": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, + "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -16876,9 +17806,8 @@ }, "node_modules/test-exclude/node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -16886,9 +17815,8 @@ }, "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==", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -16906,9 +17834,8 @@ }, "node_modules/test-exclude/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -16918,9 +17845,8 @@ }, "node_modules/text-extensions": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", - "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -16930,24 +17856,21 @@ }, "node_modules/text-table": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/thenify": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, + "license": "MIT", "dependencies": { "any-promise": "^1.0.0" } }, "node_modules/thenify-all": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "dev": true, + "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -16956,120 +17879,86 @@ } }, "node_modules/thread-stream": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", - "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.6.0.tgz", + "integrity": "sha512-t4eNiKdGwd1EV6tx76mRbrOqwvkxz+ssOiQXEXw88m4p/Xp6679vg16sf39BAstRjHOiWIqp5+J2ylHk3pU30g==", "dependencies": { "real-require": "^0.2.0" } }, "node_modules/throttleit": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", - "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "version": "2.0.5", "dev": true, + "license": "MIT", "dependencies": { - "readable-stream": "3" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, "node_modules/through2/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==", + "version": "2.3.8", "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/tmp": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.2.tgz", - "integrity": "sha512-ETcvHhaIc9J2MDEAH6N67j9bvBvu/3Gb764qaGhwtFvjtvhegqoqSpofgeyq1Sc24mW5pdyUDs9HP5j3ehkxRw==", - "dependencies": { - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">=14" - } + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" }, - "node_modules/tmp/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "safe-buffer": "~5.1.0" } }, - "node_modules/tmp/node_modules/rimraf": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", - "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, + "node_modules/tmp": { + "version": "0.2.3", + "license": "MIT", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=14.14" } }, "node_modules/tmpl": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/to-fast-properties": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -17079,17 +17968,15 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } }, "node_modules/tough-cookie": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -17102,18 +17989,16 @@ }, "node_modules/tough-cookie/node_modules/universalify": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4.0.0" } }, "node_modules/traverse": { "version": "0.6.8", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", - "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -17121,20 +18006,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/ts-api-utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", - "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", + "version": "1.3.0", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, @@ -17144,9 +18019,8 @@ }, "node_modules/ts-jest": { "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", "dev": true, + "license": "MIT", "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", @@ -17187,9 +18061,8 @@ }, "node_modules/ts-loader": { "version": "9.5.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", - "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", @@ -17205,62 +18078,74 @@ "webpack": "^5.0.0" } }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "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/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "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/ts-loader/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ts-loader/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, "node_modules/ts-loader/node_modules/source-map": { "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">= 8" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } + "engines": { + "node": ">=8" } }, "node_modules/tsconfig-paths": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, + "license": "MIT", "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", @@ -17272,24 +18157,20 @@ }, "node_modules/tsconfig-paths/node_modules/strip-bom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/tslib": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "license": "0BSD" }, "node_modules/tslint-config-prettier": { "version": "1.18.0", - "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz", - "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==", "dev": true, + "license": "MIT", "bin": { "tslint-config-prettier-check": "bin/check.js" }, @@ -17297,11 +18178,28 @@ "node": ">=4.0.0" } }, + "node_modules/tsx": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.16.0.tgz", + "integrity": "sha512-MPgN+CuY+4iKxGoJNPv+1pyo5YWZAQ5XfsyobUG+zoKG7IkvCPLZDEyoIb8yLS2FcWci1nlxAqmvPlFWD5AFiQ==", + "dependencies": { + "esbuild": "~0.21.5", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tuf-js": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", - "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==", "dev": true, + "license": "MIT", "dependencies": { "@tufjs/models": "1.0.4", "debug": "^4.3.4", @@ -17313,18 +18211,16 @@ }, "node_modules/tuf-js/node_modules/lru-cache": { "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/tuf-js/node_modules/make-fetch-happen": { "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", "dev": true, + "license": "ISC", "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^17.0.0", @@ -17348,9 +18244,8 @@ }, "node_modules/tuf-js/node_modules/minipass-fetch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", "dev": true, + "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", @@ -17365,18 +18260,16 @@ }, "node_modules/tuf-js/node_modules/minipass-fetch/node_modules/minipass": { "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/tunnel-agent": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -17386,15 +18279,13 @@ }, "node_modules/tweetnacl": { "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true + "dev": true, + "license": "Unlicense" }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -17404,18 +18295,16 @@ }, "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.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -17425,8 +18314,7 @@ }, "node_modules/type-is": { "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -17436,10 +18324,9 @@ } }, "node_modules/typedoc": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.9.tgz", - "integrity": "sha512-jVoGmfNw848iW0L313+jqHbsknepwDV6F9nzk1H30oWhKXkw65uaENgR6QtTw9a5KqRWEb6nwNd54KxffBJyWw==", + "version": "0.25.12", "dev": true, + "license": "Apache-2.0", "dependencies": { "lunr": "^2.3.9", "marked": "^4.3.0", @@ -17453,14 +18340,13 @@ "node": ">= 16" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" } }, "node_modules/typedoc-plugin-markdown": { "version": "3.17.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz", - "integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==", "dev": true, + "license": "MIT", "dependencies": { "handlebars": "^4.7.7" }, @@ -17470,18 +18356,16 @@ }, "node_modules/typedoc-plugin-merge-modules": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/typedoc-plugin-merge-modules/-/typedoc-plugin-merge-modules-5.1.0.tgz", - "integrity": "sha512-jXH27L/wlxFjErgBXleh3opVgjVTXFEuBo68Yfl18S9Oh/IqxK6NV94jlEJ9hl4TXc9Zm2l7Rfk41CEkcCyvFQ==", "dev": true, + "license": "ISC", "peerDependencies": { "typedoc": "0.24.x || 0.25.x" } }, "node_modules/typedoc-theme-hierarchy": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/typedoc-theme-hierarchy/-/typedoc-theme-hierarchy-4.1.2.tgz", - "integrity": "sha512-X3H+zaDkg7wLNoaPJoqXs3rnMfZ9BZjmlXRwplWDciaPfn2hojHxJJ+yVKdqqmojgiHJgg7MYWGDVpOfNlOJ5A==", "dev": true, + "license": "MIT", "dependencies": { "fs-extra": "11.1.1" }, @@ -17491,9 +18375,8 @@ }, "node_modules/typedoc-theme-hierarchy/node_modules/fs-extra": { "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -17505,9 +18388,8 @@ }, "node_modules/typedoc/node_modules/marked": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -17516,9 +18398,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.3", + "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17529,9 +18411,8 @@ }, "node_modules/uglify-js": { "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "dev": true, + "license": "BSD-2-Clause", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -17542,23 +18423,21 @@ }, "node_modules/undici-types": { "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "dev": true, + "license": "MIT" }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/unicorn-magic": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -17568,9 +18447,8 @@ }, "node_modules/unique-filename": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", "dev": true, + "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" }, @@ -17580,9 +18458,8 @@ }, "node_modules/unique-slug": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -17592,9 +18469,8 @@ }, "node_modules/unique-string": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", "dev": true, + "license": "MIT", "dependencies": { "crypto-random-string": "^4.0.0" }, @@ -17606,41 +18482,35 @@ } }, "node_modules/universal-user-agent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", - "dev": true + "version": "7.0.2", + "dev": true, + "license": "ISC" }, "node_modules/universalify": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/untildify": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/update-browserslist-db": { "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { @@ -17656,6 +18526,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -17667,29 +18538,48 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, + "node_modules/update-check/node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, "node_modules/url-join": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", - "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, "node_modules/url-parse": { "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, + "license": "MIT", "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -17697,22 +18587,20 @@ }, "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 + "dev": true, + "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -17721,16 +18609,10 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" - }, "node_modules/v8-to-istanbul": { "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, + "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -17742,9 +18624,8 @@ }, "node_modules/validate-npm-package-license": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, + "license": "Apache-2.0", "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -17752,9 +18633,8 @@ }, "node_modules/validate-npm-package-name": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", - "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", "dev": true, + "license": "ISC", "dependencies": { "builtins": "^5.0.0" }, @@ -17764,20 +18644,18 @@ }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/verror": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "dev": true, "engines": [ "node >=0.6.0" ], + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -17786,29 +18664,26 @@ }, "node_modules/vscode-oniguruma": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/vscode-textmate": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/walker": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, "peer": true, "dependencies": { @@ -17820,27 +18695,27 @@ } }, "node_modules/webpack": { - "version": "5.90.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", - "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", + "version": "5.91.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", + "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", "dev": true, "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.16.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", @@ -17848,7 +18723,7 @@ "schema-utils": "^3.2.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.0", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -17903,8 +18778,8 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -17917,18 +18792,16 @@ }, "node_modules/wide-align": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "node_modules/widest-line": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "dev": true, + "license": "MIT", "dependencies": { "string-width": "^4.0.0" }, @@ -17938,15 +18811,13 @@ }, "node_modules/wordwrap": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -17962,8 +18833,8 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -17976,16 +18847,74 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "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/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "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/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, "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": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -17994,10 +18923,15 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "engines": { "node": ">=10.0.0" }, @@ -18016,55 +18950,42 @@ }, "node_modules/xml": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/xtend": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4" } }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/ya-ts-client": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/ya-ts-client/-/ya-ts-client-0.5.3.tgz", - "integrity": "sha512-jwJmgmrb39NW/IKPjoH+ISKMCDna2Q9ylfmqQwVToKiOIrsnzt37ExxW6WxWrjJ5IZ8AkYfXQMOa2WRDvyZrJA==", - "dependencies": { - "axios": "^0.21.4" - } - }, - "node_modules/ya-ts-client/node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ya-ts-client/-/ya-ts-client-1.1.3.tgz", + "integrity": "sha512-30l0EAz8E1G0JbhNj8aNQF1/g9SVXYUebdVJ248jQEA8oOwxotcw2STvAZZPX84KhbKRe1W5H+rY5cnwTLzCLQ==", + "engines": { + "node": ">=18.0.0" } }, "node_modules/yallist": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -18080,36 +19001,25 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/yauzl": { "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index ebe280b16..1f3c42dc0 100644 --- a/package.json +++ b/package.json @@ -13,39 +13,44 @@ "computer", "marketplace" ], - "main": "dist/golem-js.cjs.js", + "main": "dist/golem-js.js", + "type": "commonjs", "exports": { ".": { "types": "./dist/index.d.ts", - "default": "./dist/golem-js.cjs.js" + "import": "./dist/golem-js.mjs", + "require": "./dist/golem-js.js" }, "./experimental": { "types": "./dist/experimental.d.ts", - "default": "./dist/golem-js-experimental.cjs.js" + "import": "./dist/golem-js-experimental.mjs", + "require": "./dist/golem-js-experimental.js" } }, "browser": "dist/golem-js.min.js", - "module": "dist/golem-js.es.js", + "module": "dist/golem-js.mjs", "types": "dist/index.d.ts", "jsdelivr": "dist/golem-js.min.js", "unpkg": "dist/golem-js.min.js", "scripts": { "build": "rollup -c --forceExit", "dev": "rollup -c -w", - "docs": "typedoc src/ --plugin typedoc-plugin-merge-modules --plugin typedoc-theme-hierarchy", - "docs:md": "typedoc src/ --plugin typedoc-plugin-markdown --plugin .docs/typedoc-frontmatter-theme.cjs --hideBreadcrumbs true && node .docs/summary-generator.cjs", + "docs": "typedoc src/ --plugin typedoc-plugin-merge-modules --plugin typedoc-theme-hierarchy --out api-reference", + "docs:md": "typedoc src/ --plugin typedoc-plugin-markdown --plugin .typedoc/typedoc-frontmatter-theme.cjs --hideBreadcrumbs true --out api-reference && node .typedoc/summary-generator.cjs", "test": "npm run test:unit && npm run test:e2e", - "test:unit": "jest --config tests/unit/jest.config.json", - "test:e2e": "jest --config tests/e2e/jest.config.json tests/e2e/**.spec.ts --runInBand --forceExit", + "test:unit": "jest --config tests/jest.config.json", + "test:e2e": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --config tests/e2e/jest.config.json tests/e2e/**.spec.ts --runInBand --forceExit", "test:cypress": "cypress run", - "test:examples": "ts-node --project tsconfig.spec.json tests/examples/examples.test.ts", + "test:examples": "tsx tests/examples/examples.test.ts", + "test:import": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config tests/import/jest.config.js", "lint": "npm run lint:ts && npm run lint:ts:tests && npm run lint:eslint", "lint:ts": "tsc --project tsconfig.json --noEmit", "lint:ts:tests": "tsc --project tests/tsconfig.json --noEmit", "lint:eslint": "eslint .", "format": "prettier -w .", "format:check": "prettier -c .", - "prepare": "husky install" + "prepare": "husky install", + "lint:full": "npm run lint && npm run build && npm run lint -w examples" }, "files": [ "dist" @@ -57,27 +62,26 @@ }, "dependencies": { "async-lock": "^1.4.1", + "async-retry": "^1.3.3", "axios": "^1.6.7", "bottleneck": "^2.19.5", - "collect.js": "^4.36.1", "debug": "^4.3.4", "decimal.js-light": "^2.5.1", "eventemitter3": "^5.0.1", "eventsource": "^2.0.2", - "flatbuffers": "^23.5.26", + "flatbuffers": "^24.3.7", "ip-num": "^1.5.1", "js-sha3": "^0.9.3", - "pino": "^8.19.0", - "pino-pretty": "^10.3.1", + "rxjs": "^7.8.1", "semver": "^7.5.4", "tmp": "^0.2.2", - "uuid": "^9.0.1", + "uuid": "^10.0.0", "ws": "^8.16.0", - "ya-ts-client": "^0.5.3" + "ya-ts-client": "^1.1.3" }, "devDependencies": { - "@commitlint/cli": "^18.6.1", - "@commitlint/config-conventional": "^18.6.2", + "@commitlint/cli": "^19.0.3", + "@commitlint/config-conventional": "^19.0.3", "@johanblumenberg/ts-mockito": "^1.0.41", "@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-commonjs": "^25.0.7", @@ -86,19 +90,21 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@types/async-lock": "^1.4.2", + "@types/async-retry": "^1.4.8", "@types/debug": "^4.1.12", "@types/eventsource": "^1.1.15", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", - "@types/node": "^20.11.21", + "@types/node": "^20.11.20", "@types/semver": "^7.5.8", "@types/supertest": "^6.0.2", "@types/tmp": "^0.2.6", - "@types/uuid": "^9.0.8", + "@types/uuid": "^10.0.0", "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^7.1.0", "@typescript-eslint/parser": "^7.1.0", "buffer": "^6.0.3", + "cross-env": "^7.0.3", "cypress": "^13.6.6", "cypress-log-to-output": "^1.1.2", "eslint": "^8.57.0", @@ -114,13 +120,12 @@ "rollup-plugin-polyfill-node": "^0.13.0", "rollup-plugin-visualizer": "^5.12.0", "semantic-release": "^23.0.2", - "stream-browserify": "^3.0.0", "supertest": "^6.3.4", "ts-jest": "^29.1.2", "ts-loader": "^9.5.1", - "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "tslint-config-prettier": "^1.18.0", + "tsx": "^4.7.1", "typedoc": "^0.25.9", "typedoc-plugin-markdown": "^3.17.1", "typedoc-plugin-merge-modules": "^5.1.0", diff --git a/rollup.config.mjs b/rollup.config.mjs index 0e12285be..b44f023f2 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -8,6 +8,8 @@ import nodePolyfills from "rollup-plugin-polyfill-node"; import pkg from "./package.json" assert { type: "json" }; import ignore from "rollup-plugin-ignore"; import filesize from "rollup-plugin-filesize"; +import fs from "node:fs"; +import { fileURLToPath } from "node:url"; /** * Looking for plugins? @@ -27,62 +29,69 @@ export default [ format: "es", }, plugins: [ - ignore(["tmp", "pino"]), - alias({ - entries: [ - { find: "stream", replacement: "stream-browserify" }, - { find: /RedisDatastore/, replacement: "tests/mock/utils/empty_default.js" }, - { find: /IORedisConnection/, replacement: "tests/mock/utils/empty_default.js" }, - { find: /RedisConnection/, replacement: "tests/mock/utils/empty_default.js" }, - { find: /src\/api\/provider-api$/, replacement: "." }, - { find: /\.\/gftp.js/, replacement: "tests/mock/utils/empty.js" }, - { find: /GftpStorageProvider/, replacement: "tests/mock/utils/empty.js" }, - ], - }), + deleteExistingBundles("dist"), + ignore(["tmp"]), nodeResolve({ browser: true, preferBuiltins: true }), commonjs(), nodePolyfills(), - json(), // Required because one our dependencies (bottleneck) loads its own 'version.json' - typescript({ tsconfig: "./tsconfig.json", exclude: ["**/__tests__", "**/*.test.ts"] }), - terser({ keep_classnames: true }), + typescript({ tsconfig: "./tsconfig.json", exclude: ["**/*.test.ts"] }), + terser(), filesize({ reporter: [sizeValidator, "boxen"] }), ], }, // NodeJS { input: { - "golem-js.es": "src/index.ts", - "golem-js-experimental.es": "src/experimental.ts", + "golem-js": "src/index.ts", + "golem-js-experimental": "src/experimental/index.ts", }, output: { dir: "dist", format: "esm", sourcemap: true, - chunkFileNames: "shared-[hash].es.js", + chunkFileNames: "shared-[hash].mjs", + entryFileNames: "[name].mjs", }, plugins: [ - typescript({ tsconfig: "./tsconfig.json", exclude: ["**/__tests__", "**/*.test.ts"] }), + typescript({ tsconfig: "./tsconfig.json", exclude: ["**/*.test.ts"] }), filesize({ reporter: [sizeValidator, "boxen"] }), ], }, { input: { - "golem-js.cjs": "src/index.ts", - "golem-js-experimental.cjs": "src/experimental.ts", + "golem-js": "src/index.ts", + "golem-js-experimental": "src/experimental/index.ts", }, output: { dir: "dist", format: "cjs", sourcemap: true, - chunkFileNames: "shared-[hash].cjs.js", + chunkFileNames: "shared-[hash].js", }, plugins: [ - typescript({ tsconfig: "./tsconfig.json", exclude: ["**/__tests__", "**/*.test.ts"] }), + typescript({ + tsconfig: "./tsconfig.json", + exclude: ["**/*.test.ts"], + module: "ES2020", + }), filesize({ reporter: [sizeValidator, "boxen"] }), ], }, ]; +function deleteExistingBundles(path) { + return { + name: "delete-existing-bundles", + buildStart: () => { + const distDir = fileURLToPath(new URL(path, import.meta.url).toString()); + if (fs.existsSync(distDir)) { + fs.rmSync(distDir, { recursive: true }); + } + console.log("Deleted " + distDir); + }, + }; +} + function sizeValidator(options, bundle, { bundleSize }) { if (parseInt(bundleSize) === 0) { throw new Error(`Something went wrong while building. Bundle size = ${bundleSize}`); diff --git a/src/activity/activity.module.ts b/src/activity/activity.module.ts new file mode 100644 index 000000000..8cc4cb364 --- /dev/null +++ b/src/activity/activity.module.ts @@ -0,0 +1,330 @@ +import { EventEmitter } from "eventemitter3"; +import { Agreement } from "../market"; +import { Activity, ActivityEvents, IActivityApi, Result } from "./index"; +import { defaultLogger } from "../shared/utils"; +import { GolemServices } from "../golem-network"; +import { ExeUnit, ExeUnitOptions } from "./exe-unit"; +import { ExecutionOptions, ExeScriptExecutor, ExeScriptRequest } from "./exe-script-executor"; +import { catchError, Observable, tap } from "rxjs"; +import { StreamingBatchEvent } from "./results"; + +export interface ActivityModule { + events: EventEmitter; + + /** + * Create and start a new activity on the provider for the supplied agreement + * + * @return The resulting activity on the provider for further use + */ + createActivity(agreement: Agreement): Promise; + + /** + * Definitely terminate any work on the provider + * + * @return The activity that was permanently terminated + */ + destroyActivity(activity: Activity): Promise; + + /** + * Fetches the latest state of the activity. It's recommended to use this method + * before performing any actions on the activity to make sure it's in the correct state. + * If the fetched activity's state is different from the one you have, an event will be emitted. + */ + refreshActivity(staleActivity: Activity): Promise; + + /** + * Fetches the activity by its ID from yagna. If the activity doesn't exist, an error will be thrown. + */ + findActivityById(activityId: string): Promise; + + /** + * Create a exe-unit "within" the activity so that you can perform commands on the rented resources + * + * @return An ExeUnit that's fully commissioned and the user can execute their commands + */ + createExeUnit(activity: Activity, options?: ExeUnitOptions): Promise; + + /** + * Factory method for creating a script executor for the activity + */ + createScriptExecutor(activity: Activity, options?: ExecutionOptions): ExeScriptExecutor; + + /** + * Execute a script on the activity. + */ + executeScript(activity: Activity, script: ExeScriptRequest): Promise; + + /** + * Fetch the results of a batch execution. + */ + getBatchResults(activity: Activity, batchId: string, commandIndex?: number, timeout?: number): Promise; + + /** + * Create an observable that will emit events from the streaming batch. + */ + observeStreamingBatchEvents( + activity: Activity, + batchId: string, + commandIndex?: number, + ): Observable; +} + +/** + * Information about a file that has been published via the FileServer + */ +export type FileServerEntry = { + /** The URL of the file, that the clients can use to reach and download the file */ + fileUrl: string; + + /** The checksum that can be used by clients to validate integrity of the downloaded file */ + fileHash: string; +}; + +/** + * An abstract interface describing a File Server that can be used to expose files from the Requestor to the Golem Network + */ +export interface IFileServer { + /** + * Exposes a file that can be accessed via Golem Network and GFTP + */ + publishFile(sourcePath: string): Promise; + + /** + * Tells if the file was already published on the server + */ + isFilePublished(sourcePath: string): boolean; + + /** + * Returns publishing information for a file that has been already served + */ + getPublishInfo(sourcePath: string): FileServerEntry | undefined; + + /** + * Tells if the server is currently serving any files + */ + isServing(): boolean; +} + +export class ActivityModuleImpl implements ActivityModule { + public readonly events: EventEmitter = new EventEmitter(); + + private readonly logger = defaultLogger("activity"); + + private readonly activityApi: IActivityApi; + + constructor(private readonly services: GolemServices) { + this.logger = services.logger; + this.activityApi = services.activityApi; + } + createScriptExecutor(activity: Activity, options?: ExecutionOptions): ExeScriptExecutor { + return new ExeScriptExecutor(activity, this, this.logger.child("executor"), options); + } + + async executeScript(activity: Activity, script: ExeScriptRequest): Promise { + this.logger.debug("Executing script on activity", { activityId: activity.id }); + try { + this.events.emit("scriptSent", { + activity, + script, + }); + + const result = await this.activityApi.executeScript(activity, script); + + this.events.emit("scriptExecuted", { + activity: await this.refreshActivity(activity).catch(() => { + this.logger.warn("Failed to refresh activity after script execution", { activityId: activity.id }); + return activity; + }), + script, + result, + }); + + return result; + } catch (error) { + this.events.emit("errorExecutingScript", { + activity: await this.refreshActivity(activity).catch(() => { + this.logger.warn("Failed to refresh activity after script execution error", { activityId: activity.id }); + return activity; + }), + script, + error, + }); + + throw error; + } + } + async getBatchResults( + activity: Activity, + batchId: string, + commandIndex?: number | undefined, + timeout?: number | undefined, + ): Promise { + this.logger.debug("Fetching batch results", { activityId: activity.id, batchId }); + try { + const results = await this.activityApi.getExecBatchResults(activity, batchId, commandIndex, timeout); + this.events.emit("batchResultsReceived", { + activity: await this.refreshActivity(activity).catch(() => { + this.logger.warn("Failed to refresh activity after batch results received", { activityId: activity.id }); + return activity; + }), + batchId, + results, + }); + return results; + } catch (error) { + this.events.emit("errorGettingBatchResults", { + activity: await this.refreshActivity(activity).catch(() => { + this.logger.warn("Failed to refresh activity after batch results error", { activityId: activity.id }); + return activity; + }), + batchId, + error, + }); + throw error; + } + } + observeStreamingBatchEvents( + activity: Activity, + batchId: string, + commandIndex?: number | undefined, + ): Observable { + this.logger.debug("Observing streaming batch events", { activityId: activity.id, batchId }); + return this.activityApi.getExecBatchEvents(activity, batchId, commandIndex).pipe( + tap(async (event) => { + this.events.emit("batchEventsReceived", { + activity: await this.refreshActivity(activity).catch(() => { + this.logger.warn("Failed to refresh activity after batch events received", { activityId: activity.id }); + return activity; + }), + batchId, + event, + }); + }), + catchError(async (error) => { + this.events.emit("errorGettingBatchEvents", { + activity: await this.refreshActivity(activity).catch(() => { + this.logger.warn("Failed to refresh activity after batch events error", { activityId: activity.id }); + return activity; + }), + batchId, + error, + }); + throw error; + }), + ); + } + + async createActivity(agreement: Agreement): Promise { + this.logger.debug("Creating activity", { + agreementId: agreement.id, + provider: agreement.provider, + }); + try { + const activity = await this.activityApi.createActivity(agreement); + this.events.emit("activityCreated", { activity }); + this.logger.info("Created activity", { + activityId: activity.id, + agreementId: agreement.id, + provider: agreement.provider, + }); + return activity; + } catch (error) { + this.events.emit("errorCreatingActivity", error); + throw error; + } + } + + async destroyActivity(activity: Activity): Promise { + this.logger.debug("Destroying activity", activity); + try { + const updated = await this.activityApi.destroyActivity(activity); + this.events.emit("activityDestroyed", { + activity: updated, + }); + + this.logger.info("Destroyed activity", { + activityId: updated.id, + agreementId: updated.agreement.id, + provider: updated.agreement.provider, + }); + + return updated; + } catch (error) { + this.events.emit("errorDestroyingActivity", { activity, error }); + throw error; + } + } + + async refreshActivity(staleActivity: Activity): Promise { + // logging to debug level to avoid spamming the logs because this method is called frequently + this.logger.debug("Fetching latest activity state", { + activityId: staleActivity.id, + lastState: staleActivity.getState(), + }); + try { + const freshActivity = await this.activityApi.getActivity(staleActivity.id); + if (freshActivity.getState() !== freshActivity.getPreviousState()) { + this.logger.debug("Activity state changed", { + activityId: staleActivity.id, + previousState: freshActivity.getPreviousState(), + newState: freshActivity.getState(), + }); + this.events.emit("activityStateChanged", { + activity: freshActivity, + previousState: freshActivity.getPreviousState(), + }); + } + return freshActivity; + } catch (error) { + this.events.emit("errorRefreshingActivity", { + activity: staleActivity, + error, + }); + throw error; + } + } + + async findActivityById(activityId: string): Promise { + this.logger.info("Fetching activity by ID", { activityId }); + return await this.activityApi.getActivity(activityId); + } + + async createExeUnit(activity: Activity, options?: ExeUnitOptions): Promise { + this.logger.debug("Creating exe-unit for activity", { activityId: activity.id }); + + const exe = new ExeUnit(activity, this, { + yagnaOptions: this.services.yagna.yagnaOptions, + logger: this.logger.child("exe-unit"), + ...options, + }); + + this.logger.debug("Initializing the exe-unit for activity", { activityId: activity.id }); + + try { + await exe.setup(); + const refreshedActivity = await this.refreshActivity(activity).catch(() => { + this.logger.warn("Failed to refresh activity after work context initialization", { activityId: activity.id }); + return activity; + }); + this.events.emit("exeUnitInitialized", { + activity: refreshedActivity, + }); + this.logger.info("Initialized exe-unit", { + activityId: activity.id, + state: refreshedActivity.getState(), + }); + return exe; + } catch (error) { + this.events.emit("errorInitializingExeUnit", { + activity: await this.refreshActivity(activity).catch(() => { + this.logger.warn("Failed to refresh activity after exe-unit initialization error", { + activityId: activity.id, + }); + return activity; + }), + error, + }); + throw error; + } + } +} diff --git a/src/activity/activity.test.ts b/src/activity/activity.test.ts new file mode 100644 index 000000000..a767d1687 --- /dev/null +++ b/src/activity/activity.test.ts @@ -0,0 +1,25 @@ +import { Activity, ActivityStateEnum, Agreement } from "../index"; +import { instance, mock } from "@johanblumenberg/ts-mockito"; + +const mockAgreement = mock(Agreement); + +describe("Activity", () => { + describe("Getting state", () => { + it("should get activity state", () => { + const activity = new Activity( + "activity-id", + instance(mockAgreement), + ActivityStateEnum.Initialized, + ActivityStateEnum.New, + { + currentUsage: [0.0, 0.0, 0.0], + timestamp: Date.now(), + }, + ); + const state = activity.getState(); + const prev = activity.getPreviousState(); + expect(state).toEqual(ActivityStateEnum.Initialized); + expect(prev).toEqual(ActivityStateEnum.New); + }); + }); +}); diff --git a/src/activity/activity.ts b/src/activity/activity.ts index 422ec5655..d9bdc601c 100644 --- a/src/activity/activity.ts +++ b/src/activity/activity.ts @@ -1,14 +1,4 @@ -import { Result, ResultState, StreamingBatchEvent } from "./results"; -import EventSource from "eventsource"; -import { Readable } from "stream"; -import { Logger, YagnaApi, defaultLogger } from "../utils"; -import sleep from "../utils/sleep"; -import { ActivityFactory } from "./factory"; -import { ActivityConfig } from "./config"; -import { Events } from "../events"; -import { Agreement, ProviderInfo } from "../agreement"; -import { GolemWorkError, WorkErrorCode } from "../task/error"; -import { GolemAbortError, GolemInternalError, GolemTimeoutError } from "../error/golem-error"; +import { Agreement, ProviderInfo } from "../market/agreement"; export enum ActivityStateEnum { New = "New", @@ -17,28 +7,19 @@ export enum ActivityStateEnum { Ready = "Ready", Unresponsive = "Unresponsive", Terminated = "Terminated", + /** In case when we couldn't establish the in on yagna */ + Unknown = "Unknown", } -export interface ExeScriptRequest { - text: string; -} +export type ActivityUsageInfo = { + currentUsage?: number[]; + timestamp: number; +}; -export interface ActivityOptions { - /** timeout for sending and creating batch */ - activityRequestTimeout?: number; - /** timeout for executing batch */ - activityExecuteTimeout?: number; - /** interval for fetching batch results while polling */ - activityExeBatchResultPollIntervalSeconds?: number; - /** Logger module */ - logger?: Logger; - /** Event Bus implements EventTarget */ - eventTarget?: EventTarget; -} +export interface IActivityRepository { + getById(id: string): Promise; -type AxiosError = Error & { response: { status: number }; code: string }; -function isAxiosError(error: Error | AxiosError): error is AxiosError { - return Boolean(error && "response" in error && typeof error.response === "object" && "status" in error.response); + getStateOfActivity(id: string): Promise; } /** @@ -46,413 +27,30 @@ function isAxiosError(error: Error | AxiosError): error is AxiosError { * As part of a given activity, it is possible to execute exe script commands and capture their results. */ export class Activity { - private readonly logger: Logger; - private isRunning = true; - private currentState: ActivityStateEnum = ActivityStateEnum.New; - private eventSource?: EventSource; - /** - * @param id activity ID - * @param agreement Agreement - * @param yagnaApi - {@link YagnaApi} - * @param options - {@link ActivityOptions} - * @hidden + * @param id The ID of the activity in Yagna + * @param agreement The agreement that's related to this activity + * @param currentState The current state as it was obtained from yagna + * @param previousState The previous state (or New if this is the first time we're creating the activity) + * @param usage Current resource usage vector information */ constructor( public readonly id: string, public readonly agreement: Agreement, - protected readonly yagnaApi: YagnaApi, - protected readonly options: ActivityConfig, - ) { - this.logger = options?.logger || defaultLogger("work"); - } - - /** - * Create activity for given agreement ID - * - * @param agreement - * @param yagnaApi - * @param options - {@link ActivityOptions} - * @param secure - defines if activity will be secure type - * @return Activity - */ - static async create( - agreement: Agreement, - yagnaApi: YagnaApi, - options?: ActivityOptions, - secure = false, - ): Promise { - const factory = new ActivityFactory(agreement, yagnaApi, options); - return factory.create(secure); - } - - public getProviderInfo(): ProviderInfo { - return this.agreement.getProviderInfo(); - } - - /** - * Execute script - * - * @param script - exe script request - * @param stream - define type of getting results from execution (polling or streaming) - * @param timeout - execution timeout - */ - public async execute(script: ExeScriptRequest, stream?: boolean, timeout?: number): Promise { - let batchId: string, batchSize: number; - let startTime = new Date(); - try { - batchId = await this.send(script); - startTime = new Date(); - batchSize = JSON.parse(script.text).length; - } catch (error) { - const message = error?.response?.data?.message || error.message || error; - this.logger.error("Execution of script failed.", { - reason: message, - }); - throw new GolemWorkError( - `Unable to execute script ${message}`, - WorkErrorCode.ScriptExecutionFailed, - this.agreement, - this, - this.getProviderInfo(), - error, - ); - } - - this.logger.debug(`Script sent.`, { batchId }); - - this.options.eventTarget?.dispatchEvent( - new Events.ScriptSent({ activityId: this.id, agreementId: this.agreement.id }), - ); - - return stream - ? this.streamingBatch(batchId, batchSize, startTime, timeout) - : this.pollingBatch(batchId, startTime, timeout); - } - - /** - * Stop and destroy activity - * - * @return boolean - */ - public async stop(): Promise { - this.isRunning = false; - await this.end(); - return true; - } - - /** - * Getting current state of activity - * - * @return state - * @throws Error when cannot query the state - */ - public async getState(): Promise { - try { - const { data } = await this.yagnaApi.activity.state.getActivityState(this.id); - const state = data.state[0]; - if (this.currentState !== ActivityStateEnum[state]) { - this.options.eventTarget?.dispatchEvent( - new Events.ActivityStateChanged({ id: this.id, state: ActivityStateEnum[state] }), - ); - this.currentState = ActivityStateEnum[state]; - } - return ActivityStateEnum[state]; - } catch (error) { - this.logger.warn(`Cannot query activity state`, { - reason: error?.response?.data?.message || error?.message || error, - }); - throw new GolemWorkError( - `Cannot query activity state: ${error?.response?.data?.message || error?.message || error}`, - WorkErrorCode.ActivityStatusQueryFailed, - this.agreement, - this, - this.getProviderInfo(), - error, - ); - } - } - - protected async send(script: ExeScriptRequest): Promise { - const { data: batchId } = await this.yagnaApi.activity.control.exec(this.id, script, { - timeout: this.options.activityRequestTimeout, - }); - return batchId; - } - - private async end() { - try { - this.eventSource?.close(); - await this.yagnaApi.activity.control.destroyActivity(this.id, this.options.activityRequestTimeout / 1000, { - timeout: this.options.activityRequestTimeout + 1000, - }); - this.options.eventTarget?.dispatchEvent( - new Events.ActivityDestroyed({ id: this.id, agreementId: this.agreement.id }), - ); - this.logger.debug(`Activity destroyed`, { id: this.id }); - } catch (error) { - throw new GolemWorkError( - `Unable to destroy activity ${this.id}. ${error?.response?.data?.message || error?.message || error}`, - WorkErrorCode.ActivityDestroyingFailed, - this.agreement, - this, - this.getProviderInfo(), - ); - } - } - - private async pollingBatch(batchId: string, startTime: Date, timeout?: number): Promise { - this.logger.debug("Starting to poll for batch results"); - let isBatchFinished = false; - let lastIndex: number; - let retryCount = 0; - const maxRetries = 5; - // eslint-disable-next-line @typescript-eslint/no-this-alias - const activity = this; - const { id: activityId, agreement, logger } = activity; - const isRunning = () => this.isRunning; - const { activityExecuteTimeout, eventTarget, activityExeBatchResultPollIntervalSeconds } = this.options; - const api = this.yagnaApi.activity; - const handleError = this.handleError.bind(this); - - return new Readable({ - objectMode: true, - - async read() { - while (!isBatchFinished) { - logger.debug("Polling for batch script execution result"); - - if (startTime.valueOf() + (timeout || activityExecuteTimeout) <= new Date().valueOf()) { - logger.debug("Activity probably timed-out, will stop polling for batch execution results"); - return this.destroy(new GolemTimeoutError(`Activity ${activityId} timeout.`)); - } - - if (!isRunning()) { - logger.debug("Activity is no longer running, will stop polling for batch execution results"); - return this.destroy(new GolemAbortError(`Activity ${activityId} has been interrupted.`)); - } - - try { - logger.debug("Trying to poll for batch execution results from yagna"); - // This will ignore "incompatibility" between ExeScriptCommandResultResultEnum and ResultState, which both - // contain exactly the same entries, however TSC refuses to compile it as it assumes the former is dynamically - // computed. - const { data: rawExecBachResults } = await api.control.getExecBatchResults( - activityId, - batchId, - undefined, - activityExeBatchResultPollIntervalSeconds, - { - timeout: 0, - }, - ); - retryCount = 0; - if (!isRunning()) { - logger.debug("Activity is no longer running, will stop polling for batch execution results"); - return this.destroy(new GolemAbortError(`Activity ${activityId} has been interrupted.`)); - } - - const newResults = rawExecBachResults.map((rawResult) => new Result(rawResult)).slice(lastIndex + 1); - - logger.debug(`Received batch execution results`, { results: newResults, activityId }); - - if (Array.isArray(newResults) && newResults.length) { - newResults.forEach((result) => { - this.push(result); - isBatchFinished = result.isBatchFinished || false; - lastIndex = result.index; - }); - } - } catch (error) { - if (!isRunning()) { - logger.debug("Activity is no longer running, will stop polling for batch execution results"); - return this.destroy(new GolemAbortError(`Activity ${activityId} has been interrupted.`, error)); - } - logger.error(`Processing batch execution results failed`, error); - - try { - retryCount = await handleError(error, lastIndex, retryCount, maxRetries); - } catch (error) { - eventTarget?.dispatchEvent( - new Events.ScriptExecuted({ activityId, agreementId: agreement.id, success: false }), - ); - return this.destroy( - new GolemWorkError( - `Unable to get activity results. ${error?.message || error}`, - WorkErrorCode.ActivityResultsFetchingFailed, - agreement, - activity, - activity.getProviderInfo(), - error, - ), - ); - } - } - } - - eventTarget?.dispatchEvent(new Events.ScriptExecuted({ activityId, agreementId: agreement.id, success: true })); - this.push(null); - }, - }); - } - - private async streamingBatch( - batchId: string, - batchSize: number, - startTime: Date, - timeout?: number, - ): Promise { - const basePath = this.yagnaApi.activity.control["basePath"]; - const apiKey = this.yagnaApi.yagnaOptions.apiKey; - - const eventSource = new EventSource(`${basePath}/activity/${this.id}/exec/${batchId}`, { - headers: { - Accept: "text/event-stream", - Authorization: `Bearer ${apiKey}`, - }, - }); - eventSource.addEventListener("runtime", (event) => results.push(this.parseEventToResult(event.data, batchSize))); - eventSource.addEventListener("error", (error) => errors.push(error.data?.message ?? error)); - this.eventSource = eventSource; - - let isBatchFinished = false; - // eslint-disable-next-line @typescript-eslint/no-this-alias - const activity = this; - const isRunning = () => this.isRunning; - const activityExecuteTimeout = this.options.activityExecuteTimeout; - const { logger } = this; - - const errors: object[] = []; - const results: Result[] = []; - - return new Readable({ - objectMode: true, - async read() { - while (!isBatchFinished) { - let error: Error | undefined; - if (startTime.valueOf() + (timeout || activityExecuteTimeout) <= new Date().valueOf()) { - logger.debug("Activity probably timed-out, will stop streaming batch execution results"); - error = new GolemTimeoutError(`Activity ${activity.id} timeout.`); - } - - if (!isRunning()) { - logger.debug("Activity is no longer running, will stop streaming batch execution results"); - error = new GolemAbortError(`Activity ${activity.id} has been interrupted.`); - } - - if (errors.length) { - error = new GolemWorkError( - `Unable to get activity results. ${JSON.stringify(errors)}`, - WorkErrorCode.ActivityResultsFetchingFailed, - activity.agreement, - activity, - activity.getProviderInfo(), - ); - } - if (error) { - eventSource?.close(); - return this.destroy(error); - } - - if (results.length) { - const result = results.shift(); - this.push(result); - isBatchFinished = result?.isBatchFinished || false; - } - await sleep(500, true); - } - - this.push(null); - eventSource?.close(); - }, - }); - } - - private async handleError(error: Error, cmdIndex: number, retryCount: number, maxRetries: number) { - if (!this.isRunning) { - this.logger.debug("Activity is no longer running, will stop polling for batch execution results"); - throw new GolemAbortError(`Activity ${this.id} has been interrupted.`, error); - } - if (this.isTimeoutError(error)) { - this.logger.warn("API request timeout.", error); - return retryCount; - } - - const { terminated, reason, errorMessage } = await this.isTerminated(); - - if (terminated) { - const msg = (reason || "") + (errorMessage || ""); - this.logger.warn(`Activity terminated by provider.`, { reason: msg, id: this.id }); - throw error; - } - - ++retryCount; - - const failMsg = "There was an error retrieving activity results."; - - if (retryCount < maxRetries) { - this.logger.debug(`${failMsg} Retrying in ${this.options.activityExeBatchResultPollIntervalSeconds} seconds.`); - return retryCount; - } else { - this.logger.warn(`${failMsg} Giving up after ${retryCount} attempts.`, error); - } - - throw new GolemWorkError( - `Command #${cmdIndex || 0} getExecBatchResults error: ${error.message}`, - WorkErrorCode.ActivityResultsFetchingFailed, - this.agreement, - this, - this.getProviderInfo(), - ); - } + protected readonly currentState: ActivityStateEnum = ActivityStateEnum.New, + protected readonly previousState: ActivityStateEnum = ActivityStateEnum.Unknown, + protected readonly usage: ActivityUsageInfo, + ) {} - private isTimeoutError(error: Error | AxiosError) { - if (!isAxiosError(error)) return false; - const timeoutMsg = error.message && error.message.includes("timeout"); - return ( - (error.response && error.response.status === 408) || - error.code === "ETIMEDOUT" || - (error.code === "ECONNABORTED" && timeoutMsg) - ); + public get provider(): ProviderInfo { + return this.agreement.provider; } - private async isTerminated(): Promise<{ terminated: boolean; reason?: string; errorMessage?: string }> { - try { - const { data } = await this.yagnaApi.activity.state.getActivityState(this.id); - const state = ActivityStateEnum[data?.state?.[0]]; - return { - terminated: state === ActivityStateEnum.Terminated, - reason: data?.reason, - errorMessage: data?.errorMessage, - }; - } catch (err) { - this.logger.debug(`Cannot query activity state:`, err); - return { terminated: false }; - } + public getState() { + return this.currentState; } - private parseEventToResult(msg: string, batchSize: number): Result { - try { - const event: StreamingBatchEvent = JSON.parse(msg); - // StreamingBatchEvent has a slightly more extensive structure, - // including a return code that could be added to the Result entity... (?) - return new Result({ - index: event.index, - eventDate: event.timestamp, - result: event?.kind?.finished - ? event?.kind?.finished?.return_code === 0 - ? ResultState.Ok - : ResultState.Error - : event?.kind?.stderr - ? ResultState.Error - : ResultState.Ok, - stdout: event?.kind?.stdout, - stderr: event?.kind?.stderr, - message: event?.kind?.finished?.message, - isBatchFinished: event.index + 1 >= batchSize && Boolean(event?.kind?.finished), - }); - } catch (error) { - throw new GolemInternalError(`Cannot parse ${msg} as StreamingBatchEvent`); - } + public getPreviousState() { + return this.previousState; } } diff --git a/src/activity/api.ts b/src/activity/api.ts new file mode 100644 index 000000000..5bf678c69 --- /dev/null +++ b/src/activity/api.ts @@ -0,0 +1,48 @@ +import { Activity, ActivityStateEnum } from "./activity"; +import { Agreement } from "../market"; +import { ExeScriptRequest } from "./exe-script-executor"; +import { Result, StreamingBatchEvent } from "./results"; +import { Observable } from "rxjs"; + +export type ActivityEvents = { + activityCreated: (event: { activity: Activity }) => void; + errorCreatingActivity: (event: { error: Error }) => void; + + activityDestroyed: (event: { activity: Activity }) => void; + errorDestroyingActivity: (event: { activity: Activity; error: Error }) => void; + + exeUnitInitialized: (event: { activity: Activity }) => void; + errorInitializingExeUnit: (event: { activity: Activity; error: Error }) => void; + + activityStateChanged: (event: { activity: Activity; previousState: ActivityStateEnum }) => void; + errorRefreshingActivity: (event: { activity: Activity; error: Error }) => void; + + scriptSent: (event: { activity: Activity; script: ExeScriptRequest }) => void; + scriptExecuted: (event: { activity: Activity; script: ExeScriptRequest; result: string }) => void; + errorExecutingScript: (event: { activity: Activity; script: ExeScriptRequest; error: Error }) => void; + + batchResultsReceived: (event: { activity: Activity; batchId: string; results: Result[] }) => void; + errorGettingBatchResults: (event: { activity: Activity; batchId: string; error: Error }) => void; + + batchEventsReceived: (event: { activity: Activity; batchId: string; event: StreamingBatchEvent }) => void; + errorGettingBatchEvents: (event: { activity: Activity; batchId: string; error: Error }) => void; +}; + +/** + * Represents a set of use cases related to managing the lifetime of an activity + */ +export interface IActivityApi { + getActivity(id: string): Promise; + + createActivity(agreement: Agreement): Promise; + + destroyActivity(activity: Activity): Promise; + + getActivityState(id: string): Promise; + + executeScript(activity: Activity, script: ExeScriptRequest): Promise; + + getExecBatchResults(activity: Activity, batchId: string, commandIndex?: number, timeout?: number): Promise; + + getExecBatchEvents(activity: Activity, batchId: string, commandIndex?: number): Observable; +} diff --git a/src/activity/config.ts b/src/activity/config.ts index ba7213c7d..c313c0c3d 100644 --- a/src/activity/config.ts +++ b/src/activity/config.ts @@ -1,28 +1,21 @@ -import { ActivityOptions } from "./activity"; -import { Logger, defaultLogger } from "../utils"; +import { ExecutionOptions } from "./exe-script-executor"; const DEFAULTS = { - activityRequestTimeout: 10000, - activityExecuteTimeout: 1000 * 60 * 5, // 5 min, activityExeBatchResultPollIntervalSeconds: 5, + activityExeBatchResultMaxRetries: 20, }; /** * @internal */ -export class ActivityConfig { - public readonly activityRequestTimeout: number; - public readonly activityExecuteTimeout: number; +export class ExecutionConfig { public readonly activityExeBatchResultPollIntervalSeconds: number; - public readonly logger: Logger; - public readonly eventTarget?: EventTarget; + public readonly activityExeBatchResultMaxRetries: number; - constructor(options?: ActivityOptions) { - this.activityRequestTimeout = options?.activityRequestTimeout || DEFAULTS.activityRequestTimeout; - this.activityExecuteTimeout = options?.activityExecuteTimeout || DEFAULTS.activityExecuteTimeout; + constructor(options?: ExecutionOptions) { + this.activityExeBatchResultMaxRetries = + options?.activityExeBatchResultMaxRetries || DEFAULTS.activityExeBatchResultMaxRetries; this.activityExeBatchResultPollIntervalSeconds = options?.activityExeBatchResultPollIntervalSeconds || DEFAULTS.activityExeBatchResultPollIntervalSeconds; - this.logger = options?.logger || defaultLogger("work"); - this.eventTarget = options?.eventTarget; } } diff --git a/src/activity/exe-script-executor.test.ts b/src/activity/exe-script-executor.test.ts new file mode 100644 index 000000000..46ea51182 --- /dev/null +++ b/src/activity/exe-script-executor.test.ts @@ -0,0 +1,616 @@ +import { Activity } from "./activity"; +import { _, anything, imock, instance, mock, reset, verify, when } from "@johanblumenberg/ts-mockito"; +import { Capture, Deploy, DownloadFile, Run, Script, Start, Terminate, UploadFile } from "./script"; +import { buildExeScriptSuccessResult } from "../../tests/utils/helpers"; +import { GolemWorkError, WorkErrorCode } from "./exe-unit"; +import { Logger, sleep } from "../shared/utils"; +import { GolemAbortError } from "../shared/error/golem-error"; +import { ExeScriptExecutor } from "./exe-script-executor"; +import { StorageProvider } from "../shared/storage"; +import { from, lastValueFrom, of, throwError, toArray } from "rxjs"; +import { Result, StreamingBatchEvent } from "./results"; +import resetAllMocks = jest.resetAllMocks; +import { ActivityModule } from "./activity.module"; + +describe("ExeScriptExecutor", () => { + const mockActivity = mock(Activity); + const mockLogger = imock(); + const mockActivityModule = imock(); + const mockStorageProvider = imock(); + + beforeEach(() => { + reset(mockActivity); + reset(mockLogger); + reset(mockStorageProvider); + reset(mockActivityModule); + resetAllMocks(); + when(mockActivity.provider).thenReturn({ + id: "test-provider-id", + name: "test-provider-name", + walletAddress: "0xProviderWallet", + }); + }); + + describe("Executing", () => { + it("should execute commands on activity", async () => { + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + ); + + when(mockActivityModule.getBatchResults(anything(), anything(), anything(), anything())).thenResolve([ + new Result({ + isBatchFinished: true, + result: "Ok", + stdout: "Done", + stderr: "", + index: 1, + eventDate: new Date().toISOString(), + }), + ]); + + const executionMetadata = await executor.execute(new Deploy().toExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata); + const result = await lastValueFrom(result$); + expect(result.result).toEqual("Ok"); + }); + + it("should execute script and get results by iterator", async () => { + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + ); + const command1 = new Deploy(); + const command2 = new Start(); + const command3 = new Run("test_command1"); + const command4 = new Run("test_command2"); + const command5 = new Terminate(); + const script = Script.create([command1, command2, command3, command4, command5]); + + when(mockActivityModule.getBatchResults(anything(), anything(), anything(), anything())).thenResolve([ + buildExeScriptSuccessResult("test"), + buildExeScriptSuccessResult("test"), + buildExeScriptSuccessResult("stdout_test_command_run_1"), + buildExeScriptSuccessResult("stdout_test_command_run_2"), + buildExeScriptSuccessResult("test"), + ]); + + const expectedRunStdOuts = ["test", "test", "stdout_test_command_run_1", "stdout_test_command_run_2", "test"]; + await script.before(); + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata); + const results = await lastValueFrom(result$.pipe(toArray())); + for (const result of results) { + expect(result.result).toEqual("Ok"); + expect(result.stdout).toEqual(expectedRunStdOuts.shift()); + } + + await script.after([]); + }); + + it("should execute script and get results by events", async () => { + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + ); + const command1 = new Deploy(); + const command2 = new Start(); + const command3 = new UploadFile(instance(mockStorageProvider), "testSrc", "testDst"); + const command4 = new Run("test_command1"); + const command5 = new DownloadFile(instance(mockStorageProvider), "testSrc", "testDst"); + const command6 = new Terminate(); + const script = Script.create([command1, command2, command3, command4, command5, command6]); + + when(mockActivityModule.getBatchResults(_, _, _, _)).thenResolve([ + buildExeScriptSuccessResult("test"), + buildExeScriptSuccessResult("test"), + buildExeScriptSuccessResult("stdout_test_command_run_1"), + buildExeScriptSuccessResult("stdout_test_command_run_2"), + buildExeScriptSuccessResult("test"), + buildExeScriptSuccessResult("test"), + ]); + + const expectedRunStdOuts = [ + "test", + "test", + "stdout_test_command_run_1", + "stdout_test_command_run_2", + "test", + "test", + ]; + await script.before(); + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata); + let resultCount = 0; + // each result 2 assertions and 1 in "complete" + expect.assertions(12 + 1); + return new Promise((res, rej) => { + result$.subscribe({ + next: (result) => { + expect(result.result).toEqual("Ok"); + expect(result.stdout).toEqual(expectedRunStdOuts.shift()); + ++resultCount; + }, + complete: async () => { + try { + await script.after([]); + expect(resultCount).toEqual(6); + return res(); + } catch (err) { + rej(err); + } + }, + error: (error) => rej(error), + }); + }); + }); + + it("should execute script by streaming batch", async () => { + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + ); + const command1 = new Deploy(); + const command2 = new Start(); + const capture: Capture = { + stdout: { stream: { format: "string" } }, + stderr: { stream: { format: "string" } }, + }; + const command3 = new Run("test_command1", null, null, capture); + const command4 = new Terminate(); + const script = Script.create([command1, command2, command3, command4]); + const mockedEvents: StreamingBatchEvent[] = [ + { + batch_id: "04a9b0f49e564db99e6f15ba95c35817", + index: 0, + timestamp: "2022-06-23T10:42:38.626573153", + kind: { stdout: '{"startMode":"blocking","valid":{"Ok":""},"vols":[]}' }, + }, + { + batch_id: "04a9b0f49e564db99e6f15ba95c35817", + index: 0, + timestamp: "2022-06-23T10:42:38.626958777", + kind: { finished: { return_code: 0, message: null } }, + }, + { + batch_id: "04a9b0f49e564db99e6f15ba95c35817", + index: 1, + timestamp: "2022-06-23T10:42:38.626960850", + kind: { started: { command: { start: { args: [] } } } }, + }, + { + batch_id: "04a9b0f49e564db99e6f15ba95c35817", + index: 1, + timestamp: "2022-06-23T10:42:39.946031527", + kind: { finished: { return_code: 0, message: null } }, + }, + { + batch_id: "04a9b0f49e564db99e6f15ba95c35817", + index: 2, + timestamp: "2022-06-23T10:42:39.946034161", + kind: { + started: { + command: { + run: { + entry_point: "/bin/sh", + args: ["-c", "echo test"], + capture: { stdout: { stream: { format: "str" } }, stderr: { stream: { format: "str" } } }, + }, + }, + }, + }, + }, + { + batch_id: "04a9b0f49e564db99e6f15ba95c35817", + index: 2, + timestamp: "2022-06-23T10:42:39.957927713", + kind: { stdout: "test" }, + }, + { + batch_id: "04a9b0f49e564db99e6f15ba95c35817", + index: 2, + timestamp: "2022-06-23T10:42:39.958238754", + kind: { finished: { return_code: 0, message: null } }, + }, + { + batch_id: "04a9b0f49e564db99e6f15ba95c35817", + index: 3, + timestamp: "2022-06-23T10:42:39.962014674", + kind: { started: { command: { terminate: {} } } }, + }, + { + batch_id: "04a9b0f49e564db99e6f15ba95c35817", + index: 3, + timestamp: "2022-06-23T10:42:40.009603540", + kind: { finished: { return_code: 0, message: null } }, + }, + ]; + when(mockActivityModule.observeStreamingBatchEvents(_, _)).thenReturn(from(mockedEvents)); + await script.before(); + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata, true); + let expectedStdout; + const results = await lastValueFrom(result$.pipe(toArray())); + for (const result of results) { + expect(result).toHaveProperty("index"); + if (result.index === 2 && result.stdout) expectedStdout = result.stdout; + } + expect(expectedStdout).toEqual("test"); + await script.after([]); + }); + }); + + describe("Cancelling", () => { + it("should cancel executor", async () => { + const ac = new AbortController(); + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + { signalOrTimeout: ac.signal }, + ); + const command1 = new Deploy(); + const command2 = new Start(); + const command3 = new Run("test_command1"); + const command4 = new Run("test_command2"); + const command5 = new Run("test_command3"); + const command6 = new Terminate(); + const script = Script.create([command1, command2, command3, command4, command5, command6]); + await script.before(); + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata, undefined, undefined); + ac.abort(); + + expect.assertions(1); + return new Promise((res, rej) => { + result$.subscribe({ + complete: () => rej("Shouldn't have completed"), + error: (error) => { + try { + expect(error).toEqual(new GolemAbortError(`Execution of script has been aborted`)); + return res(); + } catch (err) { + rej(err); + } + }, + }); + }); + }); + + it("should cancel executor while streaming batch", async () => { + when(mockActivityModule.observeStreamingBatchEvents(_, _)).thenReturn(of()); + const ac = new AbortController(); + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + { + signalOrTimeout: ac.signal, + }, + ); + const command1 = new Deploy(); + const command2 = new Start(); + const capture: Capture = { + stdout: { stream: { format: "string" } }, + stderr: { stream: { format: "string" } }, + }; + const command3 = new Run("test_command1", null, null, capture); + const command4 = new Terminate(); + const script = Script.create([command1, command2, command3, command4]); + await script.before(); + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata, true, undefined); + ac.abort(); + + expect.assertions(1); + return new Promise((res, rej) => { + result$.subscribe({ + complete: () => rej("Shouldn't have completed"), + error: (error) => { + try { + expect(error).toEqual(new GolemAbortError(`Execution of script has been aborted`)); + return res(); + } catch (err) { + rej(err); + } + }, + }); + }); + }); + }); + + describe("Error handling", () => { + it("should handle some error", async () => { + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + ); + const command1 = new Deploy(); + const command2 = new Start(); + const command3 = new Run("test_command1"); + const script = Script.create([command1, command2, command3]); + + const error = new Error("Some undefined error"); + when(mockActivityModule.getBatchResults(anything(), anything(), anything(), anything())).thenReject(error); + + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata, false, 200, 0); + + expect.assertions(7); + return new Promise((res, rej) => { + result$.subscribe({ + complete: () => rej("Shouldn't have completed"), + error: (error) => { + try { + expect(error).toBeInstanceOf(GolemWorkError); + expect(error.code).toEqual(WorkErrorCode.ActivityResultsFetchingFailed); + expect(error.getActivity()).toBeDefined(); + expect(error.getAgreement()).toBeDefined(); + expect(error.getProvider()?.name).toEqual("test-provider-name"); + expect(error.previous?.toString()).toEqual("Error: Some undefined error"); + expect(error.toString()).toEqual("Error: Unable to get activity results. Error: Some undefined error"); + return res(); + } catch (err) { + rej(err); + } + }, + }); + }); + }); + + it("should handle non-retryable error", async () => { + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + { + activityExeBatchResultPollIntervalSeconds: 10, + }, + ); + const command1 = new Deploy(); + const command2 = new Start(); + const command3 = new Run("test_command1"); + const script = Script.create([command1, command2, command3]); + + const error = { + message: "non-retryable error", + status: 401, + toString: () => `Error: non-retryable error`, + }; + when(mockActivityModule.getBatchResults(anything(), anything(), anything(), anything())).thenReject(error); + + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata, false, 1_000, 3); + + expect.assertions(6); + return new Promise((res, rej) => { + result$.subscribe({ + complete: () => rej("Shouldn't have completed"), + error: (error) => { + try { + expect(error).toBeInstanceOf(GolemWorkError); + expect(error.code).toEqual(WorkErrorCode.ActivityResultsFetchingFailed); + expect(error.getActivity()).toBeDefined(); + expect(error.getAgreement()).toBeDefined(); + expect(error.getProvider()?.name).toEqual("test-provider-name"); + expect(error.previous?.toString()).toEqual("Error: non-retryable error"); + return res(); + } catch (err) { + rej(err); + } + }, + }); + }); + }); + + it("should retry when a retryable error occurs", async () => { + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + ); + const command1 = new Deploy(); + const command2 = new Start(); + const command3 = new Run("test_command1"); + const script = Script.create([command1, command2, command3]); + + const error = { + message: "timeout", + status: 408, + toString: () => `Error: timeout`, + }; + const testResult = new Result({ + isBatchFinished: true, + result: "Ok" as "Ok" | "Error", + stdout: "Done", + stderr: "", + index: 1, + eventDate: new Date().toISOString(), + }); + when(mockActivityModule.getBatchResults(anything(), anything(), anything(), anything())) + .thenReject(error) + .thenReject(error) + .thenResolve([testResult]); + + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata, false, undefined, 10); + const results = await lastValueFrom(result$.pipe(toArray())); + for (const result of results) { + expect(result).toEqual(testResult); + } + verify(mockActivityModule.getBatchResults(anything(), anything(), anything(), anything())).times(3); + }, 7_000); + + it("should handle termination error", async () => { + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + ); + const command1 = new Deploy(); + const command2 = new Start(); + const command3 = new Run("test_command1"); + const script = Script.create([command1, command2, command3]); + const error = { + message: "GSB error: endpoint address not found. Terminated.", + status: 500, + toString: () => "GSB error: endpoint address not found. Terminated.", + }; + + when(mockActivityModule.getBatchResults(anything(), anything(), anything(), anything())).thenReject(error); + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata, false, undefined, 1); + + expect.assertions(7); + return new Promise((res, rej) => { + result$.subscribe({ + complete: () => rej("Shouldn't have completed"), + error: (error) => { + try { + expect(error).toBeInstanceOf(GolemWorkError); + expect(error.code).toEqual(WorkErrorCode.ActivityResultsFetchingFailed); + expect(error.getActivity()).toBeDefined(); + expect(error.getAgreement()).toBeDefined(); + expect(error.getProvider()?.name).toEqual("test-provider-name"); + expect(error.previous?.message).toEqual("GSB error: endpoint address not found. Terminated."); + expect(error.toString()).toEqual( + "Error: Unable to get activity results. GSB error: endpoint address not found. Terminated.", + ); + return res(); + } catch (err) { + rej(err); + } + }, + }); + }); + }); + + it("should handle timeout error", async () => { + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + ); + const command1 = new Deploy(); + const command2 = new Start(); + const command3 = new Run("test_command1"); + const command4 = new Run("test_command2"); + const command5 = new Run("test_command3"); + const script = Script.create([command1, command2, command3, command4, command5]); + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata, false, 1); + + // wait for execute timeout to fire + await sleep(10, true); + + expect.assertions(2); + return new Promise((res, rej) => { + result$.subscribe({ + complete: () => rej("Shouldn't have completed"), + error: (error) => { + try { + expect(error).toBeInstanceOf(GolemAbortError); + expect(error.toString()).toEqual("Error: Execution of script has been aborted"); + return res(); + } catch (err) { + rej(err); + } + }, + }); + }); + }); + + it("should handle abort error while streaming batch", async () => { + when(mockActivityModule.observeStreamingBatchEvents(anything(), anything())).thenReturn(of()); + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + { + signalOrTimeout: 1, + }, + ); + const command1 = new Deploy(); + const command2 = new Start(); + const capture: Capture = { + stdout: { stream: { format: "string" } }, + stderr: { stream: { format: "string" } }, + }; + const command3 = new Run("test_command1", null, null, capture); + const command4 = new Terminate(); + const script = Script.create([command1, command2, command3, command4]); + await script.before(); + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata, true, 800); + + // wait for ExeScriptExecutor abort signal to fire + await sleep(10, true); + + expect.assertions(2); + return new Promise((res, rej) => { + result$.subscribe({ + complete: () => rej("Shouldn't have completed"), + error: (error) => { + try { + expect(error).toBeInstanceOf(GolemAbortError); + expect(error.toString()).toEqual("Error: Execution of script has been aborted"); + return res(); + } catch (err) { + rej(err); + } + }, + }); + }); + }); + + it("should handle some error while streaming batch", async () => { + const executor = new ExeScriptExecutor( + instance(mockActivity), + instance(mockActivityModule), + instance(mockLogger), + ); + const command1 = new Deploy(); + const command2 = new Start(); + const capture: Capture = { + stdout: { stream: { format: "string" } }, + stderr: { stream: { format: "string" } }, + }; + const command3 = new Run("test_command1", null, null, capture); + const command4 = new Terminate(); + const script = Script.create([command1, command2, command3, command4]); + const mockedEventSourceErrorMessage = "Some undefined error"; + when(mockActivityModule.observeStreamingBatchEvents(_, _)).thenReturn( + throwError(() => mockedEventSourceErrorMessage), + ); + await script.before(); + const executionMetadata = await executor.execute(script.getExeScriptRequest()); + const result$ = executor.getResultsObservable(executionMetadata, true); + + expect.assertions(7); + return new Promise((res, rej) => { + result$.subscribe({ + complete: () => rej("Shouldn't have completed"), + error: (error) => { + try { + expect(error).toBeInstanceOf(GolemWorkError); + expect(error.code).toEqual(WorkErrorCode.ActivityResultsFetchingFailed); + expect(error.getActivity()).toBeDefined(); + expect(error.getAgreement()).toBeDefined(); + expect(error.getProvider()?.name).toEqual("test-provider-name"); + expect(error.previous?.toString()).toEqual("Some undefined error"); + expect(error.toString()).toEqual("Error: Unable to get activity results. Some undefined error"); + return res(); + } catch (err) { + rej(err); + } + }, + }); + }); + }); + }); +}); diff --git a/src/activity/exe-script-executor.ts b/src/activity/exe-script-executor.ts new file mode 100644 index 000000000..266fadf96 --- /dev/null +++ b/src/activity/exe-script-executor.ts @@ -0,0 +1,254 @@ +import { + anyAbortSignal, + createAbortSignalFromTimeout, + Logger, + mergeUntilFirstComplete, + runOnNextEventLoopIteration, +} from "../shared/utils"; +import { ExecutionConfig } from "./config"; +import { GolemWorkError, WorkErrorCode } from "./exe-unit"; +import { withTimeout } from "../shared/utils/timeout"; +import { GolemAbortError } from "../shared/error/golem-error"; +import retry from "async-retry"; +import { Result, StreamingBatchEvent } from "./results"; +import { Activity } from "./activity"; +import { getMessageFromApiError } from "../shared/utils/apiErrorMessage"; +import { ActivityModule } from "./activity.module"; +import { catchError, map, Observable, takeWhile } from "rxjs"; + +/** + * Information needed to fetch the results of a script execution + */ +export interface ScriptExecutionMetadata { + batchId: string; + batchSize: number; +} + +export interface ExeScriptRequest { + text: string; +} + +export interface ExecutionOptions { + /** interval for fetching batch results while polling */ + activityExeBatchResultPollIntervalSeconds?: number; + /** maximum number of retries retrieving results when an error occurs, default: 10 */ + activityExeBatchResultMaxRetries?: number; + /** The timeout in milliseconds or an AbortSignal that will be used to cancel the execution */ + signalOrTimeout?: number | AbortSignal; +} + +const RETRYABLE_ERROR_STATUS_CODES = [408, 500]; + +export class ExeScriptExecutor { + private readonly options: ExecutionConfig; + private readonly abortSignal: AbortSignal; + + constructor( + public readonly activity: Activity, + private readonly activityModule: ActivityModule, + private readonly logger: Logger, + options?: ExecutionOptions, + ) { + this.options = new ExecutionConfig(options); + this.abortSignal = createAbortSignalFromTimeout(options?.signalOrTimeout); + } + + /** + * Executes the provided script and returns the batch id and batch size that can be used + * to fetch it's results + * @param script + * @returns script execution metadata - batch id and batch size that can be used to fetch results using `getResultsObservable` + */ + public async execute(script: ExeScriptRequest): Promise { + try { + this.abortSignal.throwIfAborted(); + const batchId = await this.send(script); + const batchSize = JSON.parse(script.text).length; + + this.logger.debug(`Script sent.`, { batchId }); + return { batchId, batchSize }; + } catch (error) { + const message = getMessageFromApiError(error); + + this.logger.error("Execution of script failed.", { + reason: message, + }); + + if (this.abortSignal.aborted) { + throw new GolemAbortError("Executions of script has been aborted", this.abortSignal.reason); + } + throw new GolemWorkError( + `Unable to execute script. ${message}`, + WorkErrorCode.ScriptExecutionFailed, + this.activity.agreement, + this.activity, + this.activity.provider, + error, + ); + } + } + + /** + * Given a batch id and batch size collect the results from yagna. You can choose to either + * stream them as they go or poll for them. When a timeout is reached (by either the timeout provided + * as an argument here or in the constructor) the observable will emit an error. + * + * + * @param batch - batch id and batch size + * @param stream - define type of getting results from execution (polling or streaming) + * @param signalOrTimeout - the timeout in milliseconds or an AbortSignal that will be used to cancel the execution + * @param maxRetries - maximum number of retries retrieving results when an error occurs, default: 10 + */ + public getResultsObservable( + batch: ScriptExecutionMetadata, + stream?: boolean, + signalOrTimeout?: number | AbortSignal, + maxRetries?: number, + ): Observable { + const signal = anyAbortSignal(this.abortSignal, createAbortSignalFromTimeout(signalOrTimeout)); + + // observable that emits when the script execution should be aborted + const abort$ = new Observable((subscriber) => { + const getError = () => new GolemAbortError("Execution of script has been aborted", signal.reason); + + if (signal.aborted) { + subscriber.error(getError()); + } + signal.addEventListener("abort", () => { + subscriber.error(getError()); + }); + }); + + // get an observable that will emit results of a batch execution + const results$ = stream + ? this.streamingBatch(batch.batchId, batch.batchSize) + : this.pollingBatch(batch.batchId, maxRetries); + + return mergeUntilFirstComplete(abort$, results$); + } + + protected async send(script: ExeScriptRequest): Promise { + return withTimeout(this.activityModule.executeScript(this.activity, script), 10_000); + } + + private pollingBatch(batchId: string, maxRetries?: number): Observable { + let isCompleted = false; + let lastIndex: number; + + const { id: activityId, agreement } = this.activity; + + const { activityExeBatchResultPollIntervalSeconds, activityExeBatchResultMaxRetries } = this.options; + const { logger, activity, activityModule } = this; + + return new Observable((subscriber) => { + const pollForResults = async (): Promise => { + if (isCompleted) { + subscriber.complete(); + return; + } + logger.debug("Polling for batch script execution result"); + + await retry( + async (bail, attempt) => { + logger.debug(`Trying to poll for batch execution results from yagna. Attempt: ${attempt}`); + try { + if (isCompleted) { + bail(new Error("Batch is finished")); + } + const results = await activityModule.getBatchResults( + activity, + batchId, + undefined, + activityExeBatchResultPollIntervalSeconds, + ); + + const newResults = results && results.slice(lastIndex + 1); + + logger.debug(`Received batch execution results`, { results: newResults, activityId }); + + if (Array.isArray(newResults) && newResults.length) { + newResults.forEach((result) => { + subscriber.next(result); + isCompleted ||= !!result.isBatchFinished; + lastIndex = result.index; + }); + } + } catch (error) { + logger.debug(`Failed to fetch activity results. Attempt: ${attempt}. ${error}`); + if (RETRYABLE_ERROR_STATUS_CODES.includes(error?.status)) { + throw error; + } else { + bail(error); + } + } + }, + { + retries: maxRetries ?? activityExeBatchResultMaxRetries, + maxTimeout: 15_000, + }, + ); + return runOnNextEventLoopIteration(pollForResults); + }; + + pollForResults().catch((error) => { + logger.error(`Polling for batch results failed`, error); + subscriber.error(error); + }); + return () => { + isCompleted = true; + }; + }).pipe( + catchError((error) => { + if (error instanceof GolemWorkError) { + throw error; + } + throw new GolemWorkError( + `Unable to get activity results. ${error}`, + WorkErrorCode.ActivityResultsFetchingFailed, + agreement, + activity, + activity.provider, + error, + ); + }), + ); + } + + private streamingBatch(batchId: string, batchSize: number): Observable { + return this.activityModule.observeStreamingBatchEvents(this.activity, batchId).pipe( + map((resultEvents) => this.parseEventToResult(resultEvents, batchSize)), + takeWhile((result) => !result.isBatchFinished, true), + // transform to domain error + catchError((error) => { + throw new GolemWorkError( + `Unable to get activity results. ${error}`, + WorkErrorCode.ActivityResultsFetchingFailed, + this.activity.agreement, + this.activity, + this.activity.provider, + error, + ); + }), + ); + } + + private parseEventToResult(event: StreamingBatchEvent, batchSize: number): Result { + // StreamingBatchEvent has a slightly more extensive structure, + // including a return code that could be added to the Result entity... (?) + return new Result({ + index: event.index, + eventDate: event.timestamp, + result: event?.kind?.finished + ? event?.kind?.finished?.return_code === 0 + ? "Ok" + : "Error" + : event?.kind?.stderr + ? "Error" + : "Ok", + stdout: event?.kind?.stdout, + stderr: event?.kind?.stderr, + message: event?.kind?.finished?.message, + isBatchFinished: event.index + 1 >= batchSize && Boolean(event?.kind?.finished), + }); + } +} diff --git a/src/task/batch.spec.ts b/src/activity/exe-unit/batch.spec.ts similarity index 65% rename from src/task/batch.spec.ts rename to src/activity/exe-unit/batch.spec.ts index 0a6dced69..bb7f4419c 100644 --- a/src/task/batch.spec.ts +++ b/src/activity/exe-unit/batch.spec.ts @@ -1,19 +1,52 @@ import { DownloadFile, Run, Transfer, UploadData, UploadFile } from "../script"; import { Batch } from "./batch"; -import { NullStorageProvider } from "../storage"; -import { ActivityMock } from "../../tests/mock/activity.mock"; -import { LoggerMock, YagnaMock } from "../../tests/mock"; -import { Result } from "../activity"; -import { agreement } from "../../tests/mock/entities/agreement"; +import { NullStorageProvider } from "../../shared/storage"; +import { Activity, Result } from "../index"; import { GolemWorkError, WorkErrorCode } from "./error"; +import { anything, imock, instance, mock, reset, when } from "@johanblumenberg/ts-mockito"; +import { Logger, YagnaApi } from "../../shared/utils"; +import { + buildExeScriptSuccessResult, + buildExeScriptErrorResult, + buildExecutorResults, +} from "../../../tests/utils/helpers"; +import { Agreement } from "../../market/agreement"; + +import { ExeScriptExecutor } from "../exe-script-executor"; +import { lastValueFrom, of, toArray } from "rxjs"; + +const mockLogger = imock(); +const mockYagna = mock(YagnaApi); +const mockActivity = mock(Activity); +const mockAgreement = mock(Agreement); +const mockExecutor = mock(ExeScriptExecutor); describe("Batch", () => { - let activity: ActivityMock; + let activity: Activity; let batch: Batch; beforeEach(() => { - activity = new ActivityMock("test_id", agreement, new YagnaMock().getApi()); - batch = new Batch(activity, new NullStorageProvider(), new LoggerMock()); + reset(mockLogger); + reset(mockYagna); + reset(mockActivity); + reset(mockAgreement); + reset(mockExecutor); + + const providerInfo = { + id: "provider-id", + name: "Test Provider", + walletAddress: "0xTestProvider", + }; + + when(mockAgreement.provider).thenReturn(providerInfo); + when(mockActivity.provider).thenReturn(providerInfo); + when(mockActivity.agreement).thenReturn(instance(mockAgreement)); + when(mockExecutor.getResultsObservable(anything())).thenReturn(of()); + + activity = instance(mockActivity); + + when(mockExecutor.activity).thenReturn(activity); + batch = new Batch(instance(mockExecutor), new NullStorageProvider(), instance(mockLogger)); }); describe("Commands", () => { @@ -21,7 +54,8 @@ describe("Batch", () => { it("should accept shell command", async () => { expect(batch.run("rm -rf")).toBe(batch); expect(batch["script"]["commands"][0]).toBeInstanceOf(Run); - // TODO: check if constructed script is correct. + expect(batch["script"]["commands"][0]["args"]["entry_point"]).toBe("/bin/sh"); + expect(batch["script"]["commands"][0]["args"]["args"]).toStrictEqual(["-c", "rm -rf"]); }); it("should accept executable", async () => { @@ -89,8 +123,12 @@ describe("Batch", () => { }); it("should work", async () => { - activity.mockResultCreate({ stdout: "Hello World" }); - activity.mockResultCreate({ stdout: "Hello World 2" }); + when(mockExecutor.getResultsObservable(anything())).thenReturn( + buildExecutorResults([ + buildExeScriptSuccessResult("Hello World"), + buildExeScriptSuccessResult("Hello World 2"), + ]), + ); const results = await batch.end(); @@ -101,7 +139,7 @@ describe("Batch", () => { it("should initialize script with script.before()", async () => { const spy = jest.spyOn(batch["script"], "before"); - + when(mockExecutor.getResultsObservable(anything())).thenReturn(buildExecutorResults([])); await batch.end(); expect(spy).toHaveBeenCalled(); @@ -109,7 +147,7 @@ describe("Batch", () => { it("should call script.after() on success", async () => { const spy = jest.spyOn(batch["script"], "after"); - + when(mockExecutor.getResultsObservable(anything())).thenReturn(buildExecutorResults([])); await batch.end(); expect(spy).toHaveBeenCalled(); @@ -117,7 +155,10 @@ describe("Batch", () => { it("should call script.after() on failure", async () => { const spy = jest.spyOn(batch["script"], "after"); - activity.mockResultFailure("FAILURE"); + + when(mockExecutor.getResultsObservable(anything())).thenReturn( + buildExecutorResults(undefined, undefined, new Error("FAILURE")), + ); await expect(batch.end()).rejects.toMatchError( new GolemWorkError( @@ -125,7 +166,7 @@ describe("Batch", () => { WorkErrorCode.ScriptExecutionFailed, activity.agreement, activity, - activity.getProviderInfo(), + activity.provider, new Error("FAILURE"), ), ); @@ -135,7 +176,7 @@ describe("Batch", () => { it("should call script.after() on execute error", async () => { const spy = jest.spyOn(batch["script"], "after"); - jest.spyOn(activity, "execute").mockRejectedValue(new Error("ERROR")); + when(mockExecutor.execute(anything())).thenThrow(new Error("ERROR")); await expect(batch.end()).rejects.toStrictEqual( new GolemWorkError( @@ -143,7 +184,7 @@ describe("Batch", () => { WorkErrorCode.ScriptExecutionFailed, activity.agreement, activity, - activity.getProviderInfo(), + activity.provider, new Error("ERROR"), ), ); @@ -152,14 +193,16 @@ describe("Batch", () => { }); it("should throw error on result stream error", async () => { - activity.mockResultFailure("FAILURE"); + when(mockExecutor.getResultsObservable(anything())).thenReturn( + buildExecutorResults(undefined, undefined, new Error("FAILURE")), + ); await expect(batch.end()).rejects.toStrictEqual( new GolemWorkError( "Unable to execute script Error: FAILURE", WorkErrorCode.ScriptExecutionFailed, activity.agreement, activity, - activity.getProviderInfo(), + activity.provider, new Error("FAILURE"), ), ); @@ -172,13 +215,19 @@ describe("Batch", () => { }); it("should work", async () => { - activity.mockResultCreate({ stdout: "Hello World" }); - activity.mockResultCreate({ stdout: "Hello World 2" }); + when(mockExecutor.getResultsObservable(anything())).thenReturn( + buildExecutorResults([ + buildExeScriptSuccessResult("Hello World"), + buildExeScriptSuccessResult("Hello World 2"), + ]), + ); + const results: Result[] = []; const stream = await batch.endStream(); + const allResults = await lastValueFrom(stream.pipe(toArray())); - for await (const result of stream) { + for (const result of allResults) { results.push(result); } @@ -198,24 +247,22 @@ describe("Batch", () => { it("should call script.after() on success", async () => { const spy = jest.spyOn(batch["script"], "after"); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for await (const r of await batch.endStream()) { - /* empty */ - } + const result$ = await batch.endStream(); + // lastValueFrom errors if the stream is empty, so we need to provide a default value + await lastValueFrom(result$, { defaultValue: undefined }); expect(spy).toHaveBeenCalled(); }); it("should call script.after() on result stream error", async () => { const spy = jest.spyOn(batch["script"], "after"); - activity.mockResultFailure("FAILURE"); + when(mockExecutor.getResultsObservable(anything())).thenReturn( + buildExecutorResults(undefined, [buildExeScriptErrorResult("FAILURE", "FAILURE")]), + ); const stream = await batch.endStream(); try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for await (const r of stream) { - /* empty */ - } + await lastValueFrom(stream); fail("Expected to throw"); } catch (e) { /* empty */ @@ -226,40 +273,20 @@ describe("Batch", () => { it("should call script.after() on execute error", async () => { const spy = jest.spyOn(batch["script"], "after"); - jest.spyOn(activity, "execute").mockRejectedValue(new Error("ERROR")); - - await expect(async () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for await (const r of await batch.endStream()) { - /* empty */ - } - }).rejects.toMatchError( + when(mockExecutor.execute(anything())).thenThrow(new Error("ERROR")); + + await expect(batch.endStream()).rejects.toMatchError( new GolemWorkError( "Unable to execute script Error: ERROR", WorkErrorCode.ScriptExecutionFailed, activity.agreement, activity, - activity.getProviderInfo(), + activity.provider, new Error("ERROR"), ), ); expect(spy).toHaveBeenCalled(); }); - - it("should destroy the stream on result stream error", async () => { - activity.mockResultFailure("FAILURE"); - const stream = await batch.endStream(); - try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for await (const r of stream) { - /* empty */ - } - fail("Expected to throw"); - } catch (e) { - /* empty */ - } - expect(stream.destroyed).toBe(true); - }); }); }); diff --git a/src/task/batch.ts b/src/activity/exe-unit/batch.ts similarity index 52% rename from src/task/batch.ts rename to src/activity/exe-unit/batch.ts index 7c3da9c76..b39ea8c8c 100644 --- a/src/task/batch.ts +++ b/src/activity/exe-unit/batch.ts @@ -1,19 +1,17 @@ import { DownloadFile, Run, Script, Transfer, UploadData, UploadFile } from "../script"; -import { Activity, Result } from "../activity"; -import { StorageProvider } from "../storage"; -import { Logger, defaultLogger } from "../utils"; -import { pipeline, Readable, Transform } from "stream"; +import { Result } from "../index"; +import { StorageProvider } from "../../shared/storage"; +import { Logger } from "../../shared/utils"; import { GolemWorkError, WorkErrorCode } from "./error"; +import { ExeScriptExecutor, ScriptExecutionMetadata } from "../exe-script-executor"; +import { Observable, finalize, map, tap } from "rxjs"; + export class Batch { private readonly script: Script; - static create(activity: Activity, storageProvider: StorageProvider, logger?: Logger): Batch { - return new Batch(activity, storageProvider, logger || defaultLogger("work")); - } - constructor( - private activity: Activity, + private executor: ExeScriptExecutor, private storageProvider: StorageProvider, private logger: Logger, ) { @@ -81,42 +79,42 @@ export class Batch { const script = this.script.getExeScriptRequest(); this.logger.debug(`Sending exec script request to the exe-unit on provider:`, { script }); - const results = await this.activity.execute(script); + const executionMetadata = await this.executor.execute(script); + const result$ = this.executor.getResultsObservable(executionMetadata); return new Promise((resolve, reject) => { this.logger.debug("Reading the results of the batch script"); - results.on("data", (res) => { - this.logger.debug(`Received data for batch script execution`, { res }); - - allResults.push(res); - }); - - results.on("end", () => { - this.logger.debug("End of batch script execution"); - this.script - .after(allResults) - .then((results) => resolve(results)) - .catch((error) => reject(error)); - }); - - results.on("error", (error) => { - const golemError = - error instanceof GolemWorkError - ? error - : new GolemWorkError( - `Unable to execute script ${error}`, - WorkErrorCode.ScriptExecutionFailed, - this.activity.agreement, - this.activity, - this.activity.agreement.getProviderInfo(), - error, - ); - this.logger.debug("Error in batch script execution"); - this.script - .after(allResults) - .then(() => reject(golemError)) - .catch(() => reject(golemError)); // Return original error, as it might be more important. + result$.subscribe({ + next: (res) => { + this.logger.debug(`Received data for batch script execution`, { res }); + allResults.push(res); + }, + complete: () => { + this.logger.debug("End of batch script execution"); + this.script + .after(allResults) + .then((results) => resolve(results)) + .catch((error) => reject(error)); + }, + error: (error) => { + const golemError = + error instanceof GolemWorkError + ? error + : new GolemWorkError( + `Unable to execute script ${error}`, + WorkErrorCode.ScriptExecutionFailed, + this.executor.activity.agreement, + this.executor.activity, + this.executor.activity.agreement.provider, + error, + ); + this.logger.debug("Error in batch script execution"); + this.script + .after(allResults) + .then(() => reject(golemError)) + .catch(() => reject(golemError)); // Return original error, as it might be more important. + }, }); }); } catch (error) { @@ -130,20 +128,20 @@ export class Batch { throw new GolemWorkError( `Unable to execute script ${error}`, WorkErrorCode.ScriptExecutionFailed, - this.activity.agreement, - this.activity, - this.activity.agreement.getProviderInfo(), + this.executor.activity.agreement, + this.executor.activity, + this.executor.activity.agreement.provider, error, ); } } - async endStream(): Promise { + async endStream(): Promise> { const script = this.script; await script.before(); - let results: Readable; + let executionMetadata: ScriptExecutionMetadata; try { - results = await this.activity.execute(this.script.getExeScriptRequest()); + executionMetadata = await this.executor.execute(this.script.getExeScriptRequest()); } catch (error) { // the original error is more important than the one from after() await script.after([]); @@ -153,40 +151,34 @@ export class Batch { throw new GolemWorkError( `Unable to execute script ${error}`, WorkErrorCode.ScriptExecutionFailed, - this.activity.agreement, - this.activity, - this.activity.agreement.getProviderInfo(), + this.executor.activity.agreement, + this.executor.activity, + this.executor.activity.agreement.provider, error, ); } const decodedResults: Result[] = []; - const activity = this.activity; - const errorResultHandler = new Transform({ - objectMode: true, - transform(chunk, encoding, callback) { - const error = - chunk?.result === "Error" - ? new GolemWorkError( - `${chunk?.message}. Stdout: ${chunk?.stdout?.trim()}. Stderr: ${chunk?.stderr?.trim()}`, - WorkErrorCode.ScriptExecutionFailed, - activity.agreement, - activity, - activity.agreement.getProviderInfo(), - ) - : null; - if (error) { - script.after(decodedResults).catch(); - this.destroy(error); - } else { - decodedResults.push(chunk); - // FIXME: This is broken, chunk result didn't go through after() at this point yet, it might be incomplete. - callback(null, chunk); + const { activity } = this.executor; + const result$ = this.executor.getResultsObservable(executionMetadata); + return result$.pipe( + map((chunk) => { + if (chunk.result !== "Error") { + return chunk; } - }, - }); - const resultsWithErrorHandling = pipeline(results, errorResultHandler, () => { - script.after(decodedResults).catch(); - }); - return resultsWithErrorHandling; + throw new GolemWorkError( + `${chunk?.message}. Stdout: ${String(chunk?.stdout).trim()}. Stderr: ${String(chunk?.stderr).trim()}`, + WorkErrorCode.ScriptExecutionFailed, + activity.agreement, + activity, + activity.provider, + ); + }), + tap((chunk) => { + decodedResults.push(chunk); + }), + finalize(() => + script.after(decodedResults).catch((error) => this.logger.error("Failed to cleanup script", { error })), + ), + ); } } diff --git a/src/activity/exe-unit/error.ts b/src/activity/exe-unit/error.ts new file mode 100644 index 000000000..666cc9125 --- /dev/null +++ b/src/activity/exe-unit/error.ts @@ -0,0 +1,43 @@ +import { GolemModuleError } from "../../shared/error/golem-error"; +import { Agreement, ProviderInfo } from "../../market/agreement"; +import { Activity } from "../index"; + +export enum WorkErrorCode { + ServiceNotInitialized = "ServiceNotInitialized", + ScriptExecutionFailed = "ScriptExecutionFailed", + ActivityDestroyingFailed = "ActivityDestroyingFailed", + ActivityResultsFetchingFailed = "ActivityResultsFetchingFailed", + ActivityCreationFailed = "ActivityCreationFailed", + NetworkSetupMissing = "NetworkSetupMissing", + ScriptInitializationFailed = "ScriptInitializationFailed", + ActivityDeploymentFailed = "ActivityDeploymentFailed", + ActivityStatusQueryFailed = "ActivityStatusQueryFailed", + ActivityResetFailed = "ActivityResetFailed", +} +export class GolemWorkError extends GolemModuleError { + #agreement?: Agreement; + #activity?: Activity; + #provider?: ProviderInfo; + constructor( + message: string, + public code: WorkErrorCode, + agreement?: Agreement, + activity?: Activity, + provider?: ProviderInfo, + public previous?: Error, + ) { + super(message, code, previous); + this.#agreement = agreement; + this.#activity = activity; + this.#provider = provider; + } + public getAgreement(): Agreement | undefined { + return this.#agreement; + } + public getActivity(): Activity | undefined { + return this.#activity; + } + public getProvider(): ProviderInfo | undefined { + return this.#provider; + } +} diff --git a/src/activity/exe-unit/exe-unit.test.ts b/src/activity/exe-unit/exe-unit.test.ts new file mode 100644 index 000000000..3d1391d7f --- /dev/null +++ b/src/activity/exe-unit/exe-unit.test.ts @@ -0,0 +1,506 @@ +import { + Activity, + ActivityModule, + ActivityStateEnum, + Agreement, + GolemModuleError, + GolemWorkError, + IActivityApi, + NetworkNode, + StorageProvider, + ExeUnit, + WorkErrorCode, + YagnaExeScriptObserver, +} from "../../index"; +import { _, anyOfClass, anything, imock, instance, mock, reset, verify, when } from "@johanblumenberg/ts-mockito"; +import { + buildExecutorResults, + buildExeScriptErrorResult, + buildExeScriptSuccessResult, +} from "../../../tests/utils/helpers"; +import { StorageProviderDataCallback } from "../../shared/storage/provider"; +import { ActivityApi } from "ya-ts-client"; +import { ExeScriptExecutor } from "../exe-script-executor"; +import { INetworkApi } from "../../network/api"; + +const mockActivityApi = imock(); +const mockNetworkApi = imock(); +const mockActivity = mock(Activity); +const mockExecutor = mock(ExeScriptExecutor); +const mockActivityControl = imock(); +const mockExecObserver = imock(); +const mockStorageProvider = imock(); +const mockAgreement = mock(Agreement); +const mockActivityModule = imock(); + +describe("ExeUnit", () => { + beforeEach(() => { + // Make mocks ready to re-use + reset(mockActivityApi); + reset(mockNetworkApi); + reset(mockActivity); + reset(mockActivityControl); + reset(mockExecObserver); + reset(mockStorageProvider); + reset(mockAgreement); + reset(mockActivityModule); + when(mockActivity.provider).thenReturn({ + id: "test-provider-id", + name: "test-provider-name", + walletAddress: "0xProviderWallet", + }); + when(mockActivityModule.createScriptExecutor(_, _)).thenReturn(instance(mockExecutor)); + when(mockActivity.getState()).thenReturn(ActivityStateEnum.Ready); + when(mockActivityModule.refreshActivity(_)).thenResolve(instance(mockActivity)); + when(mockActivity.agreement).thenReturn(instance(mockAgreement)); + when(mockExecutor.activity).thenReturn(instance(mockActivity)); + }); + + describe("Executing", () => { + it("should execute run command with a single parameter", async () => { + when(mockExecutor.getResultsObservable(_, _, _, _)).thenReturn( + buildExecutorResults([ + { + index: 0, + eventDate: new Date().toISOString(), + result: "Ok", + stdout: "test_result", + isBatchFinished: true, + }, + ]), + ); + + const worker = async (exe: ExeUnit) => exe.run("some_shell_command"); + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule)); + await exe.setup(); + const results = await worker(exe); + expect(results?.stdout).toEqual("test_result"); + }); + + it("should execute run command with multiple parameters", async () => { + when(mockExecutor.getResultsObservable(_, _, _, _)).thenReturn( + buildExecutorResults([ + { + index: 0, + eventDate: new Date().toISOString(), + result: "Ok", + stdout: "test_result_from_ls", + isBatchFinished: true, + }, + ]), + ); + + const worker = async (exe: ExeUnit) => exe.run("/bin/ls", ["-R"]); + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule)); + await exe.setup(); + const results = await worker(exe); + + expect(results?.stdout).toEqual("test_result_from_ls"); + }); + + describe("upload commands", () => { + it("should execute upload file command", async () => { + const worker = async (exe: ExeUnit) => exe.uploadFile("./file.txt", "/golem/file.txt"); + + when(mockExecutor.getResultsObservable(_, _, _, _)).thenReturn( + buildExecutorResults([ + { + index: 0, + eventDate: new Date().toISOString(), + result: "Ok", + stdout: "test_result", + isBatchFinished: true, + }, + ]), + ); + + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule), { + storageProvider: instance(mockStorageProvider), + }); + await exe.setup(); + const results = await worker(exe); + expect(results?.stdout).toEqual("test_result"); + verify(mockStorageProvider.publishFile("./file.txt")).once(); + }); + + it("should execute upload json command", async () => { + const worker = async (exe: ExeUnit) => exe.uploadJson({ test: true }, "/golem/file.txt"); + + when(mockExecutor.getResultsObservable(_, _, _, _)).thenReturn( + buildExecutorResults([ + { + index: 0, + eventDate: new Date().toISOString(), + result: "Ok", + stdout: "test_result", + isBatchFinished: true, + }, + ]), + ); + + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule), { + storageProvider: instance(mockStorageProvider), + }); + await exe.setup(); + const results = await worker(exe); + + expect(results?.stdout).toEqual("test_result"); + verify(mockStorageProvider.publishData(anyOfClass(Uint8Array))).once(); + }); + }); + + describe("download commands", () => { + it("should execute download file command", async () => { + const worker = async (exe: ExeUnit) => exe.downloadFile("/golem/file.txt", "./file.txt"); + + when(mockExecutor.getResultsObservable(_, _, _, _)).thenReturn( + buildExecutorResults([ + { + index: 0, + eventDate: new Date().toISOString(), + result: "Ok", + stdout: "test_result", + isBatchFinished: true, + }, + ]), + ); + + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule), { + storageProvider: instance(mockStorageProvider), + }); + + await exe.setup(); + const results = await worker(exe); + + expect(results?.stdout).toEqual("test_result"); + verify(mockStorageProvider.receiveFile("./file.txt")).once(); + }); + + it("should execute download json command", async () => { + const json = { hello: "World" }; + const jsonStr = JSON.stringify(json); + const encoded = new TextEncoder().encode(jsonStr); + + when(mockExecutor.getResultsObservable(_, _, _, _)).thenReturn( + buildExecutorResults([ + { + index: 0, + eventDate: new Date().toISOString(), + result: "Ok", + data: encoded.buffer, + isBatchFinished: true, + }, + ]), + ); + + when(mockStorageProvider.receiveData(anything())).thenCall(async (onData: StorageProviderDataCallback) => { + onData(encoded); + return "/golem/file.txt"; + }); + + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule), { + storageProvider: instance(mockStorageProvider), + }); + const result = await exe.downloadJson("/golem/file.txt"); + + expect(result.result).toEqual("Ok"); + expect(result.data).toEqual(json); + }); + + it("should execute download data command", async () => { + const data = new Uint8Array(10); + + const eventDate = new Date().toISOString(); + + when(mockStorageProvider.receiveData(anything())).thenResolve(data.toString()); + + when(mockExecutor.getResultsObservable(_, _, _, _)).thenReturn( + buildExecutorResults([ + { + index: 0, + eventDate: eventDate, + result: "Ok", + data: data, + isBatchFinished: true, + }, + ]), + ); + + when(mockStorageProvider.receiveData(anything())).thenCall(async (onData: StorageProviderDataCallback) => { + onData(data); + return "/golem/file.txt"; + }); + + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule), { + storageProvider: instance(mockStorageProvider), + }); + const result = await exe.downloadData("/golem/file.txt"); + + expect(result).toEqual( + expect.objectContaining({ + index: 0, + eventDate: eventDate, + result: "Ok", + data: data, + }), + ); + }); + }); + }); + + describe("Exec and stream", () => { + it("should execute runAndStream command", async () => { + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule)); + when(mockExecutor.getResultsObservable(_, _, _, _)).thenReturn( + buildExecutorResults([ + { + index: 0, + eventDate: new Date().toISOString(), + result: "Ok", + isBatchFinished: true, + }, + ]), + ); + const remote = await exe.runAndStream("rm -rf foo/"); + + const finalResult = await remote.waitForExit(); + expect(finalResult.result).toBe("Ok"); + }); + }); + + describe("transfer()", () => { + it("should execute transfer command", async () => { + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule)); + + when(mockExecutor.getResultsObservable(_, _, _, _)).thenReturn( + buildExecutorResults([ + { + index: 0, + eventDate: new Date().toISOString(), + result: "Ok", + stdout: "Ok", + stderr: "", + isBatchFinished: true, + }, + ]), + ); + + const remote = await exe.transfer("http://golem.network/test.txt", "/golem/work/test.txt"); + expect(remote.result).toEqual("Ok"); + }); + }); + + describe("Batch", () => { + it("should execute batch as promise", async () => { + const worker = async (exe: ExeUnit) => { + return exe + .beginBatch() + .run("some_shell_command") + .uploadFile("./file.txt", "/golem/file.txt") + .uploadJson({ test: true }, "/golem/file.txt") + .downloadFile("/golem/file.txt", "./file.txt") + .end(); + }; + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule), { + storageProvider: instance(mockStorageProvider), + }); + const expectedStdout = [ + { stdout: "ok_run" }, + { stdout: "ok_upload_file" }, + { stdout: "ok_upload_json" }, + { stdout: "ok_download_file" }, + ]; + + when(mockExecutor.getResultsObservable(_)).thenReturn( + buildExecutorResults(expectedStdout.map((e) => buildExeScriptSuccessResult(e.stdout))), + ); + + const results = await worker(exe); + expect(results?.map((r) => r.stdout)).toEqual(expectedStdout.map((s) => s.stdout)); + + verify(mockStorageProvider.publishFile("./file.txt")).once(); + verify(mockStorageProvider.publishData(anyOfClass(Uint8Array))).once(); + verify(mockStorageProvider.receiveFile("./file.txt")).once(); + }); + + it("should execute batch as stream", async () => { + const worker = async (exe: ExeUnit) => { + return exe + .beginBatch() + .run("some_shell_command") + .uploadFile("./file.txt", "/golem/file.txt") + .uploadJson({ test: true }, "/golem/file.txt") + .downloadFile("/golem/file.txt", "./file.txt") + .endStream(); + }; + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule), { + storageProvider: instance(mockStorageProvider), + }); + const expectedStdout = [ + { stdout: "ok_run" }, + { stdout: "ok_upload_file" }, + { stdout: "ok_upload_json" }, + { stdout: "ok_download_file" }, + ]; + + when(mockExecutor.getResultsObservable(_)).thenReturn( + buildExecutorResults(expectedStdout.map((e) => buildExeScriptSuccessResult(e.stdout))), + ); + + const results = await worker(exe); + await new Promise((res, rej) => { + results.subscribe({ + next: (result) => { + try { + expect(result.stdout).toEqual(expectedStdout?.shift()?.stdout); + } catch (e) { + rej(e); + } + }, + complete: () => res(), + }); + }); + + verify(mockStorageProvider.publishFile("./file.txt")).once(); + verify(mockStorageProvider.publishData(anyOfClass(Uint8Array))).once(); + verify(mockStorageProvider.receiveFile("./file.txt")).once(); + }); + }); + + describe("fetchState() helper function", () => { + it("should return activity state", async () => { + when(mockActivity.getState()).thenReturn(ActivityStateEnum.Deployed); + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule)); + await expect(exe["fetchState"]()).resolves.toEqual(ActivityStateEnum.Deployed); + }); + }); + + describe("getIp()", () => { + it("should throw error if there is no network node", async () => { + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule)); + + expect(() => exe.getIp()).toThrow(new Error("There is no network in this exe-unit")); + }); + + it("should return ip address of provider vpn network node", async () => { + const networkNode = new NetworkNode( + "test-node", + "192.168.0.10", + () => ({ + id: "test-network", + ip: "192.168.0.0/24", + nodes: { + "192.168.0.10": "example-provider-id", + }, + mask: "255.255.255.0", + }), + "http://127.0.0.1:7465", + ); + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule), { + networkNode, + }); + + expect(exe.getIp()).toEqual("192.168.0.10"); + }); + }); + + describe("getWebsocketUri()", () => { + it("should throw error if there is no network node", async () => { + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule)); + + expect(() => exe.getWebsocketUri(80)).toThrow(new Error("There is no network in this exe-unit")); + }); + + it("should return websocket URI from the NetworkNode", async () => { + const mockNode = mock(NetworkNode); + + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule), { + networkNode: instance(mockNode), + }); + + when(mockNode.getWebsocketUri(20)).thenReturn("ws://localhost:20"); + + expect(exe.getWebsocketUri(20)).toEqual("ws://localhost:20"); + }); + }); + + describe("uploadData()", () => { + it("should execute upload json command", async () => { + const input = "Hello World"; + + const eventDate = new Date().toISOString(); + + when(mockExecutor.getResultsObservable(_, _, _, _)).thenReturn( + buildExecutorResults([ + { + index: 0, + result: "Ok", + isBatchFinished: true, + eventDate, + }, + ]), + ); + + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule), { + storageProvider: instance(mockStorageProvider), + }); + + const result = await exe.uploadData(new TextEncoder().encode(input), "/golem/file.txt"); + + expect(result).toEqual( + expect.objectContaining({ + index: 0, + result: "Ok", + isBatchFinished: true, + eventDate, + }), + ); + }); + }); + + describe("setupActivity() - called as part of before()", () => { + it("should call setup function", async () => { + const setup = jest.fn(); + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule), { setup }); + await exe.setup(); + expect(setup).toHaveBeenCalled(); + }); + }); + + describe("Error handling", () => { + it("should return a result with error in case the command to execute is invalid", async () => { + const worker = async (exe: ExeUnit) => exe.beginBatch().run("invalid_shell_command").end(); + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule)); + + when(mockExecutor.getResultsObservable(_)).thenReturn( + buildExecutorResults(undefined, [buildExeScriptErrorResult("error", "Some error occurred")]), + ); + const [result] = await worker(exe); + + expect(result.result).toEqual("Error"); + expect(result.message).toEqual("Some error occurred"); + }); + + it("should catch error while executing batch as stream with invalid command", async () => { + const worker = async (exe: ExeUnit) => exe.beginBatch().run("invalid_shell_command").endStream(); + const exe = new ExeUnit(instance(mockActivity), instance(mockActivityModule)); + + when(mockExecutor.getResultsObservable(_)).thenReturn( + buildExecutorResults(undefined, [buildExeScriptErrorResult("error", "Some error occurred", "test_result")]), + ); + + const results = await worker(exe); + + await new Promise((res) => + results.subscribe({ + error: (error: GolemModuleError) => { + expect(error.message).toEqual("Some error occurred. Stdout: test_result. Stderr: error"); + expect(error).toBeInstanceOf(GolemWorkError); + expect(error.code).toEqual(WorkErrorCode.ScriptExecutionFailed); + res(true); + }, + }), + ); + }); + }); +}); diff --git a/src/task/work.ts b/src/activity/exe-unit/exe-unit.ts similarity index 57% rename from src/task/work.ts rename to src/activity/exe-unit/exe-unit.ts index 4745d74df..473488d78 100644 --- a/src/task/work.ts +++ b/src/activity/exe-unit/exe-unit.ts @@ -1,4 +1,4 @@ -import { Activity, ActivityStateEnum, Result, ResultState } from "../activity"; +import { Activity, ActivityModule, ActivityStateEnum, Result } from "../"; import { Capture, Command, @@ -12,154 +12,180 @@ import { UploadData, UploadFile, } from "../script"; -import { NullStorageProvider, StorageProvider } from "../storage"; -import { defaultLogger, Logger, sleep, YagnaOptions } from "../utils"; +import { NullStorageProvider, StorageProvider } from "../../shared/storage"; +import { createAbortSignalFromTimeout, defaultLogger, Logger, sleep, YagnaOptions } from "../../shared/utils"; import { Batch } from "./batch"; -import { NetworkNode } from "../network"; +import { NetworkNode } from "../../network"; import { RemoteProcess } from "./process"; import { GolemWorkError, WorkErrorCode } from "./error"; -import { GolemTimeoutError } from "../error/golem-error"; -import { ProviderInfo } from "../agreement"; -import { TcpProxy } from "../network/tcpProxy"; +import { GolemAbortError, GolemConfigError, GolemTimeoutError } from "../../shared/error/golem-error"; +import { Agreement, ProviderInfo } from "../../market"; +import { TcpProxy } from "../../network/tcpProxy"; +import { ExecutionOptions, ExeScriptExecutor } from "../exe-script-executor"; +import { lastValueFrom, tap, toArray } from "rxjs"; -export type Worker = (ctx: WorkContext) => Promise; +export type LifecycleFunction = (exe: ExeUnit) => Promise; -const DEFAULTS = { - activityPreparingTimeout: 300_000, - activityStateCheckInterval: 1000, -}; - -export interface WorkOptions { - activityPreparingTimeout?: number; - activityStateCheckingInterval?: number; +export interface ExeUnitOptions { + activityDeployingTimeout?: number; storageProvider?: StorageProvider; networkNode?: NetworkNode; logger?: Logger; - activityReadySetupFunctions?: Worker[]; - yagnaOptions: YagnaOptions; + yagnaOptions?: YagnaOptions; + /** this function is called as soon as the exe unit is ready */ + setup?: LifecycleFunction; + /** this function is called before the exe unit is destroyed */ + teardown?: LifecycleFunction; + executionOptions?: ExecutionOptions; + signalOrTimeout?: number | AbortSignal; } export interface CommandOptions { - timeout?: number; + signalOrTimeout?: number | AbortSignal; + maxRetries?: number; env?: object; capture?: Capture; } +export interface ActivityDTO { + provider: ProviderInfo; + id: string; + agreement: Agreement; +} + /** * Groups most common operations that the requestors might need to implement their workflows */ -export class WorkContext { - private readonly activityPreparingTimeout: number; - private readonly activityStateCheckingInterval: number; - +export class ExeUnit { public readonly provider: ProviderInfo; private readonly logger: Logger; private readonly storageProvider: StorageProvider; private readonly networkNode?: NetworkNode; + private executor: ExeScriptExecutor; + private readonly abortSignal: AbortSignal; + constructor( public readonly activity: Activity, - private options: WorkOptions, + public readonly activityModule: ActivityModule, + private options?: ExeUnitOptions, ) { - this.activityPreparingTimeout = options.activityPreparingTimeout || DEFAULTS.activityPreparingTimeout; - this.activityStateCheckingInterval = options.activityStateCheckingInterval || DEFAULTS.activityStateCheckInterval; - - this.logger = options.logger ?? defaultLogger("work"); - this.provider = activity.agreement.getProviderInfo(); - this.storageProvider = options.storageProvider ?? new NullStorageProvider(); + this.logger = options?.logger ?? defaultLogger("work"); + this.provider = activity.provider; + this.storageProvider = options?.storageProvider ?? new NullStorageProvider(); + + this.networkNode = options?.networkNode; + this.abortSignal = createAbortSignalFromTimeout(options?.signalOrTimeout); + this.executor = this.activityModule.createScriptExecutor(this.activity, { + ...this.options?.executionOptions, + signalOrTimeout: this.abortSignal, + }); + } - this.networkNode = options.networkNode; + private async fetchState(): Promise { + if (this.abortSignal.aborted) { + throw new GolemAbortError("ExeUnit has been aborted"); + } + return this.activityModule + .refreshActivity(this.activity) + .then((activity) => activity.getState()) + .catch((err) => { + this.logger.error("Failed to read activity state", err); + throw new GolemWorkError( + "Failed to read activity state", + WorkErrorCode.ActivityStatusQueryFailed, + this.activity.agreement, + this.activity, + err, + ); + }); } - async before(): Promise { - let state = await this.activity - .getState() - .catch((error) => this.logger.warn("Error while getting activity state", { error })); - if (state === ActivityStateEnum.Ready) { + /** + * This function initializes the exe unit by deploying the image to the remote machine + * and preparing and running the environment. + * This process also includes running setup function if the user has defined it + */ + async setup(): Promise { + try { + let state = await this.fetchState(); + if (state === ActivityStateEnum.Ready) { + await this.setupActivity(); + return; + } + + if (state === ActivityStateEnum.Initialized) { + await this.deployActivity(); + } + + await sleep(1000, true); + state = await this.fetchState(); + + if (state !== ActivityStateEnum.Ready) { + throw new GolemWorkError( + `Activity ${this.activity.id} cannot reach the Ready state. Current state: ${state}`, + WorkErrorCode.ActivityDeploymentFailed, + this.activity.agreement, + this.activity, + this.activity.provider, + ); + } await this.setupActivity(); - return; + } catch (error) { + if (this.abortSignal.aborted) { + throw this.abortSignal.reason.name === "TimeoutError" + ? new GolemTimeoutError( + "Initializing of the exe-unit has been aborted due to a timeout", + this.abortSignal.reason, + ) + : new GolemAbortError("Initializing of the exe-unit has been aborted", this.abortSignal.reason); + } + throw error; } - if (state === ActivityStateEnum.Initialized) { - const result = await this.activity - .execute( - new Script([new Deploy(this.networkNode?.getNetworkConfig?.()), new Start()]).getExeScriptRequest(), - undefined, - this.activityPreparingTimeout, - ) - .catch((e) => { - throw new GolemWorkError( - `Unable to deploy activity. ${e}`, - WorkErrorCode.ActivityDeploymentFailed, - this.activity.agreement, - this.activity, - this.activity.getProviderInfo(), - e, - ); - }); - let timeoutId: NodeJS.Timeout; - await Promise.race([ - new Promise( - (res, rej) => - (timeoutId = setTimeout( - () => rej(new GolemTimeoutError("Preparing activity timeout")), - this.activityPreparingTimeout, - )), - ), - (async () => { - for await (const res of result) { - if (res.result === ResultState.Error) - throw new GolemWorkError( - `Preparing activity failed. Error: ${res.message}`, - WorkErrorCode.ActivityDeploymentFailed, - this.activity.agreement, - this.activity, - this.activity.getProviderInfo(), - ); - } - })(), - ]) - .catch((error) => { - if (error instanceof GolemWorkError) { - throw error; - } - throw new GolemWorkError( - `Preparing activity failed. Error: ${error.toString()}`, - WorkErrorCode.ActivityDeploymentFailed, - this.activity.agreement, - this.activity, - this.activity.getProviderInfo(), - error, - ); - }) - .finally(() => clearTimeout(timeoutId)); + } + + /** + * This function starts the teardown function if the user has defined it. + * It is run before the machine is destroyed. + */ + async teardown(): Promise { + if (this.options?.teardown) { + await this.options.teardown(this); } - await sleep(this.activityStateCheckingInterval, true); - state = await this.activity.getState().catch((e) => - this.logger.warn("Error while getting activity state", { - error: e, - provider: this.provider.name, - }), - ); + } - if (state !== ActivityStateEnum.Ready) { + private async deployActivity() { + try { + const executionMetadata = await this.executor.execute( + new Script([new Deploy(this.networkNode?.getNetworkConfig?.()), new Start()]).getExeScriptRequest(), + ); + const result$ = this.executor.getResultsObservable(executionMetadata); + // if any result is an error, throw an error + await lastValueFrom( + result$.pipe( + tap((result) => { + if (result.result === "Error") { + throw new Error(String(result.message)); + } + }), + ), + ); + } catch (error) { throw new GolemWorkError( - `Activity ${this.activity.id} cannot reach the Ready state. Current state: ${state}`, + `Unable to deploy activity. ${error}`, WorkErrorCode.ActivityDeploymentFailed, this.activity.agreement, this.activity, - this.activity.getProviderInfo(), + this.activity.provider, + error, ); } - await this.setupActivity(); } private async setupActivity() { - if (!this.options?.activityReadySetupFunctions) { - return; - } - for (const setupFunction of this.options.activityReadySetupFunctions) { - await setupFunction(this); + if (this.options?.setup) { + await this.options.setup(this); } } @@ -195,19 +221,6 @@ export class WorkContext { return this.runOneCommand(run, runOptions); } - /** @deprecated Use {@link WorkContext.runAndStream} instead */ - async spawn(commandLine: string, options?: Omit): Promise; - /** @deprecated Use {@link WorkContext.runAndStream} instead */ - async spawn(executable: string, args: string[], options?: CommandOptions): Promise; - /** @deprecated Use {@link WorkContext.runAndStream} instead */ - async spawn(exeOrCmd: string, argsOrOptions?: string[] | CommandOptions, options?: CommandOptions) { - if (Array.isArray(argsOrOptions)) { - return this.runAndStream(exeOrCmd, argsOrOptions, options); - } else { - return this.runAndStream(exeOrCmd, options); - } - } - /** * Run an executable on provider and return {@link RemoteProcess} that will allow streaming * that contain stdout and stderr as Readable @@ -238,22 +251,15 @@ export class WorkContext { const script = new Script([run]); // In this case, the script consists only of one run command, // so we skip the execution of script.before and script.after - const streamOfActivityResults = await this.activity - .execute(script.getExeScriptRequest(), true, options?.timeout) - .catch((e) => { - throw new GolemWorkError( - `Script execution failed for command: ${JSON.stringify(run.toJson())}. ${ - e?.response?.data?.message || e?.message || e - }`, - WorkErrorCode.ScriptExecutionFailed, - this.activity.agreement, - this.activity, - this.activity.getProviderInfo(), - e, - ); - }); + const executionMetadata = await this.executor.execute(script.getExeScriptRequest()); + const activityResult$ = this.executor.getResultsObservable( + executionMetadata, + true, + options?.signalOrTimeout, + options?.maxRetries, + ); - return new RemoteProcess(streamOfActivityResults, this.activity); + return new RemoteProcess(this.activityModule, activityResult$, this.activity, this.logger); } /** @@ -295,11 +301,10 @@ export class WorkContext { return this.runOneCommand(new DownloadData(this.storageProvider, src), options); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any async downloadJson(src: string, options?: CommandOptions): Promise { this.logger.debug(`Downloading json`, { src }); const result = await this.downloadData(src, options); - if (result.result !== ResultState.Ok) { + if (result.result !== "Ok") { return new Result({ ...result, data: undefined, @@ -313,7 +318,7 @@ export class WorkContext { } beginBatch() { - return Batch.create(this.activity, this.storageProvider, this.logger); + return new Batch(this.executor, this.storageProvider, this.logger); } /** @@ -324,11 +329,11 @@ export class WorkContext { getWebsocketUri(port: number): string { if (!this.networkNode) throw new GolemWorkError( - "There is no network in this work context", + "There is no network in this exe-unit", WorkErrorCode.NetworkSetupMissing, this.activity.agreement, this.activity, - this.activity.getProviderInfo(), + this.activity.provider, ); return this.networkNode.getWebsocketUri(port); @@ -337,13 +342,13 @@ export class WorkContext { getIp(): string { if (!this.networkNode) throw new GolemWorkError( - "There is no network in this work context", + "There is no network in this exe-unit", WorkErrorCode.NetworkSetupMissing, this.activity.agreement, this.activity, - this.activity.getProviderInfo(), + this.activity.provider, ); - return this.networkNode.ip.toString(); + return this.networkNode.ip; } /** @@ -352,13 +357,21 @@ export class WorkContext { * @param portOnProvider The port that the service running on the provider is listening to */ createTcpProxy(portOnProvider: number) { - return new TcpProxy(this.getWebsocketUri(portOnProvider), this.options.yagnaOptions.apiKey as string, { + if (!this.options?.yagnaOptions?.apiKey) { + throw new GolemConfigError("You need to provide yagna API key to use the TCP Proxy functionality"); + } + + return new TcpProxy(this.getWebsocketUri(portOnProvider), this.options.yagnaOptions.apiKey, { logger: this.logger, }); } - async getState(): Promise { - return this.activity.getState(); + getDto(): ActivityDTO { + return { + provider: this.provider, + id: this.activity.id, + agreement: this.activity.agreement, + }; } private async runOneCommand(command: Command, options?: CommandOptions): Promise> { @@ -372,18 +385,23 @@ export class WorkContext { WorkErrorCode.ScriptInitializationFailed, this.activity.agreement, this.activity, - this.activity.getProviderInfo(), + this.activity.provider, e, ); }); await sleep(100, true); // Send script. - const results = await this.activity.execute(script.getExeScriptRequest(), false, options?.timeout); + const executionMetadata = await this.executor.execute(script.getExeScriptRequest()); + const result$ = this.executor.getResultsObservable( + executionMetadata, + false, + options?.signalOrTimeout, + options?.maxRetries, + ); // Process result. - let allResults: Result[] = []; - for await (const result of results) allResults.push(result); + let allResults: Result[] = await lastValueFrom(result$.pipe(toArray())); allResults = await script.after(allResults); // Handle errors. diff --git a/src/activity/exe-unit/index.ts b/src/activity/exe-unit/index.ts new file mode 100644 index 000000000..41162c872 --- /dev/null +++ b/src/activity/exe-unit/index.ts @@ -0,0 +1,4 @@ +export { ExeUnit, LifecycleFunction, ExeUnitOptions } from "./exe-unit"; +export { Batch } from "./batch"; +export { GolemWorkError, WorkErrorCode } from "./error"; +export { TcpProxy } from "../../network/tcpProxy"; diff --git a/src/activity/exe-unit/process.spec.ts b/src/activity/exe-unit/process.spec.ts new file mode 100644 index 000000000..046903078 --- /dev/null +++ b/src/activity/exe-unit/process.spec.ts @@ -0,0 +1,81 @@ +import { RemoteProcess } from "./process"; +import { imock, instance, mock, reset } from "@johanblumenberg/ts-mockito"; +import { Logger, YagnaApi } from "../../shared/utils"; +import { Agreement } from "../../market/agreement"; +import { Activity, ActivityModule } from "../index"; +import { + buildExecutorResults, + buildExeScriptErrorResult, + buildExeScriptSuccessResult, +} from "../../../tests/utils/helpers"; +import { lastValueFrom, toArray } from "rxjs"; + +const mockYagna = mock(YagnaApi); +const mockAgreement = mock(Agreement); +const mockActivity = mock(Activity); +const mockLogger = imock(); +const mockActivityModule = imock(); +describe("RemoteProcess", () => { + let activity: Activity; + + beforeEach(() => { + reset(mockYagna); + reset(mockAgreement); + reset(mockActivity); + reset(mockLogger); + reset(mockActivityModule); + + activity = instance(mockActivity); + }); + + it("should create remote process", async () => { + const streamOfActivityResults = buildExecutorResults([buildExeScriptSuccessResult("ok")]); + const remoteProcess = new RemoteProcess( + instance(mockActivityModule), + streamOfActivityResults, + activity, + instance(mockLogger), + ); + expect(remoteProcess).toBeDefined(); + }); + + it("should read stdout from remote process", async () => { + const streamOfActivityResults = buildExecutorResults([buildExeScriptSuccessResult("Output")]); + const remoteProcess = new RemoteProcess( + instance(mockActivityModule), + streamOfActivityResults, + activity, + instance(mockLogger), + ); + const allStdout = await lastValueFrom(remoteProcess.stdout.pipe(toArray())); + for (const stdout of allStdout) { + expect(stdout).toEqual("Output"); + } + }); + + it("should read stderr from remote process", async () => { + const streamOfActivityResults = buildExecutorResults(undefined, [buildExeScriptErrorResult("Error", "Error")]); + const remoteProcess = new RemoteProcess( + instance(mockActivityModule), + streamOfActivityResults, + activity, + instance(mockLogger), + ); + const allStderr = await lastValueFrom(remoteProcess.stderr.pipe(toArray())); + for (const stderr of allStderr) { + expect(stderr).toEqual("Error"); + } + }); + + it("should wait for exit", async () => { + const streamOfActivityResults = buildExecutorResults([buildExeScriptSuccessResult("Ok")]); + const remoteProcess = new RemoteProcess( + instance(mockActivityModule), + streamOfActivityResults, + activity, + instance(mockLogger), + ); + const finalResult = await remoteProcess.waitForExit(); + expect(finalResult.result).toEqual("Ok"); + }); +}); diff --git a/src/task/process.ts b/src/activity/exe-unit/process.ts similarity index 56% rename from src/task/process.ts rename to src/activity/exe-unit/process.ts index 316c01cb1..44c69d0a0 100644 --- a/src/task/process.ts +++ b/src/activity/exe-unit/process.ts @@ -1,38 +1,53 @@ -import { Readable, Transform } from "stream"; -import { Activity, Result } from "../activity"; +import { Activity, ActivityModule, Result } from "../index"; import { GolemWorkError, WorkErrorCode } from "./error"; -import { GolemTimeoutError } from "../error/golem-error"; +import { GolemTimeoutError } from "../../shared/error/golem-error"; +import { Logger } from "../../shared/utils"; +import { Observable, Subject, Subscription, finalize } from "rxjs"; const DEFAULTS = { exitWaitingTimeout: 20_000, }; /** - * RemoteProcess class representing the process spawned on the provider by {@link WorkContext.runAndStream} + * RemoteProcess class representing the process spawned on the provider by {@link activity/exe-unit/exeunit.ExeUnit.runAndStream} */ export class RemoteProcess { /** * Stream connected to stdout from provider process */ - readonly stdout: Readable; + readonly stdout: Subject = new Subject(); /** * Stream connected to stderr from provider process */ - readonly stderr: Readable; + readonly stderr: Subject = new Subject(); private lastResult?: Result; private streamError?: Error; + private subscription: Subscription; + constructor( - private streamOfActivityResults: Readable, + private readonly activityModule: ActivityModule, + activityResult$: Observable, private activity: Activity, + private readonly logger: Logger, ) { - this.streamOfActivityResults.on("data", (data) => (this.lastResult = data)); - this.streamOfActivityResults.on("error", (error) => (this.streamError = error)); - const { stdout, stderr } = this.transformResultsStream(); - this.stdout = stdout; - this.stderr = stderr; + this.subscription = activityResult$ + .pipe( + finalize(() => { + this.stdout.complete(); + this.stderr.complete(); + }), + ) + .subscribe({ + next: (result) => { + this.lastResult = result; + if (result.stdout) this.stdout.next(result.stdout); + if (result.stderr) this.stderr.next(result.stderr); + }, + error: (error) => (this.streamError = error), + }); } /** @@ -50,11 +65,13 @@ export class RemoteProcess { WorkErrorCode.ActivityResultsFetchingFailed, this.activity.agreement, this.activity, - this.activity.getProviderInfo(), + this.activity.provider, new GolemTimeoutError(`The waiting time (${timeoutInMs} ms) for the final result has been exceeded`), ), ); - this.activity.stop().catch(); + this.activityModule + .destroyActivity(this.activity) + .catch((err) => this.logger.error(`Error when destroying activity`, err)); }, timeoutInMs); const end = () => { clearTimeout(timeoutId); @@ -67,28 +84,15 @@ export class RemoteProcess { WorkErrorCode.ActivityResultsFetchingFailed, this.activity.agreement, this.activity, - this.activity.getProviderInfo(), + this.activity.provider, ), ); - this.activity.stop().catch(); + this.activityModule + .destroyActivity(this.activity) + .catch((err) => this.logger.error(`Error when destroying activity`, err)); } }; - if (this.streamOfActivityResults.closed) return end(); - this.streamOfActivityResults.on("close", end); + this.subscription.add(() => end()); }); } - - private transformResultsStream(): { stdout: Readable; stderr: Readable } { - const transform = (std: string) => - new Transform({ - objectMode: true, - transform(chunk, encoding, callback) { - callback(null, chunk?.[std]); - }, - }); - return { - stdout: this.streamOfActivityResults.pipe(transform("stdout")), - stderr: this.streamOfActivityResults.pipe(transform("stderr")), - }; - } } diff --git a/src/activity/factory.test.ts b/src/activity/factory.test.ts deleted file mode 100644 index a45156c0d..000000000 --- a/src/activity/factory.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ActivityFactory } from "./factory"; -import { Agreement } from "../agreement"; -import { anything, imock, instance, mock, when } from "@johanblumenberg/ts-mockito"; -import { YagnaApi } from "../utils"; -import { RequestorControlApi } from "ya-ts-client/dist/ya-activity/api"; -import { RequestorApi as RequestorStateApi } from "../utils/yagna/activity"; -import { GolemWorkError, WorkErrorCode } from "../task/error"; - -describe("Activity Factory", () => { - describe("Creating activities", () => { - describe("Negative cases", () => { - it("Correctly passes the exception thrown during activity creation to the user", async () => { - const agreementMock = mock(Agreement); - const yagnaAPi = imock(); - - const controlApi = mock(RequestorControlApi); - const stateApi = mock(RequestorStateApi); - - const components = { - control: instance(controlApi), - state: instance(stateApi), - }; - - when(yagnaAPi.activity).thenReturn(components); - const testError = new Error("Foo"); - when(controlApi.createActivity(anything())).thenReject(testError); - - const agreement = instance(agreementMock); - const factory = new ActivityFactory(agreement, instance(yagnaAPi)); - - await expect(() => factory.create()).rejects.toMatchError( - new GolemWorkError( - "Unable to create activity: Error: Foo", - WorkErrorCode.ActivityCreationFailed, - agreement, - undefined, - agreement.getProviderInfo(), - testError, - ), - ); - }); - }); - }); -}); diff --git a/src/activity/factory.ts b/src/activity/factory.ts deleted file mode 100644 index 25bef0e33..000000000 --- a/src/activity/factory.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Activity, ActivityOptions } from "./activity"; -import { ActivityConfig } from "./config"; -import { Events } from "../events"; -import { YagnaApi } from "../utils"; -import { Agreement } from "../agreement"; -import { GolemWorkError, WorkErrorCode } from "../task/error"; -import { GolemInternalError } from "../error/golem-error"; - -/** - * Activity Factory - * @description Use {@link Activity.create} instead - * @internal - */ -export class ActivityFactory { - private readonly options: ActivityConfig; - - constructor( - private readonly agreement: Agreement, - private readonly yagnaApi: YagnaApi, - options?: ActivityOptions, - ) { - this.options = new ActivityConfig(options); - } - - public async create(secure = false): Promise { - try { - if (secure) { - throw new GolemInternalError("Creating an activity in secure mode is not implemented"); - } - return await this.createActivity(); - } catch (error) { - this.options.logger.error("Unable to create activity", error); - throw new GolemWorkError( - `Unable to create activity: ${error?.response?.data?.message || error}`, - WorkErrorCode.ActivityCreationFailed, - this.agreement, - undefined, - this.agreement.getProviderInfo(), - error, - ); - } - } - - private async createActivity(): Promise { - const { data } = await this.yagnaApi.activity.control.createActivity({ agreementId: this.agreement.id }); - - const id = typeof data == "string" ? data : data.activityId; - - this.options.logger.debug(`Activity created`, { id }); - this.options.eventTarget?.dispatchEvent(new Events.ActivityCreated({ id, agreementId: this.agreement.id })); - - return new Activity(id, this.agreement, this.yagnaApi, this.options); - } -} diff --git a/src/activity/index.ts b/src/activity/index.ts index 4a4026b10..3965651e5 100644 --- a/src/activity/index.ts +++ b/src/activity/index.ts @@ -1,4 +1,6 @@ -export { Activity, ActivityStateEnum, ActivityOptions } from "./activity"; -export { Result, ResultState } from "./results"; -export { ActivityConfig } from "./config"; -export { ActivityPoolService } from "./service"; +export { Activity, ActivityStateEnum } from "./activity"; +export { Result } from "./results"; +export { ExecutionConfig } from "./config"; +export * from "./activity.module"; +export * from "./exe-unit"; +export * from "./api"; diff --git a/src/activity/results.test.ts b/src/activity/results.test.ts index 38261d18c..63171b126 100644 --- a/src/activity/results.test.ts +++ b/src/activity/results.test.ts @@ -1,4 +1,4 @@ -import { Result, ResultState } from "./results"; +import { Result } from "./results"; describe("Results", () => { describe("converting output to JSON", () => { @@ -6,7 +6,7 @@ describe("Results", () => { test("produces JSON when the stdout contains correct data", () => { const result = new Result({ index: 0, - result: ResultState.Ok, + result: "Ok", stdout: '{ "value": 55 }\n', stderr: null, message: null, @@ -24,7 +24,7 @@ describe("Results", () => { test("throws an error when stdout does not contain nice JSON", () => { const result = new Result({ index: 0, - result: ResultState.Ok, + result: "Ok", stdout: "not json\n", stderr: null, message: null, diff --git a/src/activity/results.ts b/src/activity/results.ts index 37b343bb0..bcf38fbfb 100644 --- a/src/activity/results.ts +++ b/src/activity/results.ts @@ -1,7 +1,5 @@ -import { ExeScriptCommandResultResultEnum } from "ya-ts-client/dist/ya-activity/src/models/exe-script-command-result"; - -export import ResultState = ExeScriptCommandResultResultEnum; -import { GolemInternalError } from "../error/golem-error"; +import { ActivityApi } from "ya-ts-client"; +import { GolemInternalError } from "../shared/error/golem-error"; // FIXME: Make the `data` field Uint8Array and update the rest of the code // eslint-disable-next-line @@ -11,7 +9,7 @@ export interface ResultData { /** The datetime of the event on which the result was received */ eventDate: string; /** If is success */ - result: ResultState; + result: ActivityApi.ExeScriptCommandResultDTO["result"]; /** stdout of script command */ stdout?: string | ArrayBuffer | null; /** stderr of script command */ @@ -30,7 +28,7 @@ export interface ResultData { export class Result implements ResultData { index: number; eventDate: string; - result: ResultState; + result: ActivityApi.ExeScriptCommandResultDTO["result"]; stdout?: string | ArrayBuffer | null; stderr?: string | ArrayBuffer | null; message?: string | null; @@ -82,13 +80,13 @@ export interface RuntimeEventKind { finished?: RuntimeEventFinished; } -interface RuntimeEventStarted { +export interface RuntimeEventStarted { command: object; } -interface RuntimeEventFinished { +export interface RuntimeEventFinished { // Reason for disable: That's something what yagna returns from its api // eslint-disable-next-line @typescript-eslint/naming-convention return_code: number; - message: string; + message: string | null; } diff --git a/src/script/command.ts b/src/activity/script/command.ts similarity index 87% rename from src/script/command.ts rename to src/activity/script/command.ts index 3e632a395..012e9592b 100644 --- a/src/script/command.ts +++ b/src/activity/script/command.ts @@ -1,16 +1,16 @@ -import { ExeScriptRequest } from "ya-ts-client/dist/ya-activity/src/models"; -import { StorageProvider } from "../storage"; -import { Result, ResultState } from "../activity"; +import { ActivityApi } from "ya-ts-client"; +import { StorageProvider } from "../../shared/storage"; +import { Result } from "../results"; const EMPTY_ERROR_RESULT = new Result({ - result: ResultState.Error, + result: "Error", eventDate: new Date().toISOString(), index: -1, message: "No result due to error", }); /** - * @hidden + * Generic command that can be send to an exe-unit via yagna's API */ export class Command { protected args: Record; @@ -22,22 +22,26 @@ export class Command { this.args = args || {}; } + /** + * Serializes the command to a JSON representation + */ toJson() { return { [this.commandName]: this.args, }; } - toExeScriptRequest(): ExeScriptRequest { + /** + * Converts the command into + */ + toExeScriptRequest(): ActivityApi.ExeScriptRequestDTO { return { text: JSON.stringify([this.toJson()]) }; } /** * Setup local environment for executing this command. */ - async before() { - // abstract - } + async before() {} /** * Cleanup local setup that was needed for the command to run. @@ -55,18 +59,12 @@ export class Command { } } -/** - * @hidden - */ export class Deploy extends Command { constructor(args?: Record) { super("deploy", args); } } -/** - * @hidden - */ export class Start extends Command { constructor(args?: Record) { super("start", args); @@ -77,16 +75,15 @@ export type Capture = { stdout?: CaptureMode; stderr?: CaptureMode; }; + export type CaptureMode = | { atEnd: { part?: CapturePart; format?: CaptureFormat } } | { stream: { limit?: number; format?: CaptureFormat } }; -type CapturePart = { head: number } | { tail: number } | { headTail: number }; -type CaptureFormat = "string" | "binary"; +export type CapturePart = { head: number } | { tail: number } | { headTail: number }; + +export type CaptureFormat = "string" | "binary"; -/** - * @hidden - */ export class Run extends Command { constructor(cmd: string, args?: string[] | null, env?: object | null, capture?: Capture) { const captureOpt = capture || { @@ -108,9 +105,6 @@ export class Terminate extends Command { } } -/** - * @hidden - */ export class Transfer extends Command { constructor( protected from?: string, @@ -121,9 +115,6 @@ export class Transfer extends Command { } } -/** - * @hidden - */ export class UploadFile extends Transfer { constructor( private storageProvider: StorageProvider, @@ -144,9 +135,6 @@ export class UploadFile extends Transfer { } } -/** - * @category Mid-level - */ export class UploadData extends Transfer { constructor( private storageProvider: StorageProvider, @@ -167,9 +155,6 @@ export class UploadData extends Transfer { } } -/** - * @hidden - */ export class DownloadFile extends Transfer { constructor( private storageProvider: StorageProvider, @@ -190,9 +175,6 @@ export class DownloadFile extends Transfer { } } -/** - * @category Mid-level - */ export class DownloadData extends Transfer { private chunks: Uint8Array[] = []; @@ -213,7 +195,7 @@ export class DownloadData extends Transfer { async after(result: Result): Promise> { await this.storageProvider.release([this.args["to"] as string]); - if (result.result === ResultState.Ok) { + if (result.result === "Ok") { return new Result({ ...result, data: this.combineChunks(), @@ -222,7 +204,7 @@ export class DownloadData extends Transfer { return new Result({ ...result, - result: ResultState.Error, + result: "Error", data: undefined, }); } diff --git a/src/script/index.ts b/src/activity/script/index.ts similarity index 100% rename from src/script/index.ts rename to src/activity/script/index.ts diff --git a/src/script/script.ts b/src/activity/script/script.ts similarity index 61% rename from src/script/script.ts rename to src/activity/script/script.ts index 2b4b95e2c..12e99323c 100644 --- a/src/script/script.ts +++ b/src/activity/script/script.ts @@ -1,10 +1,10 @@ -import { ExeScriptRequest } from "ya-ts-client/dist/ya-activity/src/models"; +import { ActivityApi } from "ya-ts-client"; import { Command } from "./command"; -import { Result } from "../activity"; -import { GolemInternalError } from "../error/golem-error"; +import { Result } from "../index"; +import { GolemInternalError } from "../../shared/error/golem-error"; /** - * @hidden + * Represents a series of Commands that can be sent to exe-unit via yagna's API */ export class Script { constructor(private commands: Command[] = []) {} @@ -26,8 +26,10 @@ export class Script { return Promise.all(this.commands.map((command, i) => command.after(results[i]))); } - getExeScriptRequest(): ExeScriptRequest { - if (!this.commands.length) throw new GolemInternalError("There are no commands in the script"); + getExeScriptRequest(): ActivityApi.ExeScriptRequestDTO { + if (!this.commands.length) { + throw new GolemInternalError("There are no commands in the script"); + } return { text: JSON.stringify(this.commands.map((cmd) => cmd.toJson())) }; } } diff --git a/src/activity/service.test.ts b/src/activity/service.test.ts deleted file mode 100644 index 6b6bfe3e4..000000000 --- a/src/activity/service.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { YagnaMock, agreementPoolServiceMock, paymentServiceMock } from "../../tests/mock"; -import { ActivityPoolService } from "./service"; -import { Activity } from "./activity"; - -const yagnaApi = new YagnaMock().getApi(); -describe("Activity Pool Service", () => { - let activityService: ActivityPoolService; - beforeEach(() => { - activityService = new ActivityPoolService(yagnaApi, agreementPoolServiceMock, paymentServiceMock); - }); - - describe("run()", () => { - it("should start service", async () => { - await activityService.run(); - expect(activityService.isRunning()).toEqual(true); - await activityService.end(); - }); - }); - describe("end()", () => { - it("should stop service", async () => { - await activityService.run(); - await activityService.end(); - expect(activityService.isRunning()).toEqual(false); - }); - }); - describe("getActivity()", () => { - it("should create and return activity", async () => { - await activityService.run(); - const activity = await activityService.getActivity(); - expect(activity).toBeInstanceOf(Activity); - await activityService.end(); - }); - it("should return activity if is available in the pool", async () => { - await activityService.run(); - const activity1 = await activityService.getActivity(); - await activityService.releaseActivity(activity1, { reuse: true }); - const activity2 = await activityService.getActivity(); - expect(activity1).toEqual(activity2); - await activityService.end(); - }); - }); - describe("releaseActivity()", () => { - it("should return activity to the pool if allowReuse flag is true", async () => { - await activityService.run(); - const activity = await activityService.getActivity(); - await activityService.releaseActivity(activity, { reuse: true }); - expect(activityService["pool"]).toContain(activity); - await activityService.end(); - }); - - it("should terminate activity if allowReuse flag is false", async () => { - await activityService.run(); - const activity = await activityService.getActivity(); - const spyAgreementService = jest.spyOn(agreementPoolServiceMock, "releaseAgreement"); - const spyActivity = jest.spyOn(activity, "stop"); - await activityService.releaseActivity(activity, { reuse: false }); - expect(spyActivity).toHaveBeenCalled(); - expect(spyAgreementService).toHaveBeenCalledWith(activity.agreement.id, false); - await activityService.end(); - }); - }); -}); diff --git a/src/activity/service.ts b/src/activity/service.ts deleted file mode 100644 index 5978d135b..000000000 --- a/src/activity/service.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Activity, ActivityOptions } from "./activity"; -import { defaultLogger, Logger, YagnaApi } from "../utils"; -import { AgreementPoolService } from "../agreement"; -import { PaymentService } from "../payment"; -import { GolemWorkError, WorkErrorCode } from "../task/error"; - -interface ActivityServiceOptions extends ActivityOptions {} - -/** - * Activity Pool Service - * A very simple implementation of the Activity Pool Service that allows to retrieve Activity from the pool. - * If the pool is empty, a new activity is created using the agreement provided by AgreementPoolService. - * @hidden - */ -export class ActivityPoolService { - private logger: Logger; - private pool: Activity[] = []; - private runningState = false; - - constructor( - private yagnaApi: YagnaApi, - private agreementService: AgreementPoolService, - private paymentService: PaymentService, - private options?: ActivityServiceOptions, - ) { - this.logger = this.logger = options?.logger || defaultLogger("work"); - } - - /** - * Start ActivityPoolService - */ - async run() { - this.runningState = true; - this.logger.info("Activity Pool Service has started"); - } - - isRunning() { - return this.runningState; - } - - /** - * Get an activity from the pool of available ones or create a new one - */ - async getActivity(): Promise { - if (!this.runningState) { - throw new GolemWorkError( - "Unable to get activity. Activity service is not running", - WorkErrorCode.ServiceNotInitialized, - ); - } - return this.pool.shift() || (await this.createActivity()); - } - - /** - * Release the activity back into the pool or if it is not reusable - * it will be terminated and the agreement will be released - * @param activity - * @param reuse - determines whether the activity can be reused or should be terminated - */ - async releaseActivity(activity: Activity, { reuse } = { reuse: true }) { - if (reuse) { - this.pool.push(activity); - this.logger.debug(`Activity has been released for reuse`, { id: activity.id }); - } else { - await activity.stop().catch((e) => this.logger.error("Error stopping activity", e)); - await this.agreementService.releaseAgreement(activity.agreement.id, false); - this.logger.debug(`Activity has been released and will be terminated`, { id: activity.id }); - } - } - - /** - * Stop the service and terminate all activities from the pool - */ - async end() { - await Promise.all( - this.pool.map((activity) => activity.stop().catch((e) => this.logger.error("Error stopping activity", e))), - ); - this.runningState = false; - this.logger.info("Activity Pool Service has been stopped"); - } - - private async createActivity(): Promise { - const agreement = await this.agreementService.getAgreement(); - this.paymentService.acceptPayments(agreement); - return Activity.create(agreement, this.yagnaApi, this.options); - } -} diff --git a/src/agreement/agreement.ts b/src/agreement/agreement.ts deleted file mode 100644 index 29e51fb75..000000000 --- a/src/agreement/agreement.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { Logger, YagnaApi, YagnaOptions, defaultLogger } from "../utils"; -import { Agreement as AgreementModel } from "ya-ts-client/dist/ya-market/src/models"; -import { AgreementFactory } from "./factory"; -import { AgreementConfig } from "./config"; -import { Events } from "../events"; -import { GolemMarketError, MarketErrorCode } from "../market/error"; -import { Proposal } from "../market"; - -export interface ProviderInfo { - name: string; - id: string; - walletAddress: string; -} - -/** - * @hidden - */ -export enum AgreementStateEnum { - Proposal = "Proposal", - Pending = "Pending", - Cancelled = "Cancelled", - Rejected = "Rejected", - Approved = "Approved", - Expired = "Expired", - Terminated = "Terminated", -} - -/** - * @hidden - */ -export interface AgreementOptions { - /** yagnaOptions */ - yagnaOptions?: YagnaOptions; - /** timeout for create agreement and refresh details in ms */ - agreementRequestTimeout?: number; - /** timeout for wait for provider approval after requestor confirmation in ms */ - agreementWaitingForApprovalTimeout?: number; - /** Logger module */ - logger?: Logger; - /** Event Bus implements EventTarget */ - eventTarget?: EventTarget; -} -/** - * Agreement module - an object representing the contract between the requestor and the provider. - * @hidden - */ -export class Agreement { - private agreementData?: AgreementModel; - private logger: Logger; - - /** - * @param id - agreement ID - * @param proposal - {@link Proposal} - * @param yagnaApi - {@link YagnaApi} - * @param options - {@link AgreementConfig} - * @hidden - */ - constructor( - public readonly id: string, - public readonly proposal: Proposal, - private readonly yagnaApi: YagnaApi, - private readonly options: AgreementConfig, - ) { - this.logger = options.logger || defaultLogger("market"); - } - - /** - * Create agreement for given proposal - * @param proposal - * @param yagnaApi - * @param agreementOptions - {@link AgreementOptions} - * @return Agreement - */ - static async create(proposal: Proposal, yagnaApi: YagnaApi, agreementOptions?: AgreementOptions): Promise { - const factory = new AgreementFactory(yagnaApi, agreementOptions); - return factory.create(proposal); - } - - /** - * Refresh agreement details - */ - async refreshDetails() { - const { data } = await this.yagnaApi.market.getAgreement(this.id, { - timeout: this.options.agreementRequestTimeout, - }); - this.agreementData = data; - } - - /** - * Return agreement state - * @return state - */ - async getState(): Promise { - await this.refreshDetails(); - return this.agreementData!.state; - } - - getProviderInfo(): ProviderInfo { - return this.proposal.provider; - } - - /** - * Confirm agreement and waits for provider approval - * @description Blocking function waits till agreement will be confirmed and approved by provider - * - * @param appSessionId - Optional correlation/session identifier used for querying events - * related to this agreement - */ - async confirm(appSessionId?: string) { - try { - await this.yagnaApi.market.confirmAgreement(this.id, appSessionId); - await this.yagnaApi.market.waitForApproval(this.id, this.options.agreementWaitingForApprovalTimeout); - this.logger.debug(`Agreement approved`, { id: this.id }); - this.options.eventTarget?.dispatchEvent( - new Events.AgreementConfirmed({ id: this.id, provider: this.getProviderInfo() }), - ); - } catch (error) { - this.logger.error(`Unable to confirm agreement with provider`, { - providerName: this.getProviderInfo().name, - error, - }); - this.options.eventTarget?.dispatchEvent( - new Events.AgreementRejected({ id: this.id, provider: this.getProviderInfo(), reason: error.toString() }), - ); - throw error; - } - } - - /** - * Returns flag if the agreement is in the final state - * @description if the final state is true, agreement will not change state further anymore - * @return boolean - */ - async isFinalState(): Promise { - const state = await this.getState(); - return state !== AgreementStateEnum.Pending && state !== AgreementStateEnum.Proposal; - } - - /** - * Terminate agreement - * @description Blocking function waits till agreement will be terminated - * @throws Error if the agreement will be unable to terminate - */ - async terminate(reason: { [key: string]: string } = { message: "Finished" }) { - try { - if ((await this.getState()) !== AgreementStateEnum.Terminated) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore TODO: API binding BUG with reason type - await this.yagnaApi.market.terminateAgreement(this.id, reason, { - timeout: this.options.agreementRequestTimeout, - }); - this.options.eventTarget?.dispatchEvent( - new Events.AgreementTerminated({ id: this.id, provider: this.getProviderInfo(), reason: reason.message }), - ); - this.logger.debug(`Agreement terminated`, { id: this.id }); - } catch (error) { - throw new GolemMarketError( - `Unable to terminate agreement ${this.id}. ${error.response?.data?.message || error.response?.data || error}`, - MarketErrorCode.AgreementTerminationFailed, - this.proposal.demand, - ); - } - } -} diff --git a/src/agreement/config.ts b/src/agreement/config.ts deleted file mode 100644 index 0492df06f..000000000 --- a/src/agreement/config.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { AgreementOptions } from "./agreement"; -import { AgreementSelector, AgreementServiceOptions } from "./service"; -import { Logger } from "../utils"; -import { randomAgreementSelectorWithPriorityForExistingOnes } from "./strategy"; - -const DEFAULTS = { - agreementRequestTimeout: 30000, - agreementWaitingForApprovalTimeout: 60, - agreementSelector: randomAgreementSelectorWithPriorityForExistingOnes(), - agreementMaxEvents: 100, - agreementEventsFetchingIntervalSec: 5, - agreementMaxPoolSize: 5, -}; - -/** - * @internal - */ -export class AgreementConfig { - readonly agreementRequestTimeout: number; - readonly agreementWaitingForApprovalTimeout: number; - readonly logger?: Logger; - readonly eventTarget?: EventTarget; - - constructor(public readonly options?: AgreementOptions) { - this.agreementRequestTimeout = options?.agreementRequestTimeout || DEFAULTS.agreementRequestTimeout; - this.agreementWaitingForApprovalTimeout = - options?.agreementWaitingForApprovalTimeout || DEFAULTS.agreementWaitingForApprovalTimeout; - this.logger = options?.logger; - this.eventTarget = options?.eventTarget; - } -} - -/** - * @internal - */ -export class AgreementServiceConfig extends AgreementConfig { - readonly agreementSelector: AgreementSelector; - readonly agreementMaxEvents: number; - readonly agreementMaxPoolSize: number; - readonly agreementEventsFetchingIntervalSec: number; - - constructor(options?: AgreementServiceOptions) { - super(options); - this.agreementSelector = options?.agreementSelector ?? DEFAULTS.agreementSelector; - this.agreementMaxEvents = options?.agreementMaxEvents ?? DEFAULTS.agreementMaxEvents; - this.agreementMaxPoolSize = options?.agreementMaxPoolSize ?? DEFAULTS.agreementMaxPoolSize; - this.agreementEventsFetchingIntervalSec = - options?.agreementEventsFetchingIntervalSec ?? DEFAULTS.agreementEventsFetchingIntervalSec; - } -} diff --git a/src/agreement/factory.ts b/src/agreement/factory.ts deleted file mode 100644 index a4c683151..000000000 --- a/src/agreement/factory.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Agreement, AgreementOptions } from "./agreement"; -import { Logger, defaultLogger, YagnaApi } from "../utils"; -import { AgreementConfig } from "./config"; -import { Events } from "../events"; -import { Proposal, GolemMarketError, MarketErrorCode } from "../market"; - -/** - * AgreementFactory - * @description Use {@link Agreement.create} instead - * @internal - */ -export class AgreementFactory { - private readonly logger: Logger; - private readonly options: AgreementConfig; - - /** - * Create AgreementFactory - * @param yagnaApi - {@link YagnaApi} - * @param agreementOptions - {@link AgreementOptions} - */ - constructor( - private readonly yagnaApi: YagnaApi, - agreementOptions?: AgreementOptions, - ) { - this.options = new AgreementConfig(agreementOptions); - this.logger = agreementOptions?.logger || defaultLogger("market"); - } - - /** - * Create Agreement for given proposal ID - * - * @return Agreement - */ - async create(proposal: Proposal): Promise { - try { - const agreementProposalRequest = { - proposalId: proposal.id, - validTo: new Date(+new Date() + 3600 * 1000).toISOString(), - }; - const { data: agreementId } = await this.yagnaApi.market.createAgreement(agreementProposalRequest, { - timeout: this.options.agreementRequestTimeout, - }); - const { data } = await this.yagnaApi.market.getAgreement(agreementId); - const agreement = new Agreement(agreementId, proposal, this.yagnaApi, this.options); - this.options.eventTarget?.dispatchEvent( - new Events.AgreementCreated({ - id: agreementId, - provider: proposal.provider, - validTo: data?.validTo, - proposalId: proposal.id, - }), - ); - this.logger.debug(`Agreement created`, { id: agreementId }); - return agreement; - } catch (error) { - throw new GolemMarketError( - `Unable to create agreement ${error?.response?.data?.message || error?.response?.data || error}`, - MarketErrorCode.AgreementCreationFailed, - proposal.demand, - error, - ); - } - } -} diff --git a/src/agreement/index.ts b/src/agreement/index.ts deleted file mode 100644 index f1acb1143..000000000 --- a/src/agreement/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { Agreement, AgreementOptions, AgreementStateEnum, ProviderInfo } from "./agreement"; -export { AgreementPoolService, AgreementCandidate, AgreementSelector, AgreementServiceOptions } from "./service"; -export { AgreementFactory } from "./factory"; -export { AgreementConfig } from "./config"; -export * as AgreementSelectors from "./strategy"; diff --git a/src/agreement/service.ts b/src/agreement/service.ts deleted file mode 100644 index ece9c2911..000000000 --- a/src/agreement/service.ts +++ /dev/null @@ -1,276 +0,0 @@ -import Bottleneck from "bottleneck"; -import { Logger, YagnaApi, defaultLogger, sleep } from "../utils"; -import { Agreement, AgreementOptions, AgreementStateEnum } from "./agreement"; -import { AgreementServiceConfig } from "./config"; -import { GolemMarketError, MarketErrorCode, Proposal } from "../market"; -import { AgreementEvent, AgreementTerminatedEvent } from "ya-ts-client/dist/ya-market"; - -export interface AgreementDTO { - id: string; - provider: { id: string; name: string }; -} - -export class AgreementCandidate { - agreement?: AgreementDTO; - constructor(readonly proposal: Proposal) {} -} - -export type AgreementSelector = (candidates: AgreementCandidate[]) => Promise; - -export interface AgreementServiceOptions extends AgreementOptions { - /** The selector used when choosing a provider from a pool of existing offers (from the market or already used before) */ - agreementSelector?: AgreementSelector; - /** The maximum number of events fetched in one request call */ - agreementMaxEvents?: number; - /** interval for fetching agreement events */ - agreementEventsFetchingIntervalSec?: number; - /** The maximum number of agreements stored in the pool */ - agreementMaxPoolSize?: number; -} - -/** - * Agreement Pool Service - * @description Service used in {@link TaskExecutor} - * @hidden - */ -export class AgreementPoolService { - private logger: Logger; - private config: AgreementServiceConfig; - private pool = new Set(); - private candidateMap = new Map(); - private agreements = new Map(); - private isServiceRunning = false; - private limiter: Bottleneck; - - constructor( - private readonly yagnaApi: YagnaApi, - agreementServiceOptions?: AgreementServiceOptions, - ) { - this.config = new AgreementServiceConfig(agreementServiceOptions); - this.logger = agreementServiceOptions?.logger || defaultLogger("agreement"); - this.limiter = new Bottleneck({ - maxConcurrent: 1, - }); - } - - /** - * Start AgreementService - */ - async run() { - this.isServiceRunning = true; - this.subscribeForAgreementEvents().catch((e) => this.logger.warn("Unable to subscribe for agreement events", e)); - this.logger.info("Agreement Pool Service has started"); - } - - /** - * Add proposal for create agreement purposes - * @param proposal Proposal - */ - async addProposal(proposal: Proposal) { - this.logger.debug(`New proposal added to pool`, { providerName: proposal.provider.name }); - this.pool.add(new AgreementCandidate(proposal)); - } - - /** - * Release or terminate agreement by ID - * - * @param agreementId Agreement Id - * @param allowReuse if false, terminate and remove from pool, if true, back to pool for further reuse - */ - async releaseAgreement(agreementId: string, allowReuse: boolean) { - const agreementsInPool = Array.from(this.pool).filter((a) => a.agreement); - const isPoolFull = agreementsInPool.length >= this.config.agreementMaxPoolSize; - if (allowReuse && isPoolFull) { - this.logger.debug(`Agreement cannot return to the pool because the pool is already full`, { - id: agreementId, - }); - } - if (allowReuse && !isPoolFull) { - const candidate = this.candidateMap.get(agreementId); - if (candidate) { - this.pool.add(candidate); - this.logger.debug(`Agreement has been released for reuse`, { id: agreementId }); - return; - } else { - this.logger.debug(`Agreement not found in the pool`, { id: agreementId }); - } - } else { - const agreement = this.agreements.get(agreementId); - if (!agreement) { - this.logger.debug(`Agreement not found in the pool`, { id: agreementId }); - return; - } - this.logger.debug(`Agreement has been released and will be terminated`, { id: agreementId }); - try { - this.removeAgreementFromPool(agreement); - await agreement.terminate(); - } catch (e) { - this.logger.warn(`Unable to terminate agreement`, { id: agreementId, error: e }); - } - } - } - - /** - * Get agreement ready for use - * @description Return available agreement from pool, or create a new one - * @return Agreement - */ - async getAgreement(): Promise { - let agreement: Agreement | undefined; - while (!agreement && this.isServiceRunning) { - agreement = await this.getAgreementFormPool(); - if (!agreement) { - await sleep(2); - } - } - if (!agreement || !this.isServiceRunning) { - throw new GolemMarketError( - "Unable to get agreement. Agreement service is not running", - MarketErrorCode.ServiceNotInitialized, - agreement?.proposal?.demand, - ); - } - return agreement; - } - - private async getAgreementFormPool(): Promise { - // Limit concurrency to 1 - const candidate = await this.limiter.schedule(async () => { - if (this.pool.size === 0) return; - const candidates = Array.from(this.pool); - const bestCandidate = await this.config.agreementSelector(candidates); - this.pool.delete(bestCandidate); - return bestCandidate; - }); - - // If candidate is not present, return empty - if (!candidate) { - return; - } - - // If agreement is created return agreement - if (candidate?.agreement?.id) { - return this.agreements.get(candidate?.agreement?.id); - } - - // If agreement is not created, then create agreement and return new agreement - if (candidate && !candidate?.agreement) { - return await this.createAgreement(candidate); - } - } - - /** - * Stop the service - */ - async end() { - this.isServiceRunning = false; - await this.terminateAll({ message: "All computations done" }); - this.logger.info("Agreement Pool Service has been stopped"); - } - - /** - * Terminate all agreements - * @param reason - */ - async terminateAll(reason?: { [key: string]: string }) { - const agreementsToTerminate = Array.from(this.candidateMap) - .map(([agreementId]) => this.agreements.get(agreementId)) - .filter((a) => a !== undefined) as Agreement[]; - this.logger.debug(`Trying to terminate all agreements....`, { size: agreementsToTerminate.length }); - await Promise.all( - agreementsToTerminate.map((agreement) => - agreement - .terminate(reason) - .catch((e) => this.logger.warn(`Agreement cannot be terminated.`, { id: agreement.id, error: e })), - ), - ); - } - - async createAgreement(candidate: AgreementCandidate) { - try { - let agreement = await Agreement.create(candidate.proposal, this.yagnaApi, this.config.options); - agreement = await this.waitForAgreementApproval(agreement); - const state = await agreement.getState(); - - if (state !== AgreementStateEnum.Approved) { - throw new GolemMarketError( - `Agreement ${agreement.id} cannot be approved. Current state: ${state}`, - MarketErrorCode.AgreementApprovalFailed, - agreement.proposal.demand, - ); - } - this.logger.info(`Agreement confirmed by provider`, { providerName: agreement.getProviderInfo().name }); - - this.agreements.set(agreement.id, agreement); - - candidate.agreement = { - id: agreement.id, - provider: agreement.getProviderInfo(), - }; - - this.candidateMap.set(agreement.id, candidate); - - return agreement; - } catch (e) { - this.logger.debug(`Unable to create agreement form available proposal`, e); - await sleep(2); - return; - } - } - - private async waitForAgreementApproval(agreement: Agreement) { - const state = await agreement.getState(); - - if (state === AgreementStateEnum.Proposal) { - await agreement.confirm(this.yagnaApi.appSessionId); - this.logger.debug(`Agreement proposed to provider`, { providerName: agreement.getProviderInfo().name }); - } - - await this.yagnaApi.market.waitForApproval(agreement.id, this.config.agreementWaitingForApprovalTimeout); - return agreement; - } - - private async subscribeForAgreementEvents() { - let afterTimestamp: string | undefined; - while (this.isServiceRunning) { - try { - // @ts-expect-error Bug in ts-client typing - const { data: events }: { data: Array } = - await this.yagnaApi.market.collectAgreementEvents( - this.config.agreementEventsFetchingIntervalSec, - afterTimestamp, - this.config.agreementMaxEvents, - this.yagnaApi.appSessionId, - ); - events.forEach((event) => { - afterTimestamp = event.eventDate; - // @ts-expect-error: Bug in yagna 0.14. Fixed in 0.15. FIXME: remove once we bump MIN_SUPPORTED_YAGNA - const eventType = event.eventType || event.eventtype; - if (eventType === "AgreementTerminatedEvent") { - this.handleTerminationAgreementEvent(event.agreementId, event.reason); - } - }); - } catch (error) { - this.logger.debug(`Unable to get agreement events.`, error); - await sleep(2); - } - } - } - - private async handleTerminationAgreementEvent(agreementId: string, reason?: { [key: string]: string }) { - const agreement = this.agreements.get(agreementId); - if (agreement) { - await agreement.terminate(reason); - this.removeAgreementFromPool(agreement); - } - } - - private removeAgreementFromPool(agreement: Agreement) { - this.agreements.delete(agreement.id); - const candidate = this.candidateMap.get(agreement.id); - if (candidate) { - this.pool.delete(candidate); - this.candidateMap.delete(agreement.id); - } - } -} diff --git a/src/agreement/strategy.ts b/src/agreement/strategy.ts deleted file mode 100644 index a7a19cf5e..000000000 --- a/src/agreement/strategy.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { AgreementCandidate } from "./service"; - -/** Default selector that selects a random provider from the pool */ -export const randomAgreementSelector = () => async (candidates: AgreementCandidate[]) => - candidates[Math.floor(Math.random() * candidates.length)]; - -/** Selector selecting a random provider from the pool, but giving priority to those who already have a confirmed agreement and deployed activity */ -export const randomAgreementSelectorWithPriorityForExistingOnes = () => async (candidates: AgreementCandidate[]) => { - const existingAgreements = candidates.filter((c) => c.agreement); - return existingAgreements.length - ? existingAgreements[Math.floor(Math.random() * existingAgreements.length)] - : candidates[Math.floor(Math.random() * candidates.length)]; -}; - -/** Selector selecting the provider according to the provided list of scores */ -export const bestAgreementSelector = - (scores: { [providerId: string]: number }) => async (candidates: AgreementCandidate[]) => { - candidates.sort((a, b) => - (scores?.[a.proposal.provider.id] || 0) >= (scores?.[b.proposal.provider.id] || 0) ? 1 : -1, - ); - return candidates[0]; - }; diff --git a/src/events/events.ts b/src/events/events.ts deleted file mode 100644 index fcb0f304a..000000000 --- a/src/events/events.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { ProposalDetails } from "../market"; -import { PackageDetails } from "../package/package"; -import { DemandDetails } from "../market/demand"; - -import { RequireAtLeastOne } from "../utils/types"; -import { ProviderInfo } from "../agreement"; -/** - * Global Event Type with which all API events will be emitted. It should be used on all listeners that would like to handle events. - */ -export const EVENT_TYPE = "GolemEvent"; - -// Temporary polyfill -// It is now implemented natively only for nodejs 19 and newest browsers -// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent -// https://github.com/nodejs/node/issues/40678 -class CustomEvent extends Event { - readonly detail: DataType; - readonly name: string; - readonly timestamp: number; - constructor(type: string, data: { detail: DataType } & EventInit) { - super(type, data); - this.detail = data.detail; - this.name = this.constructor.name; - this.timestamp = new Date().valueOf(); - } -} - -export abstract class BaseEvent extends CustomEvent { - constructor(data: DataType) { - super(EVENT_TYPE, { detail: data }); - } -} - -export class ComputationStarted extends BaseEvent { - constructor() { - super(undefined); - } -} -export class ComputationFinished extends BaseEvent { - constructor() { - super(undefined); - } -} -export class ComputationFailed extends BaseEvent<{ reason?: string }> {} -export class TaskStarted extends BaseEvent<{ - id: string; - agreementId: string; - activityId: string; - provider: ProviderInfo; -}> {} - -/** - * Represents the situation in which running the task failed for some reason, but it will be retried - */ -export class TaskRedone extends BaseEvent<{ - id: string; - agreementId?: string; - provider?: ProviderInfo; - retriesCount: number; - /** - * The activity that was involved - * - * This might be not set when there was an issue with starting the activity on the provider - */ - activityId?: string; - reason?: string; -}> {} - -/** - * Represents the situation where all attempts to execute the task have been unsuccessful and no further processing - * will be conducted. - */ -export class TaskRejected extends BaseEvent<{ - id: string; - agreementId?: string; - - provider?: ProviderInfo; - /** - * The activity that was involved when the rejection took place - * - * This might be not set when there was an issue with starting the activity on the provider - */ - activityId?: string; - reason?: string; -}> {} -export class TaskFinished extends BaseEvent<{ id: string }> {} -export class AllocationCreated extends BaseEvent<{ id: string; amount: number; platform?: string }> {} -export class DemandSubscribed extends BaseEvent<{ id: string; details: DemandDetails }> {} -export class DemandFailed extends BaseEvent<{ reason?: string }> {} -export class DemandUnsubscribed extends BaseEvent<{ id: string }> {} -export class CollectFailed extends BaseEvent<{ id: string; reason?: string }> {} -export class ProposalReceived extends BaseEvent<{ - id: string; - provider: ProviderInfo; - parentId: string | null; - details: ProposalDetails; -}> {} -export class ProposalRejected extends BaseEvent<{ - id: string; - reason?: string; - provider?: ProviderInfo; - parentId: string | null; -}> {} -export class ProposalResponded extends BaseEvent<{ - id: string; - provider: ProviderInfo; - counteringProposalId: string; -}> {} -export class ProposalFailed extends BaseEvent<{ - id: string; - provider: ProviderInfo; - parentId: string | null; - reason?: string; -}> {} -export class ProposalConfirmed extends BaseEvent<{ id: string; provider: ProviderInfo }> {} -export class PackageCreated extends BaseEvent<{ - packageReference: RequireAtLeastOne<{ - imageHash: string; - imageTag: string; - manifest: string; - }>; - details: PackageDetails; -}> {} -export class AgreementCreated extends BaseEvent<{ - id: string; - provider: ProviderInfo; - proposalId: string; - validTo?: string; -}> {} -export class AgreementConfirmed extends BaseEvent<{ id: string; provider: ProviderInfo }> {} -export class AgreementRejected extends BaseEvent<{ id: string; provider: ProviderInfo; reason?: string }> {} -export class AgreementTerminated extends BaseEvent<{ id: string; provider: ProviderInfo; reason?: string }> {} -export class InvoiceReceived extends BaseEvent<{ - id: string; - provider: ProviderInfo; - agreementId: string; - /** @deprecated this field may store invalid values for big numbers. Use `amountPrecise` instead **/ - amount: number; - amountPrecise: string; -}> {} -export class DebitNoteReceived extends BaseEvent<{ - id: string; - agreementId: string; - activityId: string; - /** @deprecated this field may store invalid values for big numbers. Use `amountPrecise` instead **/ - amount: number; - amountPrecise: string; - provider: ProviderInfo; -}> {} -export class PaymentAccepted extends BaseEvent<{ - id: string; - agreementId: string; - /** @deprecated this field may store invalid values for big numbers. Use `amountPrecise` instead **/ - amount: number; - amountPrecise: string; - provider: ProviderInfo; -}> {} -export class DebitNoteAccepted extends BaseEvent<{ - id: string; - agreementId: string; - /** @deprecated this field may store invalid values for big numbers. Use `amountPrecise` instead **/ - amount: number; - amountPrecise: string; - provider: ProviderInfo; -}> {} -export class PaymentFailed extends BaseEvent<{ id: string; agreementId: string; reason?: string }> {} -export class ActivityCreated extends BaseEvent<{ id: string; agreementId: string }> {} -export class ActivityDestroyed extends BaseEvent<{ id: string; agreementId: string }> {} -export class ActivityStateChanged extends BaseEvent<{ id: string; state: string }> {} -export class ScriptSent extends BaseEvent<{ activityId: string; agreementId: string }> {} -export class ScriptExecuted extends BaseEvent<{ activityId: string; agreementId: string; success: boolean }> {} diff --git a/src/events/index.ts b/src/events/index.ts deleted file mode 100644 index 63d855708..000000000 --- a/src/events/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * as Events from "./events"; -export { EVENT_TYPE, BaseEvent } from "./events"; diff --git a/src/executor/config.ts b/src/executor/config.ts deleted file mode 100644 index cb29d7433..000000000 --- a/src/executor/config.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { ExecutorOptions } from "./executor"; -import { Package, PackageOptions } from "../package"; -import { ActivityOptions } from "../activity"; -import { GolemConfigError } from "../error/golem-error"; -import { Logger, runtimeContextChecker, defaultLogger, nullLogger } from "../utils"; - -const DEFAULTS = Object.freeze({ - payment: { driver: "erc20", network: "holesky" }, - budget: 1.0, - subnetTag: "public", - basePath: "http://127.0.0.1:7465", - maxParallelTasks: 5, - taskTimeout: 1000 * 60 * 5, // 5 min, - maxTaskRetries: 3, - enableLogging: true, - startupTimeout: 1000 * 90, // 90 sec - exitOnNoProposals: false, -}); - -/** - * @internal - */ -export class ExecutorConfig { - readonly package?: Package | string; - readonly maxParallelTasks: number; - readonly taskTimeout: number; - readonly budget: number; - readonly subnetTag: string; - readonly networkIp?: string; - readonly packageOptions: Omit; - readonly yagnaOptions: { apiKey: string; basePath: string }; - readonly logger: Logger; - readonly eventTarget: EventTarget; - readonly maxTaskRetries: number; - readonly startupTimeout: number; - readonly exitOnNoProposals: boolean; - readonly agreementMaxPoolSize: number; - - constructor(options: ExecutorOptions & ActivityOptions) { - const processEnv = !runtimeContextChecker.isBrowser - ? process - : { - env: { - YAGNA_APPKEY: null, - YAGNA_API_URL: null, - YAGNA_SUBNET: null, - }, - }; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore FIXME: this weirdness may not be needed anymore? - Object.keys(options).forEach((key) => (this[key] = options[key])); - const apiKey = options?.yagnaOptions?.apiKey || processEnv.env.YAGNA_APPKEY; - if (!apiKey) { - throw new GolemConfigError("Api key not defined"); - } - if (options.maxTaskRetries && options.maxTaskRetries < 0) { - throw new GolemConfigError("The maxTaskRetries parameter cannot be less than zero"); - } - this.yagnaOptions = { - apiKey, - basePath: options.yagnaOptions?.basePath || processEnv.env.YAGNA_API_URL || DEFAULTS.basePath, - }; - this.package = options.package; - this.packageOptions = { - engine: options.engine, - minMemGib: options.minMemGib, - minStorageGib: options.minStorageGib, - minCpuThreads: options.minCpuThreads, - minCpuCores: options.minCpuCores, - capabilities: options.capabilities, - manifest: options.manifest, - manifestSig: options.manifestSig, - manifestSigAlgorithm: options.manifestSigAlgorithm, - manifestCert: options.manifestCert, - }; - this.budget = options.budget || DEFAULTS.budget; - this.maxParallelTasks = options.maxParallelTasks || DEFAULTS.maxParallelTasks; - this.taskTimeout = options.taskTimeout || DEFAULTS.taskTimeout; - this.subnetTag = options.subnetTag || processEnv.env?.YAGNA_SUBNET || DEFAULTS.subnetTag; - this.networkIp = options.networkIp; - this.logger = (() => { - const isLoggingEnabled = options.enableLogging ?? DEFAULTS.enableLogging; - if (!isLoggingEnabled) return nullLogger(); - if (options.logger) return options.logger.child("task-executor"); - return defaultLogger("task-executor"); - })(); - this.eventTarget = options.eventTarget || new EventTarget(); - this.maxTaskRetries = options.maxTaskRetries ?? DEFAULTS.maxTaskRetries; - this.startupTimeout = options.startupTimeout ?? DEFAULTS.startupTimeout; - this.exitOnNoProposals = options.exitOnNoProposals ?? DEFAULTS.exitOnNoProposals; - /** - * If the user does not explicitly specify the maximum size of the aggregate pool, the value of maxParallelTask will be set. - * This means that the pool will contain a maximum number of agreements ready for reuse equal to the maximum number of tasks executed simultaneously. - * This will avoid the situation of keeping unused agreements and activities and, consequently, unnecessary costs. - */ - this.agreementMaxPoolSize = options.agreementMaxPoolSize ?? DEFAULTS.maxParallelTasks; - } -} diff --git a/src/executor/events.ts b/src/executor/events.ts deleted file mode 100644 index b8166aded..000000000 --- a/src/executor/events.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * This interface describes events emitted by `TaskExecutor` through `TaskExecutor.events` object. - */ -export interface TaskExecutorEventsDict { - /** - * Fires when task executor is initialized and ready to be used. - */ - ready: () => void; - - /** - * Fires when task executor is about to shut down, immediately after TaskExecutor.shutdown() is called. - * - */ - beforeEnd: () => void; - - /** - * Fires when task executor is completely terminated. - */ - end: () => void; -} diff --git a/src/executor/executor.spec.ts b/src/executor/executor.spec.ts deleted file mode 100644 index e0ada58d1..000000000 --- a/src/executor/executor.spec.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { MarketService } from "../market/"; -import { AgreementPoolService } from "../agreement/"; -import { Task, TaskService } from "../task/"; -import { TaskExecutor } from "./executor"; -import { sleep } from "../utils"; -import { LoggerMock } from "../../tests/mock"; -import { GolemConfigError } from "../error/golem-error"; -import { GolemWorkError, WorkErrorCode } from "../task/error"; - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -jest.mock("../market/service"); -jest.mock("../agreement/service"); -jest.mock("../network/service"); -jest.mock("../task/service"); -jest.mock("../storage/gftp"); -jest.mock("../utils/yagna/yagna"); -jest.mock("../task/task"); - -const serviceRunSpy = jest.fn().mockImplementation(() => Promise.resolve()); -jest.spyOn(MarketService.prototype, "run").mockImplementation(serviceRunSpy); -jest.spyOn(AgreementPoolService.prototype, "run").mockImplementation(serviceRunSpy); -jest.spyOn(TaskService.prototype, "run").mockImplementation(serviceRunSpy); - -jest.mock("../payment/service", () => { - return { - PaymentService: jest.fn().mockImplementation(() => { - return { - config: { payment: { network: "test" } }, - createAllocation: jest.fn(), - run: serviceRunSpy, - end: jest.fn(), - events: { - on: jest.fn(), - }, - }; - }), - }; -}); - -describe("Task Executor", () => { - const logger = new LoggerMock(); - const yagnaOptions = { apiKey: "test" }; - beforeEach(() => { - jest.clearAllMocks(); - logger.clear(); - }); - - describe("init()", () => { - it("should run all set services", async () => { - const executor = await TaskExecutor.create({ package: "test", logger, yagnaOptions }); - expect(serviceRunSpy).toHaveBeenCalledTimes(4); - expect(executor).toBeDefined(); - await executor.shutdown(); - }); - it("should handle a critical error if startup timeout is reached and exitOnNoProposals is enabled", async () => { - const executor = await TaskExecutor.create({ - package: "test", - startupTimeout: 0, - exitOnNoProposals: true, - logger, - yagnaOptions, - }); - jest - .spyOn(MarketService.prototype, "getProposalsCount") - .mockImplementation(() => ({ confirmed: 0, initial: 0, rejected: 0 })); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const handleErrorSpy = jest.spyOn(executor as any, "handleCriticalError").mockImplementation((error) => { - expect((error as Error).message).toEqual( - "Could not start any work on Golem. Processed 0 initial proposals from yagna, filters accepted 0. Check your demand if it's not too restrictive or restart yagna.", - ); - }); - await sleep(10, true); - expect(handleErrorSpy).toHaveBeenCalled(); - await executor.shutdown(); - }); - it("should only warn the user if startup timeout is reached and exitOnNoProposals is disabled", async () => { - const executor = await TaskExecutor.create({ - package: "test", - startupTimeout: 0, - exitOnNoProposals: false, - logger, - yagnaOptions, - }); - jest - .spyOn(MarketService.prototype, "getProposalsCount") - .mockImplementation(() => ({ confirmed: 0, initial: 0, rejected: 0 })); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const handleErrorSpy = jest.spyOn(executor as any, "handleCriticalError"); - const consoleErrorSpy = jest.spyOn(globalThis.console, "error").mockImplementation(() => {}); - - await sleep(10, true); - - expect(handleErrorSpy).not.toHaveBeenCalled(); - expect(consoleErrorSpy).toHaveBeenCalledWith( - "Could not start any work on Golem. Processed 0 initial proposals from yagna, filters accepted 0. Check your demand if it's not too restrictive or restart yagna.", - ); - await executor.shutdown(); - }); - - it("should pass zero for the Task entity if the maxTaskRetires option is zero", async () => { - const executor = await TaskExecutor.create({ - package: "test", - maxTaskRetries: 0, - logger, - yagnaOptions, - }); - jest.spyOn(Task.prototype, "isQueueable").mockImplementation(() => true); - jest.spyOn(Task.prototype, "isFinished").mockImplementation(() => true); - - const worker = () => Promise.resolve(true); - await executor.run(worker); - expect(Task).toHaveBeenCalledWith("1", worker, { - activityReadySetupFunctions: [], - maxRetries: 0, - timeout: 300000, - }); - await executor.shutdown(); - }); - - it("should pass zero for the Task entity if the maxRetires params in run method is zero", async () => { - const executor = await TaskExecutor.create({ - package: "test", - maxTaskRetries: 7, - logger, - yagnaOptions, - }); - jest.spyOn(Task.prototype, "isQueueable").mockImplementation(() => true); - jest.spyOn(Task.prototype, "isFinished").mockImplementation(() => true); - - const worker = () => Promise.resolve(true); - await executor.run(worker, { maxRetries: 0 }); - expect(Task).toHaveBeenCalledWith("1", worker, { - activityReadySetupFunctions: [], - maxRetries: 0, - timeout: 300000, - }); - await executor.shutdown(); - }); - - it("should throw an error if the value of maxTaskRetries is less than zero", async () => { - const executorPromise = TaskExecutor.create({ - package: "test", - maxTaskRetries: -1, - logger, - yagnaOptions, - }); - await expect(executorPromise).rejects.toMatchError( - new GolemConfigError("The maxTaskRetries parameter cannot be less than zero"), - ); - }); - - it('should emit "ready" event after init() completes', async () => { - const ready = jest.fn(); - - const executor = new TaskExecutor({ package: "test", logger, yagnaOptions }); - executor.events.on("ready", ready); - await executor.init(); - - expect(serviceRunSpy).toHaveBeenCalledTimes(4); - expect(ready).toHaveBeenCalledTimes(1); - await executor.shutdown(); - }); - }); - - describe("run()", () => { - it("should run all tasks even if some fail", async () => { - const executor = await TaskExecutor.create({ package: "test", logger, yagnaOptions }); - - jest.spyOn(Task.prototype, "isFinished").mockImplementation(() => true); - const executorShutdownSpy = jest.spyOn(executor as any, "doShutdown"); - - const rejectedSpy = jest.spyOn(Task.prototype, "isRejected"); - const resultsSpy = jest.spyOn(Task.prototype, "getResults"); - const errorSpy = jest.spyOn(Task.prototype, "getError"); - - rejectedSpy.mockImplementationOnce(() => false); - resultsSpy.mockImplementationOnce(() => "result 1"); - await expect(executor.run(() => Promise.resolve())).resolves.toEqual("result 1"); - - rejectedSpy.mockImplementationOnce(() => true); - errorSpy.mockImplementationOnce(() => new Error("error 1")); - await expect(executor.run(() => Promise.resolve())).rejects.toMatchError( - new GolemWorkError( - "Unable to execute task. Error: error 1", - WorkErrorCode.TaskExecutionFailed, - undefined, - undefined, - undefined, - new Error("error 1"), - ), - ); - - rejectedSpy.mockImplementationOnce(() => false); - resultsSpy.mockImplementationOnce(() => "result 2"); - await expect(executor.run(() => Promise.resolve())).resolves.toEqual("result 2"); - - expect(rejectedSpy).toHaveBeenCalledTimes(3); - expect(resultsSpy).toHaveBeenCalledTimes(2); - expect(errorSpy).toHaveBeenCalledTimes(1); - expect(executorShutdownSpy).toHaveBeenCalledTimes(0); - - await executor.shutdown(); - }); - it("should only warn the user if startup timeout is reached and exitOnNoProposals is disabled", async () => { - const executor = await TaskExecutor.create({ - package: "test", - startupTimeout: 0, - exitOnNoProposals: false, - logger, - yagnaOptions, - }); - jest - .spyOn(MarketService.prototype, "getProposalsCount") - .mockImplementation(() => ({ confirmed: 0, initial: 0, rejected: 0 })); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const handleErrorSpy = jest.spyOn(executor as any, "handleCriticalError"); - const consoleErrorSpy = jest.spyOn(globalThis.console, "error").mockImplementation(() => {}); - - await sleep(10, true); - - expect(handleErrorSpy).not.toHaveBeenCalled(); - expect(consoleErrorSpy).toHaveBeenCalledWith( - "Could not start any work on Golem. Processed 0 initial proposals from yagna, filters accepted 0. Check your demand if it's not too restrictive or restart yagna.", - ); - await executor.shutdown(); - }); - }); - - describe("end()", () => { - it("should call shutdown()", async () => { - const executor = await TaskExecutor.create({ package: "test", startupTimeout: 0, logger, yagnaOptions }); - const spy = jest.spyOn(executor, "shutdown"); - await executor.shutdown(); - expect(spy).toHaveBeenCalled(); - }); - }); - - describe("shutdown()", () => { - it("should allow multiple calls", async () => { - // Implementation details: the same promise is always used, so it's safe to call end() multiple times. - const executor = await TaskExecutor.create({ package: "test", startupTimeout: 0, logger, yagnaOptions }); - const p = Promise.resolve(); - const spy = jest.spyOn(executor as any, "doShutdown").mockReturnValue(p); - - const r1 = executor.shutdown(); - expect(r1).toBeDefined(); - expect(r1).toStrictEqual(p); - - const r2 = executor.shutdown(); - expect(r1).toStrictEqual(r2); - - await r1; - - const r3 = executor.shutdown(); - expect(r3).toStrictEqual(r1); - expect(spy).toHaveBeenCalledTimes(1); - }); - - it('it should emit "beforeEnd" and "end" events', async () => { - const executor = await TaskExecutor.create({ package: "test", startupTimeout: 0, logger, yagnaOptions }); - const beforeEnd = jest.fn(); - const end = jest.fn(); - - executor.events.on("beforeEnd", beforeEnd); - executor.events.on("end", end); - - await executor.shutdown(); - // Second call shouldn't generate new events. - await executor.shutdown(); - - // Both events should have been fired. - expect(beforeEnd).toHaveBeenCalledTimes(1); - expect(end).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/src/executor/executor.ts b/src/executor/executor.ts deleted file mode 100644 index 7ca076d1d..000000000 --- a/src/executor/executor.ts +++ /dev/null @@ -1,514 +0,0 @@ -import { Package, PackageOptions } from "../package"; -import { MarketOptions, MarketService } from "../market"; -import { AgreementPoolService, AgreementServiceOptions } from "../agreement"; -import { Task, TaskOptions, TaskQueue, TaskService, Worker } from "../task"; -import { PaymentOptions, PaymentService } from "../payment"; -import { NetworkService, NetworkServiceOptions } from "../network"; -import { Logger, runtimeContextChecker, sleep, Yagna } from "../utils"; -import { GftpStorageProvider, NullStorageProvider, StorageProvider, WebSocketBrowserStorageProvider } from "../storage"; -import { ExecutorConfig } from "./config"; -import { Events } from "../events"; -import { StatsService } from "../stats/service"; -import { TaskServiceOptions } from "../task/service"; -import { RequireAtLeastOne } from "../utils/types"; -import { TaskExecutorEventsDict } from "./events"; -import { EventEmitter } from "eventemitter3"; -import { GolemConfigError, GolemInternalError, GolemTimeoutError } from "../error/golem-error"; -import { WorkOptions } from "../task/work"; -import { GolemWorkError, WorkErrorCode } from "../task/error"; - -const terminatingSignals = ["SIGINT", "SIGTERM", "SIGBREAK", "SIGHUP"]; - -export type ExecutorOptions = { - /** Image hash or image tag as string, otherwise Package object */ - package?: string | Package; - /** Timeout for execute one task in ms */ - taskTimeout?: number; - /** Subnet Tag */ - subnetTag?: string; - /** Logger module */ - logger?: Logger; - /** Set to `false` to completely disable logging (even if a logger is provided) */ - enableLogging?: boolean; - /** Yagna Options */ - yagnaOptions?: YagnaOptions; - /** Event Bus implements EventTarget */ - eventTarget?: EventTarget; - /** The maximum number of retries when the job failed on the provider */ - maxTaskRetries?: number; - /** Custom Storage Provider used for transfer files */ - storageProvider?: StorageProvider; - /** Timeout for preparing activity - creating and deploy commands */ - activityPreparingTimeout?: number; - /** - * Do not install signal handlers for SIGINT, SIGTERM, SIGBREAK, SIGHUP. - * - * By default, TaskExecutor will install those and terminate itself when any of those signals is received. - * This is to make sure proper shutdown with completed invoice payments. - * - * Note: If you decide to set this to `true`, you will be responsible for proper shutdown of task executor. - */ - skipProcessSignals?: boolean; - /** - * Timeout for waiting for at least one offer from the market expressed in milliseconds. - * This parameter (set to 90 sec by default) will issue a warning when executing `TaskExecutor.run` - * if no offer from the market is accepted before this time. If you'd like to change this behavior, - * and throw an error instead, set `exitOnNoProposals` to `true`. - * You can set a slightly higher time in a situation where your parameters such as proposalFilter - * or minimum hardware requirements are quite restrictive and finding a suitable provider - * that meets these criteria may take a bit longer. - */ - startupTimeout?: number; - /** - * If set to `true`, the executor will exit with an error when no proposals are accepted. - * You can customize how long the executor will wait for proposals using the `startupTimeout` parameter. - * Default is `false`. - */ - exitOnNoProposals?: boolean; -} & Omit & - MarketOptions & - TaskServiceOptions & - PaymentOptions & - NetworkServiceOptions & - AgreementServiceOptions & - Omit; - -/** - * Contains information needed to start executor, if string the imageHash is required, otherwise it should be a type of {@link ExecutorOptions} - */ -export type ExecutorOptionsMixin = string | ExecutorOptions; - -export type YagnaOptions = { - apiKey?: string; - basePath?: string; -}; - -/** - * A high-level module for defining and executing tasks in the golem network - */ -export class TaskExecutor { - /** - * EventEmitter (EventEmitter3) instance emitting TaskExecutor events. - * @see TaskExecutorEventsDict for available events. - */ - readonly events: EventEmitter = new EventEmitter(); - - private readonly options: ExecutorConfig; - private marketService: MarketService; - private agreementPoolService: AgreementPoolService; - private taskService: TaskService; - private paymentService: PaymentService; - private networkService?: NetworkService; - private statsService: StatsService; - private activityReadySetupFunctions: Worker[] = []; - private taskQueue: TaskQueue; - private storageProvider?: StorageProvider; - private logger: Logger; - private lastTaskIndex = 0; - private isRunning = true; - private configOptions: ExecutorOptions; - private isCanceled = false; - private startupTimeoutId?: NodeJS.Timeout; - private yagna: Yagna; - - /** - * Signal handler reference, needed to remove handlers on exit. - * @param signal - */ - private signalHandler = (signal: string) => this.cancel(signal); - - /** - * Shutdown promise. - * This will be set by call to shutdown() method. - * It will be resolved when the executor is fully stopped. - */ - private shutdownPromise?: Promise; - - /** - * Create a new Task Executor - * @description Factory Method that create and initialize an instance of the TaskExecutor - * - * - * @example **Simple usage of Task Executor** - * - * The executor can be created by passing appropriate initial parameters such as package, budget, subnet tag, payment driver, payment network etc. - * One required parameter is a package. This can be done in two ways. First by passing only package image hash or image tag, e.g. - * ```js - * const executor = await TaskExecutor.create("9a3b5d67b0b27746283cb5f287c13eab1beaa12d92a9f536b747c7ae"); - * ``` - * or - * ```js - * const executor = await TaskExecutor.create("golem/alpine:3.18.2"); - * ``` - * - * @example **Usage of Task Executor with custom parameters** - * - * Or by passing some optional parameters, e.g. - * ```js - * const executor = await TaskExecutor.create({ - * subnetTag: "public", - * payment: { driver: "erc-20", network: "holesky" }, - * package: "golem/alpine:3.18.2", - * }); - * ``` - * - * @param options Task executor options - * @return TaskExecutor - */ - static async create(options: ExecutorOptionsMixin) { - const executor = new TaskExecutor(options); - await executor.init(); - return executor; - } - - /** - * Create a new TaskExecutor object. - * - * @param options - contains information needed to start executor, if string the imageHash is required, otherwise it should be a type of {@link ExecutorOptions} - */ - constructor(options: ExecutorOptionsMixin) { - this.configOptions = (typeof options === "string" ? { package: options } : options) as ExecutorOptions; - this.options = new ExecutorConfig(this.configOptions); - this.logger = this.options.logger; - this.yagna = new Yagna(this.configOptions.yagnaOptions); - const yagnaApi = this.yagna.getApi(); - this.taskQueue = new TaskQueue(); - this.agreementPoolService = new AgreementPoolService(yagnaApi, { - ...this.options, - logger: this.logger.child("agreement"), - }); - this.paymentService = new PaymentService(yagnaApi, { - ...this.options, - logger: this.logger.child("payment"), - }); - this.marketService = new MarketService(this.agreementPoolService, yagnaApi, { - ...this.options, - logger: this.logger.child("market"), - }); - this.networkService = this.options.networkIp - ? new NetworkService(yagnaApi, { ...this.options, logger: this.logger.child("network") }) - : undefined; - - // Initialize storage provider. - if (this.configOptions.storageProvider) { - this.storageProvider = this.configOptions.storageProvider; - } else if (runtimeContextChecker.isNode) { - this.storageProvider = new GftpStorageProvider(this.logger.child("storage")); - } else if (runtimeContextChecker.isBrowser) { - this.storageProvider = new WebSocketBrowserStorageProvider(yagnaApi, { - ...this.options, - logger: this.logger.child("storage"), - }); - } else { - this.storageProvider = new NullStorageProvider(); - } - - this.taskService = new TaskService( - this.yagna.getApi(), - this.taskQueue, - this.agreementPoolService, - this.paymentService, - this.networkService, - { ...this.options, storageProvider: this.storageProvider, logger: this.logger.child("work") }, - ); - this.statsService = new StatsService({ ...this.options, logger: this.logger.child("stats") }); - } - - /** - * Initialize executor - * - * @description Method responsible initialize all executor services. - */ - async init() { - try { - await this.yagna.connect(); - } catch (error) { - this.logger.error("Initialization failed", error); - throw error; - } - const manifest = this.options.packageOptions.manifest; - const packageReference = this.options.package; - let taskPackage: Package; - - if (manifest) { - taskPackage = await this.createPackage({ - manifest, - }); - } else { - if (packageReference) { - if (typeof packageReference === "string") { - taskPackage = await this.createPackage(Package.getImageIdentifier(packageReference)); - } else { - taskPackage = packageReference; - } - } else { - const error = new GolemConfigError("No package or manifest provided"); - this.logger.error("No package or manifest provided", error); - throw error; - } - } - - this.logger.debug("Initializing task executor services..."); - const allocations = await this.paymentService.createAllocation(); - await Promise.all([ - this.marketService.run(taskPackage, allocations).then(() => this.setStartupTimeout()), - this.agreementPoolService.run(), - this.paymentService.run(), - this.networkService?.run(), - this.statsService.run(), - this.storageProvider?.init(), - ]).catch((e) => this.handleCriticalError(e)); - - // Start listening to issues reported by the services - this.paymentService.events.on("error", (e) => this.handleCriticalError(e)); - - this.taskService.run().catch((e) => this.handleCriticalError(e)); - - if (runtimeContextChecker.isNode) this.installSignalHandlers(); - this.options.eventTarget.dispatchEvent(new Events.ComputationStarted()); - this.logger.info(`Task Executor has started`, { - subnet: this.options.subnetTag, - network: this.paymentService.config.payment.network, - driver: this.paymentService.config.payment.driver, - }); - this.events.emit("ready"); - } - - /** - * Stop all executor services and shut down executor instance. - * - * You can call this method multiple times, it will resolve only once the executor is shutdown. - * - * When shutdown() is initially called, a beforeEnd event is emitted. - * - * Once the executor is fully stopped, an end event is emitted. - */ - shutdown(): Promise { - if (!this.isRunning) { - // Using ! is safe, because if isRunning is false, endPromise is defined. - return this.shutdownPromise!; - } - - this.isRunning = false; - this.shutdownPromise = this.doShutdown(); - - return this.shutdownPromise; - } - - /** - * Perform everything needed to cleanly shut down the executor. - * @private - */ - private async doShutdown() { - this.events.emit("beforeEnd"); - if (runtimeContextChecker.isNode) this.removeSignalHandlers(); - clearTimeout(this.startupTimeoutId); - if (!this.configOptions.storageProvider) await this.storageProvider?.close(); - await this.networkService?.end(); - await Promise.all([this.taskService.end(), this.agreementPoolService.end(), this.marketService.end()]); - await this.paymentService.end(); - await this.yagna.end(); - this.options.eventTarget?.dispatchEvent(new Events.ComputationFinished()); - this.printStats(); - await this.statsService.end(); - this.logger.info("Task Executor has shut down"); - this.events.emit("end"); - } - - /** - * Statistics of execution process - * - * @return array - */ - getStats() { - return this.statsService.getStatsTree(); - } - - /** - * Registers a worker function that will be run when an activity is ready. - * This is the perfect place to run setup functions that need to be run only once per - * activity, for example uploading files that will be used by all tasks in the activity. - * This function can be called multiple times, each worker will be run in the order - * they were registered. - * - * @param worker worker function that will be run when an activity is ready - * @example - * ```ts - * const uploadFile1 = async (ctx) => ctx.uploadFile("./file1.txt", "/file1.txt"); - * const uploadFile2 = async (ctx) => ctx.uploadFile("./file2.txt", "/file2.txt"); - * - * executor.onActivityReady(uploadFile1); - * executor.onActivityReady(uploadFile2); - * - * await executor.run(async (ctx) => { - * await ctx.run("cat /file1.txt /file2.txt"); - * }); - * ``` - */ - onActivityReady(worker: Worker) { - this.activityReadySetupFunctions.push(worker); - } - - /** - * Run task - allows to execute a single worker function on the Golem network with a single provider. - * - * @param worker function that run task - * @param options task options - * @return result of task computation - * @example - * ```typescript - * await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - * ``` - */ - async run(worker: Worker, options?: TaskOptions): Promise { - return this.executeTask(worker, options); - } - - private async createPackage( - packageReference: RequireAtLeastOne< - { imageHash: string; manifest: string; imageTag: string }, - "manifest" | "imageTag" | "imageHash" - >, - ): Promise { - const packageInstance = Package.create({ ...this.options.packageOptions, ...packageReference }); - - this.options.eventTarget.dispatchEvent( - new Events.PackageCreated({ packageReference, details: packageInstance.details }), - ); - - return packageInstance; - } - - private async executeTask(worker: Worker, options?: TaskOptions): Promise { - let task; - try { - task = new Task((++this.lastTaskIndex).toString(), worker, { - maxRetries: options?.maxRetries ?? this.options.maxTaskRetries, - timeout: options?.timeout ?? this.options.taskTimeout, - activityReadySetupFunctions: this.activityReadySetupFunctions, - }); - this.taskQueue.addToEnd(task); - while (this.isRunning) { - if (task.isFinished()) { - if (task.isRejected()) throw task.getError(); - return task.getResults() as OutputType; - } - await sleep(2000, true); - } - throw new GolemInternalError("Task executor has been stopped"); - } catch (error) { - if (error instanceof GolemWorkError) { - throw error; - } - throw new GolemWorkError( - `Unable to execute task. ${error.toString()}`, - WorkErrorCode.TaskExecutionFailed, - task?.getActivity()?.agreement, - task?.getActivity(), - task?.getActivity()?.getProviderInfo(), - error, - ); - } - } - - public async cancel(reason: string) { - try { - if (this.isCanceled) { - this.logger.warn("The executor is already cancelled, ignoring second request"); - return; - } - - this.isCanceled = true; - - if (runtimeContextChecker.isNode) { - this.removeSignalHandlers(); - } - - const message = `Executor has interrupted by the user. Reason: ${reason}.`; - - this.logger.info(`${message}. Stopping all tasks...`, { - tasksInProgress: this.taskQueue.size, - }); - - await this.shutdown(); - } catch (error) { - this.logger.error(`Error while cancelling the executor`, error); - } - } - - private installSignalHandlers() { - if (this.configOptions.skipProcessSignals) return; - terminatingSignals.forEach((event) => { - process.on(event, this.signalHandler); - }); - } - - private removeSignalHandlers() { - if (this.configOptions.skipProcessSignals) return; - terminatingSignals.forEach((event) => { - process.removeListener(event, this.signalHandler); - }); - } - - private handleCriticalError(err: Error) { - this.options.eventTarget?.dispatchEvent(new Events.ComputationFailed({ reason: err.toString() })); - const message = - "TaskExecutor faced a critical error and will now cancel work, terminate agreements and request settling payments"; - this.logger.error(message, err); - // Make sure users know in case they didn't configure logging - console.error(message, err); - this.cancel(`Cancelling due to critical error ${err}`).catch((cancelErr) => - this.logger.error("Issue when cancelling Task Executor", { err: cancelErr }), - ); - } - - private printStats() { - const costs = this.statsService.getAllCosts(); - const costsSummary = this.statsService.getAllCostsSummary(); - const duration = this.statsService.getComputationTime(); - const providersCount = new Set(costsSummary.map((x) => x["Provider Name"])).size; - this.logger.info(`Computation finished in ${duration}`); - this.logger.info(`Negotiation summary:`, { - agreements: costsSummary.length, - providers: providersCount, - }); - costsSummary.forEach((cost, index) => { - this.logger.info(`Agreement ${index + 1}:`, { - agreement: cost["Agreement"], - provider: cost["Provider Name"], - tasks: cost["Task Computed"], - cost: cost["Cost"], - paymentStatus: cost["Payment Status"], - }); - }); - this.logger.info(`Cost summary:`, { - totalCost: costs.total, - totalPaid: costs.paid, - }); - } - - /** - * Sets a timeout for waiting for offers from the market. - * If at least one offer is not confirmed during the set timeout, - * a critical error will be reported and the entire process will be interrupted. - */ - private setStartupTimeout() { - this.startupTimeoutId = setTimeout(() => { - const proposalsCount = this.marketService.getProposalsCount(); - if (proposalsCount.confirmed === 0) { - const hint = - proposalsCount.initial === 0 && proposalsCount.confirmed === 0 - ? "Check your demand if it's not too restrictive or restart yagna." - : proposalsCount.initial === proposalsCount.rejected - ? "All off proposals got rejected." - : "Check your proposal filters if they are not too restrictive."; - const errorMessage = `Could not start any work on Golem. Processed ${proposalsCount.initial} initial proposals from yagna, filters accepted ${proposalsCount.confirmed}. ${hint}`; - if (this.options.exitOnNoProposals) { - this.handleCriticalError(new GolemTimeoutError(errorMessage)); - } else { - console.error(errorMessage); - } - } - }, this.options.startupTimeout); - } -} diff --git a/src/executor/index.ts b/src/executor/index.ts deleted file mode 100644 index ce7cf2c7d..000000000 --- a/src/executor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TaskExecutor, ExecutorOptions, ExecutorOptionsMixin } from "./executor"; diff --git a/src/experimental.ts b/src/experimental.ts deleted file mode 100644 index bfaa3372a..000000000 --- a/src/experimental.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./job"; -export * from "./golem_network"; -export * from "./experimental/reputation"; diff --git a/src/experimental/README.md b/src/experimental/README.md new file mode 100644 index 000000000..a87469f85 --- /dev/null +++ b/src/experimental/README.md @@ -0,0 +1,4 @@ +# Experimental Features + +> **WARNING:** Features present in this directory are experimental and are not yet ready for production use. +> They are subject to change without notice and within minor versions of the library. diff --git a/src/experimental/deployment/builder.test.ts b/src/experimental/deployment/builder.test.ts new file mode 100644 index 000000000..b7e9c31e2 --- /dev/null +++ b/src/experimental/deployment/builder.test.ts @@ -0,0 +1,91 @@ +import { GolemConfigError } from "../../shared/error/golem-error"; +import { GolemDeploymentBuilder } from "./builder"; +import { GolemNetwork } from "../../golem-network"; +import { imock } from "@johanblumenberg/ts-mockito"; + +const mockGolemNetwork = imock(); + +describe("Deployment builder", () => { + it("throws an error when creating an activity pool with the same name", () => { + const builder = new GolemDeploymentBuilder(mockGolemNetwork); + expect(() => { + builder + .createResourceRentalPool("my-pool", { + demand: { + workload: { + imageTag: "image", + minCpuCores: 1, + minMemGib: 1, + minStorageGib: 1, + }, + }, + market: { + rentHours: 1, + pricing: { + model: "linear", + maxStartPrice: 1, + maxEnvPerHourPrice: 1, + maxCpuPerHourPrice: 1, + }, + }, + deployment: { + replicas: 1, + }, + }) + .createResourceRentalPool("my-pool", { + demand: { + workload: { + imageTag: "image", + minCpuCores: 1, + minMemGib: 1, + minStorageGib: 1, + }, + }, + market: { + rentHours: 1, + pricing: { + model: "linear", + maxStartPrice: 1, + maxEnvPerHourPrice: 1, + maxCpuPerHourPrice: 1, + }, + }, + deployment: { + replicas: 1, + }, + }); + }).toThrow(new GolemConfigError(`Resource Rental Pool with name my-pool already exists`)); + }); + it("throws an error when creating a network with the same name", () => { + const builder = new GolemDeploymentBuilder(mockGolemNetwork); + expect(() => { + builder.createNetwork("my-network").createNetwork("my-network"); + }).toThrow(new GolemConfigError(`Network with name my-network already exists`)); + }); + it("throws an error when creating a deployment with an activity pool referencing a non-existing network", () => { + const builder = new GolemDeploymentBuilder(mockGolemNetwork); + expect(() => { + builder + .createNetwork("existing-network") + .createResourceRentalPool("my-pool", { + demand: { + workload: { imageTag: "image", minCpuCores: 1, minMemGib: 1, minStorageGib: 1 }, + }, + market: { + rentHours: 1, + pricing: { + model: "linear", + maxStartPrice: 1, + maxEnvPerHourPrice: 1, + maxCpuPerHourPrice: 1, + }, + }, + deployment: { + network: "non-existing-network", + replicas: 1, + }, + }) + .getDeployment(); + }).toThrow(new GolemConfigError(`Activity pool my-pool references non-existing network non-existing-network`)); + }); +}); diff --git a/src/experimental/deployment/builder.ts b/src/experimental/deployment/builder.ts new file mode 100644 index 000000000..3764d7af2 --- /dev/null +++ b/src/experimental/deployment/builder.ts @@ -0,0 +1,67 @@ +import { GolemConfigError } from "../../shared/error/golem-error"; +import { NetworkOptions } from "../../network"; +import { Deployment, DeploymentComponents } from "./deployment"; +import { GolemNetwork, MarketOrderSpec } from "../../golem-network"; +import { validateDeployment } from "./validate-deployment"; + +export interface DeploymentOptions { + replicas: number | { min: number; max: number }; + network?: string; +} + +export interface CreateResourceRentalPoolOptions extends MarketOrderSpec { + deployment: DeploymentOptions; +} + +export class GolemDeploymentBuilder { + private components: DeploymentComponents = { + resourceRentalPools: [], + networks: [], + }; + + public reset() { + this.components = { + resourceRentalPools: [], + networks: [], + }; + } + + constructor(private glm: GolemNetwork) {} + + createResourceRentalPool(name: string, options: CreateResourceRentalPoolOptions): this { + if (this.components.resourceRentalPools.some((pool) => pool.name === name)) { + throw new GolemConfigError(`Resource Rental Pool with name ${name} already exists`); + } + + this.components.resourceRentalPools.push({ name, options }); + + return this; + } + + createNetwork(name: string, options: NetworkOptions = {}): this { + if (this.components.networks.some((network) => network.name === name)) { + throw new GolemConfigError(`Network with name ${name} already exists`); + } + + this.components.networks.push({ name, options }); + + return this; + } + + getDeployment(): Deployment { + validateDeployment(this.components); + const deployment = new Deployment(this.components, { + logger: this.glm.services.logger, + yagna: this.glm.services.yagna, + payment: this.glm.payment, + market: this.glm.market, + activity: this.glm.activity, + network: this.glm.network, + rental: this.glm.rental, + }); + + this.reset(); + + return deployment; + } +} diff --git a/src/experimental/deployment/deployment.ts b/src/experimental/deployment/deployment.ts new file mode 100644 index 000000000..6b3e98c6f --- /dev/null +++ b/src/experimental/deployment/deployment.ts @@ -0,0 +1,252 @@ +import { GolemAbortError, GolemUserError } from "../../shared/error/golem-error"; +import { defaultLogger, Logger, YagnaApi } from "../../shared/utils"; +import { EventEmitter } from "eventemitter3"; +import { ActivityModule } from "../../activity"; +import { Network, NetworkModule, NetworkOptions } from "../../network"; +import { validateDeployment } from "./validate-deployment"; +import { DraftOfferProposalPool, MarketModule } from "../../market"; +import { PaymentModule } from "../../payment"; +import { CreateResourceRentalPoolOptions } from "./builder"; +import { Subscription } from "rxjs"; +import { RentalModule, ResourceRentalPool } from "../../resource-rental"; + +export enum DeploymentState { + INITIAL = "INITIAL", + STARTING = "STARTING", + READY = "READY", + STOPPING = "STOPPING", + STOPPED = "STOPPED", + ERROR = "ERROR", +} + +export interface DeploymentEvents { + /** + * Fires when backend is started. + */ + ready: () => void; + + /** + * Fires when a new instance encounters an error during initialization. + * @param error + */ + // activityInitError: (error: ActivityInitError) => void; + + /** + * Fires when backend is about to be stopped. + */ + beforeEnd: () => void; + + /** + * Fires when backend is completely terminated. + */ + end: () => void; +} + +export type DeploymentComponents = { + resourceRentalPools: { name: string; options: CreateResourceRentalPoolOptions }[]; + networks: { name: string; options: NetworkOptions }[]; +}; + +/** + * @experimental This feature is experimental!!! + */ +export class Deployment { + readonly events = new EventEmitter(); + + private state: DeploymentState = DeploymentState.INITIAL; + + private readonly logger: Logger; + private readonly abortController = new AbortController(); + + private readonly yagnaApi: YagnaApi; + + private readonly pools = new Map< + string, + { + proposalPool: DraftOfferProposalPool; + proposalSubscription: Subscription; + resourceRentalPool: ResourceRentalPool; + } + >(); + + private readonly networks = new Map(); + + private readonly modules: { + market: MarketModule; + activity: ActivityModule; + payment: PaymentModule; + network: NetworkModule; + rental: RentalModule; + }; + + constructor( + private readonly components: DeploymentComponents, + deps: { + logger: Logger; + yagna: YagnaApi; + market: MarketModule; + activity: ActivityModule; + payment: PaymentModule; + network: NetworkModule; + rental: RentalModule; + }, + ) { + validateDeployment(components); + + const { logger, yagna, ...modules } = deps; + + this.logger = logger ?? defaultLogger("deployment"); + this.yagnaApi = yagna; + + this.modules = modules; + + this.abortController.signal.addEventListener("abort", () => { + this.logger.info("Abort signal received"); + this.stop().catch((e) => { + this.logger.error("stop() error on abort", { error: e }); + }); + }); + } + + getState(): DeploymentState { + return this.state; + } + + async start() { + if (this.abortController.signal.aborted) { + throw new GolemAbortError("Calling start after abort signal received"); + } + + if (this.state != DeploymentState.INITIAL) { + throw new GolemUserError(`Cannot start backend, expected backend state INITIAL, current state is ${this.state}`); + } + + for (const network of this.components.networks) { + const networkInstance = await this.modules.network.createNetwork(network.options); + this.networks.set(network.name, networkInstance); + } + + // Allocation is re-used for all demands so the expiration date should + // be the equal to the longest expiration date of all demands + const longestExpiration = + Math.max(...this.components.resourceRentalPools.map((pool) => pool.options.market.rentHours)) * 3600; + + const totalBudget = this.components.resourceRentalPools.reduce((acc, pool) => { + const replicas = pool.options.deployment.replicas; + const maxAgreements = typeof replicas === "number" ? replicas : replicas?.max ?? replicas?.min ?? 1; + return ( + acc + + this.modules.market.estimateBudget({ + order: pool.options, + maxAgreements, + }) + ); + }, 0); + + const allocation = await this.modules.payment.createAllocation({ + budget: totalBudget, + expirationSec: longestExpiration, + }); + + for (const pool of this.components.resourceRentalPools) { + const network = pool.options?.deployment?.network + ? this.networks.get(pool.options?.deployment.network) + : undefined; + + const demandSpecification = await this.modules.market.buildDemandDetails( + pool.options.demand, + pool.options.market, + allocation, + ); + + const proposalPool = new DraftOfferProposalPool({ + logger: this.logger, + validateOfferProposal: pool.options.market.offerProposalFilter, + selectOfferProposal: pool.options.market.offerProposalSelector, + }); + + const draftProposal$ = this.modules.market.collectDraftOfferProposals({ + demandSpecification, + pricing: pool.options.market.pricing, + filter: pool.options.market.offerProposalFilter, + }); + + const proposalSubscription = proposalPool.readFrom(draftProposal$); + + const resourceRentalPool = this.modules.rental.createResourceRentalPool(proposalPool, allocation, { + poolSize: pool.options.deployment?.replicas, + network, + resourceRentalOptions: { + activity: pool.options?.activity, + payment: pool.options?.payment, + }, + agreementOptions: { + expirationSec: pool.options.market.rentHours * 3600, + }, + }); + this.pools.set(pool.name, { + proposalPool, + proposalSubscription, + resourceRentalPool, + }); + } + + await this.waitForDeployment(); + + this.events.emit("ready"); + } + + async stop() { + if (this.state === DeploymentState.STOPPING || this.state === DeploymentState.STOPPED) { + return; + } + + this.state = DeploymentState.STOPPING; + this.events.emit("beforeEnd"); + + try { + this.abortController.abort(); + + const stopPools = Array.from(this.pools.values()).map((pool) => + Promise.allSettled([pool.proposalSubscription.unsubscribe(), pool.resourceRentalPool.drainAndClear()]), + ); + await Promise.allSettled(stopPools); + + const stopNetworks: Promise[] = Array.from(this.networks.values()).map((network) => + this.modules.network.removeNetwork(network), + ); + await Promise.allSettled(stopNetworks); + + this.state = DeploymentState.STOPPED; + } catch (err) { + this.logger.error("The deployment failed with an error", err); + this.state = DeploymentState.ERROR; + throw err; + } + + this.events.emit("end"); + } + + getResourceRentalPool(name: string): ResourceRentalPool { + const pool = this.pools.get(name); + if (!pool) { + throw new GolemUserError(`ResourceRentalPool ${name} not found`); + } + return pool.resourceRentalPool; + } + + getNetwork(name: string): Network { + const network = this.networks.get(name); + if (!network) { + throw new GolemUserError(`Network ${name} not found`); + } + return network; + } + + private async waitForDeployment() { + this.logger.info("Waiting for all components to be deployed..."); + const readyPools = [...this.pools.values()].map((component) => component.resourceRentalPool.ready()); + await Promise.all(readyPools); + this.logger.info("Components deployed and ready to use"); + } +} diff --git a/src/experimental/deployment/index.ts b/src/experimental/deployment/index.ts new file mode 100644 index 000000000..29409cf08 --- /dev/null +++ b/src/experimental/deployment/index.ts @@ -0,0 +1,2 @@ +export * from "./deployment"; +export * from "./builder"; diff --git a/src/experimental/deployment/validate-deployment.ts b/src/experimental/deployment/validate-deployment.ts new file mode 100644 index 000000000..43c81e61a --- /dev/null +++ b/src/experimental/deployment/validate-deployment.ts @@ -0,0 +1,21 @@ +import { GolemConfigError } from "../../shared/error/golem-error"; +import { DeploymentComponents } from "./deployment"; + +function validateNetworks(components: DeploymentComponents) { + const networkNames = new Set(components.networks.map((network) => network.name)); + for (const pool of components.resourceRentalPools) { + if (!pool.options.deployment?.network) { + continue; + } + if (!networkNames.has(pool.options.deployment.network)) { + throw new GolemConfigError( + `Activity pool ${pool.name} references non-existing network ${pool.options.deployment.network}`, + ); + } + } +} + +export function validateDeployment(components: DeploymentComponents) { + validateNetworks(components); + // ... other validations +} diff --git a/src/experimental/index.ts b/src/experimental/index.ts new file mode 100644 index 000000000..b6d6d6a30 --- /dev/null +++ b/src/experimental/index.ts @@ -0,0 +1,3 @@ +export * from "./job"; +export * from "./reputation"; +export * from "./deployment"; diff --git a/src/experimental/job/index.ts b/src/experimental/job/index.ts new file mode 100644 index 000000000..58aac5c3c --- /dev/null +++ b/src/experimental/job/index.ts @@ -0,0 +1,2 @@ +export { Job, JobState } from "./job"; +export { JobManager, JobManagerConfig } from "./job_manager"; diff --git a/src/experimental/job/job.test.ts b/src/experimental/job/job.test.ts new file mode 100644 index 000000000..49f2ccb67 --- /dev/null +++ b/src/experimental/job/job.test.ts @@ -0,0 +1,55 @@ +import { Job } from "./job"; +import { ExeUnit } from "../../activity/exe-unit"; +import { anything, imock, instance, mock, reset, verify, when } from "@johanblumenberg/ts-mockito"; +import { Logger } from "../../shared/utils"; +import { GolemNetwork } from "../../golem-network"; +import { ResourceRental } from "../../resource-rental"; + +const mockGlm = mock(GolemNetwork); +const mockRental = mock(ResourceRental); +const mockExeUnit = mock(ExeUnit); +describe("Job", () => { + beforeEach(() => { + reset(mockGlm); + reset(mockRental); + reset(mockExeUnit); + }); + + describe("cancel()", () => { + it("stops the activity and releases the agreement when canceled", async () => { + when(mockRental.getExeUnit()).thenResolve(instance(mockExeUnit)); + when(mockGlm.oneOf(anything())).thenResolve(instance(mockRental)); + const job = new Job( + "test_id", + instance(mockGlm), + { + demand: { + workload: { + imageTag: "test_image", + }, + }, + market: { + rentHours: 1, + pricing: { + model: "linear", + maxStartPrice: 1, + maxEnvPerHourPrice: 1, + maxCpuPerHourPrice: 1, + }, + }, + }, + instance(imock()), + ); + + job.startWork(() => { + return new Promise((resolve) => setTimeout(resolve, 10000)); + }); + + await job.cancel(); + + await expect(job.waitForResult()).rejects.toThrow("Canceled"); + + verify(mockRental.stopAndFinalize()).once(); + }); + }); +}); diff --git a/src/experimental/job/job.ts b/src/experimental/job/job.ts new file mode 100644 index 000000000..da208eef5 --- /dev/null +++ b/src/experimental/job/job.ts @@ -0,0 +1,195 @@ +import { ExeUnitOptions, ExeUnit } from "../../activity/exe-unit"; +import { NetworkOptions } from "../../network"; +import { PaymentModuleOptions } from "../../payment"; +import { EventEmitter } from "eventemitter3"; +import { GolemAbortError, GolemUserError } from "../../shared/error/golem-error"; +import { GolemNetwork, MarketOrderSpec } from "../../golem-network/golem-network"; +import { Logger } from "../../shared/utils"; +import { WorkloadDemandDirectorConfigOptions } from "../../market/demand/options"; + +export enum JobState { + New = "new", + Queued = "queued", + Pending = "pending", + Done = "done", + Retry = "retry", + Rejected = "rejected", +} + +export type RunJobOptions = { + payment?: PaymentModuleOptions; + network?: NetworkOptions; + workload?: WorkloadDemandDirectorConfigOptions; + work?: ExeUnitOptions; +}; + +export type WorkFunction = (exe: ExeUnit) => Promise; + +export interface JobEventsDict { + /** + * Emitted immediately after the job is created and initialization begins. + */ + created: () => void; + /** + * Emitted when the job finishes initialization and work begins. + */ + started: () => void; + /** + * Emitted when the job completes successfully and cleanup begins. + */ + success: () => void; + /** + * Emitted when the job fails and cleanup begins. + */ + error: (error: Error) => void; + /** + * Emitted when the job is canceled by the user. + */ + canceled: () => void; + /** + * Emitted when the job finishes cleanup after success, error or cancelation. + */ + ended: () => void; +} + +/** + * @experimental This API is experimental and subject to change. Use at your own risk. + * + * The Job class represents a single self-contained unit of work that can be run on the Golem Network. + * It is responsible for managing the lifecycle of the work and providing information about its state. + * It also provides an event emitter that can be used to listen for state changes. + */ +export class Job { + readonly events: EventEmitter = new EventEmitter(); + private abortController = new AbortController(); + + public results: Output | undefined; + public error: Error | undefined; + public state: JobState = JobState.New; + + /** + * @param id + * @param glm + * @param order + * @param logger + */ + constructor( + public readonly id: string, + private readonly glm: GolemNetwork, + private readonly order: MarketOrderSpec, + private readonly logger: Logger, + ) {} + + public isRunning() { + const inProgressStates = [JobState.Pending, JobState.Retry]; + + return inProgressStates.includes(this.state); + } + + /** + * Run your worker function on the Golem Network. This method will synchronously initialize all internal services and validate the job options. The work itself will be run asynchronously in the background. + * You can use the {@link experimental/job/job.Job.events} event emitter to listen for state changes. + * You can also use {@link experimental/job/job.Job.waitForResult} to wait for the job to finish and get the results. + * If you want to cancel the job, use {@link experimental/job/job.Job.cancel}. + * If you want to run multiple jobs in parallel, you can use {@link experimental/job/job_manager.JobManager.createJob} to create multiple jobs and run them in parallel. + * + * @param workOnGolem - Your worker function that will be run on the Golem Network. + */ + startWork(workOnGolem: WorkFunction) { + this.logger.debug("Staring work in a Job"); + if (this.isRunning()) { + throw new GolemUserError(`Job ${this.id} is already running`); + } + + this.state = JobState.Pending; + this.events.emit("created"); + + // reset abort controller + this.abortController = new AbortController(); + + this.runWork({ + workOnGolem, + signal: this.abortController.signal, + }) + .then((results) => { + this.logger.debug("Finished work in job", { results }); + this.results = results; + this.state = JobState.Done; + this.events.emit("success"); + }) + .catch((error) => { + this.logger.error("Running work in job failed due to", error); + this.error = error; + this.state = JobState.Rejected; + this.events.emit("error", error); + }) + .finally(async () => { + this.events.emit("ended"); + }); + } + + private async runWork({ workOnGolem, signal }: { workOnGolem: WorkFunction; signal: AbortSignal }) { + if (signal.aborted) { + this.events.emit("canceled"); + throw new GolemAbortError("Canceled"); + } + + const rental = await this.glm.oneOf({ order: this.order }); + + const exeUnit = await rental.getExeUnit(); + this.events.emit("started"); + + const onAbort = async () => { + await rental.stopAndFinalize(); + this.events.emit("canceled"); + }; + + if (signal.aborted) { + await onAbort(); + throw new GolemAbortError("Canceled"); + } + + signal.addEventListener("abort", onAbort, { once: true }); + + return workOnGolem(exeUnit); + } + + /** + * Cancel the job. This method will stop the activity and wait for it to finish. + * Throws an error if the job is not running. + */ + async cancel() { + if (!this.isRunning) { + throw new GolemUserError(`Job ${this.id} is not running`); + } + this.abortController.abort(); + return new Promise((resolve) => { + this.events.once("ended", resolve); + }); + } + + /** + * Wait for the job to finish and return the results. + * Throws an error if the job was not started. + */ + async waitForResult() { + if (this.state === JobState.Done) { + return this.results; + } + if (this.state === JobState.Rejected) { + throw this.error; + } + if (!this.isRunning()) { + throw new GolemUserError(`Job ${this.id} is not running`); + } + return new Promise((resolve, reject) => { + this.events.once("ended", () => { + if (this.state === JobState.Done) { + resolve(this.results); + } else { + reject(this.error); + } + }); + }); + } +} diff --git a/src/experimental/job/job_manager.ts b/src/experimental/job/job_manager.ts new file mode 100644 index 000000000..ea44fc62a --- /dev/null +++ b/src/experimental/job/job_manager.ts @@ -0,0 +1,103 @@ +import { v4 } from "uuid"; +import { Job, RunJobOptions } from "./job"; +import { defaultLogger, Logger, YagnaOptions, isNode, isBrowser } from "../../shared/utils"; +import { GolemUserError } from "../../shared/error/golem-error"; +import { GolemNetwork, MarketOrderSpec } from "../../golem-network/golem-network"; +import { + GftpStorageProvider, + NullStorageProvider, + StorageProvider, + WebSocketBrowserStorageProvider, +} from "../../shared/storage"; + +export type JobManagerConfig = Partial & { + yagna?: YagnaOptions; +}; + +/** + * @experimental This API is experimental and subject to change. Use at your own risk. + * + * The Golem Network class provides a high-level API for running jobs on the Golem Network. + */ +export class JobManager { + private glm: GolemNetwork; + private jobs = new Map(); + + /** + * @param config - Configuration options that will be passed to all jobs created by this instance. + * @param logger + */ + constructor( + private readonly config?: JobManagerConfig, + private readonly logger: Logger = defaultLogger("jobs"), + ) { + const storageProvider = this.getDefaultStorageProvider(); + + this.logger.debug("Jobs using storage provider", { storageProvider }); + + this.glm = new GolemNetwork({ + api: { + key: this.config?.yagna?.apiKey, + url: this.config?.yagna?.basePath, + }, + dataTransferProtocol: storageProvider, + }); + } + + public isInitialized() { + return this.glm.isConnected(); + } + + public async init() { + await this.glm.connect(); + } + + /** + * Create a new job and add it to the list of jobs managed by this instance. + * This method does not start any work on the network, use {@link experimental/job/job.Job.startWork} for that. + * + * @param order + */ + public createJob(order: MarketOrderSpec) { + this.checkInitialization(); + + const jobId = v4(); + const job = new Job(jobId, this.glm, order, this.logger); + this.jobs.set(jobId, job); + + return job; + } + + public getJobById(id: string) { + this.checkInitialization(); + + return this.jobs.get(id); + } + + /** + * Close the connection to the Yagna service and cancel all running jobs. + */ + public async close() { + const pendingJobs = Array.from(this.jobs.values()).filter((job) => job.isRunning()); + await Promise.allSettled(pendingJobs.map((job) => job.cancel())); + await this.glm.disconnect(); + } + + private checkInitialization() { + if (!this.isInitialized()) { + throw new GolemUserError("GolemNetwork not initialized, please run init() first"); + } + } + + private getDefaultStorageProvider(): StorageProvider { + if (isNode) { + return new GftpStorageProvider(); + } + + if (isBrowser) { + return new WebSocketBrowserStorageProvider(this.glm.services.yagna, {}); + } + + return new NullStorageProvider(); + } +} diff --git a/src/experimental/reputation/error.ts b/src/experimental/reputation/error.ts index bca009db5..1c2305c8d 100644 --- a/src/experimental/reputation/error.ts +++ b/src/experimental/reputation/error.ts @@ -1,4 +1,4 @@ -import { GolemModuleError } from "../../error/golem-error"; +import { GolemModuleError } from "../../shared/error/golem-error"; export class GolemReputationError extends GolemModuleError { constructor(message: string, cause?: Error) { diff --git a/src/experimental/reputation/index.ts b/src/experimental/reputation/index.ts index 3d58cd2d9..a0465344b 100644 --- a/src/experimental/reputation/index.ts +++ b/src/experimental/reputation/index.ts @@ -7,7 +7,7 @@ export { DEFAULT_AGREEMENT_TOP_POOL_SIZE, } from "./system"; export { ReputationWeights } from "./types"; -export { AgreementSelectorOption } from "./types"; +export { ProposalSelectorOptions } from "./types"; export { ProposalFilterOptions } from "./types"; export { ReputationData } from "./types"; export { ReputationProviderEntry } from "./types"; diff --git a/src/experimental/reputation/system.ts b/src/experimental/reputation/system.ts index e138e3493..cb653b2bd 100644 --- a/src/experimental/reputation/system.ts +++ b/src/experimental/reputation/system.ts @@ -1,19 +1,20 @@ -import { Proposal, ProposalFilter } from "../../market"; -import { AgreementCandidate, AgreementSelector } from "../../agreement"; +import { OfferProposalFilter, OfferProposal, OfferProposalSelector } from "../../market"; import { GolemReputationError } from "./error"; import { - AgreementSelectorOption, + ProposalSelectorOptions, ProposalFilterOptions, ReputationConfig, ReputationData, + ReputationPresetName, + ReputationPresets, ReputationProviderEntry, ReputationProviderScores, ReputationRejectedOperator, ReputationRejectedProvider, ReputationWeights, } from "./types"; -import { Logger, nullLogger } from "../../utils"; -import { getPaymentNetwork } from "../../utils/env"; +import { Logger, nullLogger } from "../../shared/utils"; +import { getPaymentNetwork } from "../../shared/utils/env"; /** * Default minimum score for proposals. @@ -52,6 +53,48 @@ export const DEFAULT_REPUTATION_URL = "https://reputation.dev-test.golem.network */ export const DEFAULT_AGREEMENT_TOP_POOL_SIZE = 2; +/** + * Predefined presets for reputation system. + */ +export const REPUTATION_PRESETS: ReputationPresets = { + /** + * Preset for short CPU intensive compute tasks. + */ + compute: { + offerProposalFilter: { + min: 0.5, + weights: { + cpuSingleThreadScore: 1, + }, + }, + offerProposalSelector: { + weights: { + cpuSingleThreadScore: 1, + }, + topPoolSize: DEFAULT_AGREEMENT_TOP_POOL_SIZE, + }, + }, + /** + * Preset for long-running services, where uptime is important. + */ + service: { + offerProposalFilter: { + min: DEFAULT_PROPOSAL_MIN_SCORE, + weights: { + uptime: 0.8, + cpuMultiThreadScore: 0.2, + }, + }, + offerProposalSelector: { + weights: { + uptime: 0.5, + cpuMultiThreadScore: 0.5, + }, + topPoolSize: DEFAULT_AGREEMENT_TOP_POOL_SIZE, + }, + }, +}; + /** * Reputation system client. * @@ -120,6 +163,18 @@ export class ReputationSystem { */ private readonly logger: Logger; + /** + * Default options used when creating proposal filter. + * @private + */ + private defaultProposalFilterOptions: ProposalFilterOptions; + + /** + * Default options used when creating agreement selector. + * @private + */ + private defaultAgreementSelectorOptions: ProposalSelectorOptions; + /** * Create a new reputation system client and fetch the reputation data. */ @@ -133,6 +188,49 @@ export class ReputationSystem { this.url = this.config?.url ?? DEFAULT_REPUTATION_URL; this.logger = this.config?.logger?.child("reputation") ?? nullLogger(); this.paymentNetwork = this.config?.paymentNetwork ?? getPaymentNetwork(); + + this.defaultProposalFilterOptions = { + min: DEFAULT_PROPOSAL_MIN_SCORE, + acceptUnlisted: undefined, + }; + this.defaultAgreementSelectorOptions = { + topPoolSize: DEFAULT_AGREEMENT_TOP_POOL_SIZE, + }; + + if (this.config?.preset) { + this.usePreset(this.config.preset); + } + } + + /** + * Apply preset to current reputation system configuration. + * @param presetName Preset name to use. + */ + usePreset(presetName: ReputationPresetName): void { + const presetConfig = REPUTATION_PRESETS[presetName]; + if (!presetConfig) { + throw new GolemReputationError(`Reputation preset not found: ${presetName}`); + } + + if (presetConfig.offerProposalFilter?.weights) { + this.setProposalWeights(presetConfig.offerProposalFilter.weights); + } + + if (presetConfig.offerProposalSelector?.weights) { + this.setAgreementWeights(presetConfig.offerProposalSelector.weights); + } + + this.defaultProposalFilterOptions = { + min: presetConfig.offerProposalFilter?.min ?? this.defaultProposalFilterOptions.min, + acceptUnlisted: presetConfig.offerProposalFilter?.acceptUnlisted, // undefined is meaningful + }; + + this.defaultAgreementSelectorOptions = { + topPoolSize: presetConfig.offerProposalSelector?.topPoolSize ?? this.defaultAgreementSelectorOptions.topPoolSize, + // TODO: to be discussed with the reputation team + // agreementBonus: + // presetConfig.proposalSelector?.agreementBonus ?? this.defaultAgreementSelectorOptions.agreementBonus, + }; } /** @@ -233,8 +331,8 @@ export class ReputationSystem { * Returns a proposal filter that can be used to filter out providers with low reputation scores. * @param opts */ - proposalFilter(opts?: ProposalFilterOptions): ProposalFilter { - return (proposal: Proposal) => { + offerProposalFilter(opts?: ProposalFilterOptions): OfferProposalFilter { + return (proposal: OfferProposal) => { // Filter out rejected operators. const operatorEntry = this.rejectedOperatorsMap.get(proposal.provider.walletAddress); if (operatorEntry) { @@ -258,7 +356,7 @@ export class ReputationSystem { // Filter based on reputation scores. const scoreEntry = this.providersScoreMap.get(proposal.provider.id); if (scoreEntry) { - const min = opts?.min ?? DEFAULT_PROPOSAL_MIN_SCORE; + const min = opts?.min ?? this.defaultProposalFilterOptions.min ?? DEFAULT_PROPOSAL_MIN_SCORE; const score = this.calculateScore(scoreEntry.scores, this.proposalWeights); this.logger.debug(`Proposal score for ${proposal.provider.id}: ${score} - min ${min}`, { provider: proposal.provider, @@ -278,7 +376,11 @@ export class ReputationSystem { ); // Use the acceptUnlisted option if provided, otherwise allow only if there are no known providers. - return opts?.acceptUnlisted ?? this.data.testedProviders.length === 0; + return ( + opts?.acceptUnlisted ?? + this.defaultProposalFilterOptions.acceptUnlisted ?? + this.data.testedProviders.length === 0 + ); }; } @@ -293,36 +395,25 @@ export class ReputationSystem { * * @param opts */ - agreementSelector(opts?: AgreementSelectorOption): AgreementSelector { - return async (candidates): Promise => { - const array = Array.from(candidates); + offerProposalSelector(opts?: ProposalSelectorOptions): OfferProposalSelector { + const poolSize = + opts?.topPoolSize ?? this.defaultAgreementSelectorOptions.topPoolSize ?? DEFAULT_AGREEMENT_TOP_POOL_SIZE; + + return (proposals): OfferProposal => { // Cache scores for providers. const scoresMap = new Map(); - // Sort the array by score - array.sort((a, b) => { - const aId = a.proposal.provider.id; - const bId = b.proposal.provider.id; - - const aScoreData = this.providersScoreMap.get(aId)?.scores ?? {}; - const bScoreData = this.providersScoreMap.get(bId)?.scores ?? {}; - - // Get the score values. - let aScoreValue = scoresMap.get(aId) ?? this.calculateScore(aScoreData, this.agreementWeights); - let bScoreValue = scoresMap.get(bId) ?? this.calculateScore(bScoreData, this.agreementWeights); - - // Store score if not already stored. - if (!scoresMap.has(aId)) scoresMap.set(aId, aScoreValue); - if (!scoresMap.has(bId)) scoresMap.set(bId, bScoreValue); - - // Add bonus for existing agreements. - if (a.agreement) aScoreValue += opts?.agreementBonus ?? 0; - if (b.agreement) bScoreValue += opts?.agreementBonus ?? 0; - - return bScoreValue - aScoreValue; + proposals.forEach((c) => { + const data = this.providersScoreMap.get(c.provider.id)?.scores ?? {}; + const score = this.calculateScore(data, this.agreementWeights); + // TODO: to be discussed with the reputation team + // if (c.agreement) score += opts?.agreementBonus ?? this.defaultAgreementSelectorOptions.agreementBonus ?? 0; + scoresMap.set(c.provider.id, score); }); - const topPool = Math.min(opts?.topPoolSize ?? DEFAULT_AGREEMENT_TOP_POOL_SIZE, array.length); + const array = this.sortCandidatesByScore(proposals, scoresMap); + + const topPool = Math.min(poolSize, array.length); const index = topPool === 1 ? 0 : Math.floor(Math.random() * topPool); return array[index]; @@ -358,10 +449,27 @@ export class ReputationSystem { * @param opts */ calculateProviderPool(opts?: ProposalFilterOptions): ReputationProviderEntry[] { - const min = opts?.min ?? DEFAULT_PROPOSAL_MIN_SCORE; + const min = opts?.min ?? this.defaultProposalFilterOptions.min ?? DEFAULT_PROPOSAL_MIN_SCORE; return this.data.testedProviders.filter((entry) => { const score = this.calculateScore(entry.scores, this.proposalWeights); return score >= min; }); } + + sortCandidatesByScore(proposals: OfferProposal[], scoresMap: Map): OfferProposal[] { + const array = Array.from(proposals); + + array.sort((a, b) => { + const aId = a.provider.id; + const bId = b.provider.id; + + // Get the score values. + const aScoreValue = scoresMap.get(aId) ?? 0; + const bScoreValue = scoresMap.get(bId) ?? 0; + + return bScoreValue - aScoreValue; + }); + + return array; + } } diff --git a/src/experimental/reputation/types.ts b/src/experimental/reputation/types.ts index 6d49874d2..e0397dd69 100644 --- a/src/experimental/reputation/types.ts +++ b/src/experimental/reputation/types.ts @@ -1,5 +1,5 @@ -import { Logger } from "../../utils"; -import { ProviderInfo } from "../../agreement"; +import { Logger } from "../../shared/utils"; +import { ProviderInfo } from "../../market/agreement"; /** * Set of normalized scores for a provider. @@ -32,6 +32,7 @@ export interface ReputationProviderEntry { /** * Information about a rejected operator. + * @experimental */ export interface ReputationRejectedOperator { operator: { @@ -42,6 +43,7 @@ export interface ReputationRejectedOperator { /** * Information about a rejected provider. + * @experimental */ export interface ReputationRejectedProvider { provider: ProviderInfo; @@ -50,6 +52,7 @@ export interface ReputationRejectedProvider { /** * Information about untested provider. + * @experimental */ export interface ReputationUntestedProvider { provider: ProviderInfo; @@ -93,7 +96,7 @@ export interface ProposalFilterOptions { * Options for the agreement selector. * @experimental */ -export interface AgreementSelectorOption { +export interface ProposalSelectorOptions { /** * The size of top provider pool used to pick a random one. * @@ -102,20 +105,49 @@ export interface AgreementSelectorOption { * Default is `DEFAULT_AGREEMENT_TOP_POOL_SIZE`. */ topPoolSize?: number; - - /** - * Add extra score to provider if it has an existing agreement. - * - * Default is 0. - */ - agreementBonus?: number; } /** * Weights used to calculate the score for providers. + * @experimental */ export type ReputationWeights = Partial; +/** + * Mixin for objects with reputation weights. + * @experimental + */ +export interface ReputationWeightsMixin { + weights?: ReputationWeights; +} + +/** + * Preset configuration for reputation system. + * + * @experimental + */ +export interface ReputationPreset { + offerProposalFilter?: ProposalFilterOptions & ReputationWeightsMixin; + offerProposalSelector?: ProposalSelectorOptions & ReputationWeightsMixin; +} + +/** + * Interface for predefined reputation presets. + * + * @experimental + */ +export interface ReputationPresets { + compute: ReputationPreset; + service: ReputationPreset; +} + +/** + * Names of predefined reputation presets. + * + * @experimental + */ +export type ReputationPresetName = keyof ReputationPresets; + /** * Configuration for ReputationSystem class. * @@ -140,4 +172,6 @@ export interface ReputationConfig { * Logger to use. */ logger?: Logger; + + preset?: ReputationPresetName; } diff --git a/src/golem-network/golem-network.test.ts b/src/golem-network/golem-network.test.ts new file mode 100644 index 000000000..7406d8dea --- /dev/null +++ b/src/golem-network/golem-network.test.ts @@ -0,0 +1,260 @@ +import { Subject } from "rxjs"; +import { ActivityModuleImpl } from "../activity"; +import { RentalModuleImpl, ResourceRental, ResourceRentalPool } from "../resource-rental"; +import { DraftOfferProposalPool, MarketModuleImpl, OfferProposal } from "../market"; +import { NetworkModuleImpl } from "../network"; +import { Allocation, PaymentModuleImpl } from "../payment"; +import { YagnaApi, sleep } from "../shared/utils"; +import { MarketApiAdapter, PaymentApiAdapter } from "../shared/yagna"; +import { ActivityApiAdapter } from "../shared/yagna/adapters/activity-api-adapter"; +import { GolemNetwork, MarketOrderSpec } from "./golem-network"; +import { _, instance, mock, reset, spy, verify, when } from "@johanblumenberg/ts-mockito"; +import { GftpStorageProvider } from "../shared/storage"; + +const order: MarketOrderSpec = Object.freeze({ + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }, +} as const); +const mockMarket = mock(MarketModuleImpl); +const mockPayment = mock(PaymentModuleImpl); +const mockActivity = mock(ActivityModuleImpl); +const mockNetwork = mock(NetworkModuleImpl); +const mockRental = mock(RentalModuleImpl); +const mockYagna = mock(YagnaApi); +const mockPaymentApi = mock(PaymentApiAdapter); +const mockActivityApi = mock(ActivityApiAdapter); +const mockMarketApi = mock(MarketApiAdapter); +const mockStorageProvider = mock(GftpStorageProvider); + +afterEach(() => { + reset(mockYagna); + reset(mockActivity); + reset(mockMarket); + reset(mockPayment); + reset(mockNetwork); + reset(mockRental); + reset(mockPaymentApi); + reset(mockActivityApi); + reset(mockMarketApi); + reset(mockStorageProvider); + + jest.clearAllMocks(); +}); +function getGolemNetwork() { + return new GolemNetwork({ + override: { + yagna: instance(mockYagna), + activity: instance(mockActivity), + market: instance(mockMarket), + payment: instance(mockPayment), + network: instance(mockNetwork), + rental: instance(mockRental), + paymentApi: instance(mockPaymentApi), + activityApi: instance(mockActivityApi), + marketApi: instance(mockMarketApi), + storageProvider: instance(mockStorageProvider), + }, + }); +} + +describe("Golem Network", () => { + describe("oneOf()", () => { + it("should create a rental and clean it up when disconnected", async () => { + const mockResourceRental = mock(ResourceRental); + const mockResourceRentalInstance = instance(mockResourceRental); + + when(mockResourceRental.stopAndFinalize()).thenResolve(); + when(mockRental.createResourceRental(_, _, _)).thenReturn(mockResourceRentalInstance); + + const draftProposal$ = new Subject(); + when(mockMarket.collectDraftOfferProposals(_)).thenReturn(draftProposal$); + + const allocation = instance(mock(Allocation)); + when(mockPayment.createAllocation(_)).thenResolve(allocation); + + jest.spyOn(DraftOfferProposalPool.prototype, "acquire").mockResolvedValue({} as OfferProposal); + + const glm = getGolemNetwork(); + await glm.connect(); + + const rental = await glm.oneOf({ order }); + + expect(rental).toBe(mockResourceRentalInstance); + + await glm.disconnect(); + + verify(mockResourceRental.stopAndFinalize()).once(); + verify(mockPayment.releaseAllocation(allocation)).once(); + }); + it("should not release the allocation if it was provided by the user", async () => { + const allocation = instance(mock(Allocation)); + + const mockResourceRental = mock(ResourceRental); + const mockResourceRentalInstance = instance(mockResourceRental); + when(mockResourceRental.stopAndFinalize()).thenResolve(); + when(mockRental.createResourceRental(_, _, _)).thenReturn(mockResourceRentalInstance); + + when(mockMarket.collectDraftOfferProposals(_)).thenReturn(new Subject()); + jest.spyOn(DraftOfferProposalPool.prototype, "acquire").mockResolvedValue({} as OfferProposal); + + const glm = getGolemNetwork(); + await glm.connect(); + + const rental = await glm.oneOf({ + order: { + ...order, + payment: { + allocation, + }, + }, + }); + + expect(rental).toBe(mockResourceRentalInstance); + + await glm.disconnect(); + + verify(mockResourceRental.stopAndFinalize()).once(); + verify(mockPayment.createAllocation(_)).never(); + verify(mockPayment.releaseAllocation(allocation)).never(); + }); + }); + + describe("manyOf()", () => { + it("should create a pool and clean it up when disconnected", async () => { + const allocation = instance(mock(Allocation)); + when(mockPayment.createAllocation(_)).thenResolve(allocation); + + const draftProposal$ = new Subject(); + when(mockMarket.collectDraftOfferProposals(_)).thenReturn(draftProposal$); + + const mockRentalPool = mock(ResourceRentalPool); + when(mockRentalPool.drainAndClear()).thenResolve(); + const rentalPool = instance(mockRentalPool); + when(mockRental.createResourceRentalPool(_, _, _)).thenReturn(rentalPool); + + const glm = getGolemNetwork(); + + await glm.connect(); + + const pool = await glm.manyOf({ + poolSize: 3, + order, + }); + + expect(pool).toBe(rentalPool); + + await glm.disconnect(); + + verify(mockRentalPool.drainAndClear()).once(); + verify(mockPayment.releaseAllocation(allocation)).once(); + }); + it("should not release the allocation if it was provided by the user", async () => { + const allocation = instance(mock(Allocation)); + + when(mockMarket.collectDraftOfferProposals(_)).thenReturn(new Subject()); + const mockRentalPool = mock(ResourceRentalPool); + when(mockRentalPool.drainAndClear()).thenResolve(); + const rentalPool = instance(mockRentalPool); + when(mockRental.createResourceRentalPool(_, _, _)).thenReturn(rentalPool); + + const glm = getGolemNetwork(); + await glm.connect(); + + const pool = await glm.manyOf({ + poolSize: 3, + order: { + ...order, + payment: { + allocation, + }, + }, + }); + + expect(pool).toBe(rentalPool); + await glm.disconnect(); + verify(mockRentalPool.drainAndClear()).once(); + verify(mockPayment.createAllocation(_)).never(); + verify(mockPayment.releaseAllocation(allocation)).never(); + }); + }); + describe("disconnect()", () => { + it("reuses the same promise if called multiple times", async () => { + const glm = getGolemNetwork(); + await glm.connect(); + const glmSpy = spy(glm); + when(glmSpy["startDisconnect"]()).thenResolve(); + expect(glm["disconnectPromise"]).toBeUndefined(); + const promise1 = glm.disconnect(); + const promise2 = glm.disconnect(); + const promise3 = glm.disconnect(); + expect(glm["disconnectPromise"]).toBeDefined(); + await Promise.all([promise1, promise2, promise3]); + verify(glmSpy["startDisconnect"]()).once(); + expect(glm["disconnectPromise"]).toBeUndefined(); + }); + it("stops any oneOf() rentals that are in the process of being created", async () => { + const allocation = instance(mock(Allocation)); + when(mockPayment.createAllocation(_)).thenResolve(allocation); + when(mockPayment.releaseAllocation(allocation)).thenResolve(); + + // simulate some operation taking a long time + when(mockPayment.createAllocation(_)).thenCall(async () => { + await sleep(100, true); + return allocation; + }); + + const glm = getGolemNetwork(); + await glm.connect(); + + expect.assertions(1); + const rentalPromise = glm + .oneOf({ order }) + .then(() => { + throw new Error("Rental should not be created"); + }) + .catch((err) => { + expect(err).toBe("Golem Network is disconnecting"); + }); + await glm.disconnect(); + await rentalPromise; + verify(mockPayment.releaseAllocation(allocation)).once(); + }); + it("stops any manyOf() rentals that are in the process of being created", async () => { + const allocation = instance(mock(Allocation)); + when(mockPayment.createAllocation(_)).thenResolve(allocation); + when(mockPayment.releaseAllocation(allocation)).thenResolve(); + + // simulate some operation taking a long time + when(mockPayment.createAllocation(_)).thenCall(async () => { + await sleep(100, true); + return allocation; + }); + + const glm = getGolemNetwork(); + await glm.connect(); + + expect.assertions(1); + const rentalPromise = glm + .manyOf({ poolSize: 3, order }) + .then(() => { + throw new Error("Pool should not be created"); + }) + .catch((err) => { + expect(err).toBe("Golem Network is disconnecting"); + }); + await glm.disconnect(); + await rentalPromise; + verify(mockPayment.releaseAllocation(allocation)).once(); + }); + }); +}); diff --git a/src/golem-network/golem-network.ts b/src/golem-network/golem-network.ts new file mode 100644 index 000000000..1e40d30f8 --- /dev/null +++ b/src/golem-network/golem-network.ts @@ -0,0 +1,660 @@ +import { anyAbortSignal, createAbortSignalFromTimeout, defaultLogger, isNode, Logger, YagnaApi } from "../shared/utils"; +import { + Demand, + DraftOfferProposalPool, + IMarketApi, + MarketModule, + MarketModuleImpl, + MarketModuleOptions, + OfferProposal, + OrderMarketOptions, +} from "../market"; +import { Allocation, IPaymentApi, PaymentModule, PaymentModuleImpl, PaymentModuleOptions } from "../payment"; +import { ActivityModule, ActivityModuleImpl, ExeUnitOptions, IActivityApi, IFileServer } from "../activity"; +import { INetworkApi, Network, NetworkModule, NetworkModuleImpl, NetworkNode, NetworkOptions } from "../network"; +import { EventEmitter } from "eventemitter3"; +import { + PoolSize, + RentalModule, + RentalModuleImpl, + ResourceRental, + ResourceRentalOptions, + ResourceRentalPool, +} from "../resource-rental"; +import { DebitNoteRepository, InvoiceRepository, MarketApiAdapter, PaymentApiAdapter } from "../shared/yagna"; +import { ActivityApiAdapter } from "../shared/yagna/adapters/activity-api-adapter"; +import { ActivityRepository } from "../shared/yagna/repository/activity-repository"; +import { AgreementRepository } from "../shared/yagna/repository/agreement-repository"; +import { ProposalRepository } from "../shared/yagna/repository/proposal-repository"; +import { CacheService } from "../shared/cache/CacheService"; +import { DemandRepository } from "../shared/yagna/repository/demand-repository"; +import { IDemandRepository, OrderDemandOptions } from "../market/demand"; +import { GftpServerAdapter } from "../shared/storage/GftpServerAdapter"; +import { + GftpStorageProvider, + NullStorageProvider, + StorageProvider, + WebSocketBrowserStorageProvider, +} from "../shared/storage"; +import { DataTransferProtocol } from "../shared/types"; +import { NetworkApiAdapter } from "../shared/yagna/adapters/network-api-adapter"; +import { IProposalRepository } from "../market/proposal"; +import { Subscription } from "rxjs"; + +/** + * Instance of an object or a factory function that you can call `new` on. + * Optionally you can provide constructor arguments. + */ +export type InstanceOrFactory = + | TargetInterface + | { new (...args: ConstructorArgs): TargetInterface }; + +/** + * If no override is provided, return a function that creates a new instance of the default factory. + * If override is a factory, return a function that creates a new instance of that factory. + * If override is an instance, return a function that returns that instance (ignoring the arguments). + */ +function getFactory< + DefaultFactoryConstructorArgs extends unknown[], + InstanceType extends object, + FactoryType extends { new (...args: DefaultFactoryConstructorArgs): InstanceType }, +>( + defaultFactory: FactoryType, + override: InstanceOrFactory | undefined, +): (...args: ConstructorParameters) => InstanceType { + if (override) { + if (typeof override === "function") { + return (...args) => new override(...args); + } + return () => override; + } + return (...args) => new defaultFactory(...args); +} + +export interface GolemNetworkOptions { + /** + * Logger instance to use for logging. + * If no logger is provided you can view debug logs by setting the + * `DEBUG` environment variable to `golem-js:*`. + */ + logger?: Logger; + + /** + * Set the API key and URL for the Yagna API. + */ + api?: { + key?: string; + url?: string; + }; + + /** + * Set payment-related options. + * + * This is where you can specify the network, payment driver and more. + * By default, the network is set to the `holesky` test network. + */ + payment?: Partial; + + /** + * Set market related options. + * + * This is where you can globally specify several options that determine how the SDK will + * interact with the market. + */ + market?: Partial; + + /** + * Set the data transfer protocol to use for file transfers. + * Default is `gftp`. + */ + dataTransferProtocol?: DataTransferProtocol; + + /** + * Override some of the services used by the GolemNetwork instance. + * This is useful for testing or when you want to provide your own implementation of some services. + * Only set this if you know what you are doing. + * To override a module you can pass either an instance of an object or a factory function (that we can call `new` on). + */ + override?: Partial< + GolemServices & { + market: InstanceOrFactory; + payment: InstanceOrFactory; + activity: InstanceOrFactory; + network: InstanceOrFactory; + rental: InstanceOrFactory; + } + >; +} + +type AllocationOptions = { + /** + * Optionally pass an existing allocation to use or an ID of an allocation that already exists in yagna. + * If this is not provided, a new allocation will be created based on an estimated budget. + */ + allocation?: Allocation | string; +}; + +/** + * Represents the order specifications which will result in access to ResourceRental. + */ +export interface MarketOrderSpec { + demand: OrderDemandOptions; + market: OrderMarketOptions; + activity?: ResourceRentalOptions["activity"]; + payment?: ResourceRentalOptions["payment"] & AllocationOptions; + /** The network that should be used for communication between the resources rented as part of this order */ + network?: Network; +} + +export interface GolemNetworkEvents { + /** Fires when all startup operations related to GN are completed */ + connected: () => void; + + /** Fires when an error will be encountered */ + error: (error: Error) => void; + + /** Fires when all shutdown operations related to GN are completed */ + disconnected: () => void; +} + +export interface OneOfOptions { + order: MarketOrderSpec; + signalOrTimeout?: number | AbortSignal; + setup?: ExeUnitOptions["setup"]; + teardown?: ExeUnitOptions["teardown"]; +} + +export interface ManyOfOptions { + order: MarketOrderSpec; + poolSize: PoolSize; + setup?: ExeUnitOptions["setup"]; + teardown?: ExeUnitOptions["teardown"]; +} + +/** + * Dependency Container + */ +export type GolemServices = { + yagna: YagnaApi; + logger: Logger; + paymentApi: IPaymentApi; + activityApi: IActivityApi; + marketApi: IMarketApi; + networkApi: INetworkApi; + proposalCache: CacheService; + proposalRepository: IProposalRepository; + demandRepository: IDemandRepository; + fileServer: IFileServer; + storageProvider: StorageProvider; +}; + +/** + * General purpose and high-level API for the Golem Network + * + * This class is the main entry-point for developers that would like to build on Golem Network + * using `@golem-sdk/golem-js`. It is supposed to provide an easy access API for use 80% of use cases. + */ +export class GolemNetwork { + public readonly events = new EventEmitter(); + + public readonly options: GolemNetworkOptions; + + private readonly logger: Logger; + + private readonly yagna: YagnaApi; + + public readonly market: MarketModule; + public readonly payment: PaymentModule; + public readonly activity: ActivityModule; + public readonly network: NetworkModule; + public readonly rental: RentalModule; + + /** + * Dependency Container + */ + public readonly services: GolemServices; + + private hasConnection = false; + private disconnectPromise: Promise | undefined; + private abortController = new AbortController(); + + private readonly storageProvider: StorageProvider; + + /** + * List af additional tasks that should be executed when the network is being shut down + * (for example finalizing resource rental created with `oneOf`) + */ + private cleanupTasks: (() => Promise | void)[] = []; + + constructor(options: Partial = {}) { + const optDefaults: GolemNetworkOptions = { + dataTransferProtocol: isNode ? "gftp" : "ws", + }; + + this.options = { + ...optDefaults, + ...options, + }; + + this.logger = options.logger ?? defaultLogger("golem-network"); + + this.logger.debug("Creating Golem Network instance with options", { options: this.options }); + + try { + this.yagna = + options.override?.yagna || + new YagnaApi({ + logger: this.logger, + apiKey: this.options.api?.key, + basePath: this.options.api?.url, + }); + + this.storageProvider = options.override?.storageProvider || this.createStorageProvider(); + + const demandCache = new CacheService(); + const proposalCache = new CacheService(); + + const demandRepository = new DemandRepository(this.yagna.market, demandCache); + const proposalRepository = new ProposalRepository(this.yagna.market, this.yagna.identity, proposalCache); + const agreementRepository = new AgreementRepository(this.yagna.market, demandRepository); + + this.services = { + logger: this.logger, + yagna: this.yagna, + storageProvider: this.storageProvider, + demandRepository, + proposalCache, + proposalRepository, + paymentApi: + this.options.override?.paymentApi || + new PaymentApiAdapter( + this.yagna, + new InvoiceRepository(this.yagna.payment, this.yagna.market), + new DebitNoteRepository(this.yagna.payment, this.yagna.market), + this.logger, + ), + activityApi: + this.options.override?.activityApi || + new ActivityApiAdapter( + this.yagna.activity.state, + this.yagna.activity.control, + this.yagna.activity.exec, + new ActivityRepository(this.yagna.activity.state, agreementRepository), + ), + marketApi: + this.options.override?.marketApi || + new MarketApiAdapter(this.yagna, agreementRepository, proposalRepository, demandRepository, this.logger), + networkApi: this.options.override?.networkApi || new NetworkApiAdapter(this.yagna), + fileServer: this.options.override?.fileServer || new GftpServerAdapter(this.storageProvider), + }; + this.network = getFactory(NetworkModuleImpl, this.options.override?.network)(this.services); + this.market = getFactory(MarketModuleImpl, this.options.override?.market)( + { + ...this.services, + networkModule: this.network, + }, + this.options.market, + ); + this.payment = getFactory(PaymentModuleImpl, this.options.override?.payment)(this.services, this.options.payment); + this.activity = getFactory(ActivityModuleImpl, this.options.override?.activity)(this.services); + this.rental = getFactory( + RentalModuleImpl, + this.options.override?.rental, + )({ + activityModule: this.activity, + paymentModule: this.payment, + marketModule: this.market, + networkModule: this.network, + logger: this.logger, + storageProvider: this.storageProvider, + }); + } catch (err) { + this.events.emit("error", err); + throw err; + } + } + + /** + * "Connects" to the network by initializing the underlying components required to perform operations on Golem Network + * + * @return Resolves when all initialization steps are completed + */ + async connect() { + try { + await this.yagna.connect(); + await this.services.paymentApi.connect(); + await this.storageProvider.init(); + this.events.emit("connected"); + this.hasConnection = true; + } catch (err) { + this.events.emit("error", err); + throw err; + } + } + + private async startDisconnect() { + try { + this.abortController.abort("Golem Network is disconnecting"); + await Promise.allSettled(this.cleanupTasks.map((task) => task())); + this.cleanupTasks = []; + await this.storageProvider.close(); + await this.yagna.disconnect(); + this.services.proposalCache.flushAll(); + this.abortController = new AbortController(); + } catch (err) { + this.logger.error("Error while disconnecting", err); + throw err; + } finally { + this.events.emit("disconnected"); + this.hasConnection = false; + } + } + + /** + * "Disconnects" from the Golem Network + * + * @return Resolves when all shutdown steps are completed + */ + async disconnect() { + if (!this.isConnected()) { + return; + } + if (this.disconnectPromise) { + return this.disconnectPromise; + } + this.disconnectPromise = this.startDisconnect().finally(() => { + this.disconnectPromise = undefined; + }); + return this.disconnectPromise; + } + + private async getAllocationFromOrder({ + order, + maxAgreements, + }: { + order: MarketOrderSpec; + maxAgreements: number; + }): Promise { + if (!order.payment?.allocation) { + const budget = this.market.estimateBudget({ order, maxAgreements }); + + /** + * We need to create allocations that will exist longer than the agreements made. + * + * Without this in the event of agreement termination due to its expiry, + * the invoice for the agreement arrives, and we try to accept the invoice with + * an allocation that already expired (had the same expiration time as the agreement), + * which leads to unpaid invoices. + */ + const EXPIRATION_BUFFER_MINUTES = 15; + + return this.payment.createAllocation({ + budget, + expirationSec: order.market.rentHours * (60 + EXPIRATION_BUFFER_MINUTES) * 60, + }); + } + + if (typeof order.payment.allocation === "string") { + return this.payment.getAllocation(order.payment.allocation); + } + + return order.payment.allocation; + } + + /** + * Define your computational resource demand and access a single instance + * + * Use Case: Get a single instance of a resource from the market to execute operations on + * + * @example + * ```ts + * const rental = await glm.oneOf({ order }); + * await rental + * .getExeUnit() + * .then((exe) => exe.run("echo Hello, Golem! πŸ‘‹")) + * .then((res) => console.log(res.stdout)); + * await rental.stopAndFinalize(); + * ``` + * + * @param {Object} options + * @param options.order - represents the order specifications which will result in access to ResourceRental. + * @param options.signalOrTimeout - timeout in milliseconds or an AbortSignal that will be used to cancel the rental request + * @param options.setup - an optional function that is called as soon as the exe unit is ready + * @param options.teardown - an optional function that is called before the exe unit is destroyed + */ + async oneOf({ order, setup, teardown, signalOrTimeout }: OneOfOptions): Promise { + const signal = anyAbortSignal(createAbortSignalFromTimeout(signalOrTimeout), this.abortController.signal); + + let allocation: Allocation | undefined = undefined; + let proposalSubscription: Subscription | undefined = undefined; + let rental: ResourceRental | undefined = undefined; + let networkNode: NetworkNode | undefined = undefined; + + const cleanup = async () => { + if (proposalSubscription) { + proposalSubscription.unsubscribe(); + } + // First finalize the rental (which will wait for all payments to be processed) + // and only then release the allocation + if (rental) { + await rental.stopAndFinalize().catch((err) => this.logger.error("Error while finalizing rental", err)); + } + if (order.network && networkNode) { + await this.network + .removeNetworkNode(order.network, networkNode) + .catch((err) => this.logger.error("Error while removing network node", err)); + } + // Don't release the allocation if it was provided by the user + if (order.payment?.allocation || !allocation) { + return; + } + await this.payment + .releaseAllocation(allocation) + .catch((err) => this.logger.error("Error while releasing allocation", err)); + }; + try { + const proposalPool = new DraftOfferProposalPool({ + logger: this.logger, + validateOfferProposal: order.market.offerProposalFilter, + selectOfferProposal: order.market.offerProposalSelector, + }); + + allocation = await this.getAllocationFromOrder({ order, maxAgreements: 1 }); + signal.throwIfAborted(); + + const demandSpecification = await this.market.buildDemandDetails(order.demand, order.market, allocation); + const draftProposal$ = this.market.collectDraftOfferProposals({ + demandSpecification, + pricing: order.market.pricing, + filter: order.market.offerProposalFilter, + }); + + proposalSubscription = proposalPool.readFrom(draftProposal$); + const agreement = await this.market.signAgreementFromPool( + proposalPool, + { + expirationSec: order.market.rentHours * 60 * 60, + }, + signal, + ); + + networkNode = order.network + ? await this.network.createNetworkNode(order.network, agreement.provider.id) + : undefined; + + rental = this.rental.createResourceRental(agreement, allocation, { + payment: order.payment, + activity: order.activity, + networkNode, + exeUnit: { setup, teardown }, + }); + + // We managed to create the activity, no need to look for more agreement candidates + proposalSubscription.unsubscribe(); + + this.cleanupTasks.push(cleanup); + + return rental; + } catch (err) { + this.logger.error("Error while creating rental", err); + // if the execution was interrupted before we got the chance to add the cleanup task + // we need to call it manually + await cleanup(); + throw err; + } + } + + /** + * Define your computational resource demand and access a pool of instances. + * The pool will grow up to the specified poolSize. + * + * @example + * ```ts + * // create a pool that can grow up to 3 rentals at the same time + * const pool = await glm.manyOf({ + * poolSize: 3, + * demand + * }); + * await Promise.allSettled([ + * pool.withRental(async (rental) => + * rental + * .getExeUnit() + * .then((exe) => exe.run("echo Hello, Golem from the first machine! πŸ‘‹")) + * .then((res) => console.log(res.stdout)), + * ), + * pool.withRental(async (rental) => + * rental + * .getExeUnit() + * .then((exe) => exe.run("echo Hello, Golem from the second machine! πŸ‘‹")) + * .then((res) => console.log(res.stdout)), + * ), + * pool.withRental(async (rental) => + * rental + * .getExeUnit() + * .then((exe) => exe.run("echo Hello, Golem from the third machine! πŸ‘‹")) + * .then((res) => console.log(res.stdout)), + * ), + * ]); + * ``` + * + * @param {Object} options + * @param options.order - represents the order specifications which will result in access to LeaseProcess. + * @param options.poolSize {Object | number} - can be defined as a number or an object with min and max fields, if defined as a number it will be treated as a min parameter. + * @param options.poolSize.min - the minimum pool size to achieve ready state (default = 0) + * @param options.poolSize.max - the maximum pool size, if reached, the next pool element will only be available if the borrowed resource is released or destroyed (dafault = 100) + * @param options.setup - an optional function that is called as soon as the exe unit is ready + * @param options.teardown - an optional function that is called before the exe unit is destroyed + */ + public async manyOf({ poolSize, order, setup, teardown }: ManyOfOptions): Promise { + const signal = this.abortController.signal; + let allocation: Allocation | undefined = undefined; + let resourceRentalPool: ResourceRentalPool | undefined = undefined; + let subscription: Subscription | undefined = undefined; + + const cleanup = async () => { + if (subscription) { + subscription.unsubscribe(); + } + // First drain the pool (which will wait for all rentals to be paid for + // and only then release the allocation + if (resourceRentalPool) { + await resourceRentalPool + .drainAndClear() + .catch((err) => this.logger.error("Error while draining resource rental pool", err)); + } + // Don't release the allocation if it was provided by the user + if (order.payment?.allocation || !allocation) { + return; + } + await this.payment + .releaseAllocation(allocation) + .catch((err) => this.logger.error("Error while releasing allocation", err)); + }; + + try { + const proposalPool = new DraftOfferProposalPool({ + logger: this.logger, + validateOfferProposal: order.market.offerProposalFilter, + selectOfferProposal: order.market.offerProposalSelector, + }); + + const maxAgreements = typeof poolSize === "number" ? poolSize : poolSize?.max ?? poolSize?.min ?? 1; + allocation = await this.getAllocationFromOrder({ order, maxAgreements }); + signal.throwIfAborted(); + + const demandSpecification = await this.market.buildDemandDetails(order.demand, order.market, allocation); + + const draftProposal$ = this.market.collectDraftOfferProposals({ + demandSpecification, + pricing: order.market.pricing, + filter: order.market.offerProposalFilter, + }); + subscription = proposalPool.readFrom(draftProposal$); + + const rentSeconds = order.market.rentHours * 60 * 60; + + resourceRentalPool = this.rental.createResourceRentalPool(proposalPool, allocation, { + poolSize, + network: order.network, + resourceRentalOptions: { + activity: order.activity, + payment: order.payment, + exeUnit: { setup, teardown }, + }, + agreementOptions: { + expirationSec: rentSeconds, + }, + }); + + this.cleanupTasks.push(cleanup); + + return resourceRentalPool; + } catch (err) { + this.logger.error("Error while creating rental pool", err); + // if the execution was interrupted before we got the chance to add the cleanup task + // we need to call it manually + await cleanup(); + throw err; + } + } + + isConnected() { + return this.hasConnection; + } + + /** + * Creates a new logical network within the Golem VPN infrastructure. + * Allows communication between network nodes using standard network mechanisms, + * but requires specific implementation in the ExeUnit/runtime, + * which must be capable of providing a standard Unix-socket interface to their payloads + * and marshaling the logical network traffic through the Golem Net transport layer + * @param options + */ + async createNetwork(options?: NetworkOptions): Promise { + return await this.network.createNetwork(options); + } + + /** + * Removes an existing network from the Golem VPN infrastructure. + * @param network + */ + async destroyNetwork(network: Network): Promise { + return await this.network.removeNetwork(network); + } + + private createStorageProvider(): StorageProvider { + if (typeof this.options.dataTransferProtocol === "string") { + switch (this.options.dataTransferProtocol) { + case "ws": + return new WebSocketBrowserStorageProvider(this.yagna, {}); + case "gftp": + default: + return new GftpStorageProvider(); + } + } else if (this.options.dataTransferProtocol !== undefined) { + return this.options.dataTransferProtocol; + } else { + return new NullStorageProvider(); + } + } +} diff --git a/src/golem-network/index.ts b/src/golem-network/index.ts new file mode 100644 index 000000000..2a17939a9 --- /dev/null +++ b/src/golem-network/index.ts @@ -0,0 +1 @@ +export * from "./golem-network"; diff --git a/src/golem_network/golem_network.ts b/src/golem_network/golem_network.ts deleted file mode 100644 index 862b4f17f..000000000 --- a/src/golem_network/golem_network.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { v4 } from "uuid"; -import { Job } from "../job"; -import { Yagna, YagnaApi, YagnaOptions } from "../utils"; -import { RunJobOptions } from "../job/job"; -import { GolemUserError } from "../error/golem-error"; - -export type GolemNetworkConfig = Partial & { yagna?: YagnaOptions }; - -/** - * @experimental This API is experimental and subject to change. Use at your own risk. - * - * The Golem Network class provides a high-level API for running jobs on the Golem Network. - */ -export class GolemNetwork { - private yagna: Yagna; - private api: YagnaApi | null = null; - - private jobs = new Map(); - - /** - * @param config - Configuration options that will be passed to all jobs created by this instance. - */ - constructor(private readonly config: GolemNetworkConfig) { - this.yagna = new Yagna(this.config.yagna); - } - - public isInitialized() { - return this.api !== null; - } - - public async init() { - await this.yagna.connect(); - this.api = this.yagna.getApi(); - } - - /** - * Create a new job and add it to the list of jobs managed by this instance. - * This method does not start any work on the network, use {@link Job.startWork} for that. - * - * @param options - Configuration options for the job. These options will be merged with the options passed to the constructor. - */ - public createJob(options: RunJobOptions = {}) { - this.checkInitialization(); - - const jobId = v4(); - const job = new Job(jobId, this.api!, { ...this.config, ...options }); - this.jobs.set(jobId, job); - - return job; - } - - public getJobById(id: string) { - this.checkInitialization(); - - return this.jobs.get(id); - } - - /** - * Close the connection to the Yagna service and cancel all running jobs. - */ - public async close() { - const pendingJobs = Array.from(this.jobs.values()).filter((job) => job.isRunning()); - await Promise.allSettled(pendingJobs.map((job) => job.cancel())); - await this.yagna.end(); - this.api = null; - } - - private checkInitialization() { - if (!this.isInitialized()) { - throw new GolemUserError("GolemNetwork not initialized, please run init() first"); - } - } -} diff --git a/src/golem_network/index.ts b/src/golem_network/index.ts deleted file mode 100644 index 1ef6df135..000000000 --- a/src/golem_network/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { GolemNetwork, GolemNetworkConfig } from "./golem_network"; diff --git a/src/index.ts b/src/index.ts index 398bcbf36..e4b53295b 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,18 @@ -export * from "./executor"; -export * from "./storage"; -export * from "./activity"; -export * from "./agreement"; +// High-level entry points +export * from "./golem-network"; +export * from "./resource-rental"; + +// Low level entry points for advanced users export * from "./market"; -export * from "./package"; export * from "./payment"; export * from "./network"; -export * from "./events"; -export * from "./utils"; -export * from "./utils/yagna/yagna"; -export * from "./task"; -export * from "./error/golem-error"; -export { StatsService } from "./stats/service"; -export { TcpProxy } from "./network/tcpProxy"; +export * from "./activity"; + +// Necessary domain entities for users to consume +export * from "./shared/error/golem-error"; +export * from "./network/tcpProxy"; + +// Internals +export * from "./shared/utils"; +export * from "./shared/yagna"; +export * from "./shared/storage"; diff --git a/src/job/index.ts b/src/job/index.ts deleted file mode 100644 index aa289b22d..000000000 --- a/src/job/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Job, JobState } from "./job"; diff --git a/src/job/job.test.ts b/src/job/job.test.ts deleted file mode 100644 index 8a82a9f55..000000000 --- a/src/job/job.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Job } from "./job"; -import { YagnaMock } from "../../tests/mock/"; -import { Agreement, AgreementPoolService } from "../agreement"; -import { TaskService, WorkContext } from "../task"; -import { NetworkNode, NetworkService } from "../network"; -import { Activity } from "../activity"; -import { Package } from "../package"; - -jest.mock("../payment"); -jest.mock("../market"); - -afterEach(() => { - jest.clearAllMocks(); -}); - -describe("Job", () => { - describe("cancel()", () => { - it("stops the activity and releases the agreement when canceled", async () => { - jest.spyOn(AgreementPoolService.prototype, "run").mockResolvedValue(); - jest.spyOn(TaskService.prototype, "run").mockResolvedValue(); - jest.spyOn(NetworkService.prototype, "run").mockResolvedValue(); - jest.spyOn(Package, "create").mockReturnValue({} as unknown as Package); - jest.spyOn(WorkContext.prototype, "before").mockResolvedValue(); - jest.spyOn(AgreementPoolService.prototype, "releaseAgreement").mockResolvedValue(); - jest.spyOn(NetworkService.prototype, "addNode").mockResolvedValue({} as unknown as NetworkNode); - - const mockAgreement = { - id: "test_agreement_id", - getProviderInfo: () => ({ - id: "test_provider_id", - }), - } as Agreement; - const mockActivity = { - stop: jest.fn(), - agreement: mockAgreement, - } as unknown as Activity; - - jest.spyOn(AgreementPoolService.prototype, "getAgreement").mockResolvedValue(mockAgreement); - jest.spyOn(Activity, "create").mockResolvedValue(mockActivity); - - const yagna = new YagnaMock().getApi(); - const job = new Job("test_id", yagna, { - package: { - imageTag: "test_image", - }, - }); - - job.startWork(() => { - return new Promise((resolve) => setTimeout(resolve, 10000)); - }); - await job.cancel(); - - await expect(job.waitForResult()).rejects.toThrow("Canceled"); - - expect(mockActivity.stop).toHaveBeenCalled(); - expect(AgreementPoolService.prototype.releaseAgreement).toHaveBeenCalledWith(mockAgreement.id, false); - }); - }); -}); diff --git a/src/job/job.ts b/src/job/job.ts deleted file mode 100644 index a26b668d1..000000000 --- a/src/job/job.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { TaskState as JobState } from "../task/task"; -import { WorkContext, Worker, WorkOptions } from "../task/work"; -import { runtimeContextChecker, YagnaApi } from "../utils"; -import { AgreementOptions, AgreementPoolService } from "../agreement"; -import { MarketOptions, MarketService } from "../market"; -import { NetworkService } from "../network"; -import { PaymentOptions, PaymentService } from "../payment"; -import { NetworkOptions } from "../network/network"; -import { Package, PackageOptions } from "../package"; -import { Activity, ActivityOptions } from "../activity"; -import { EventEmitter } from "eventemitter3"; -import { GftpStorageProvider, NullStorageProvider, StorageProvider, WebSocketBrowserStorageProvider } from "../storage"; -import { GolemAbortError, GolemConfigError, GolemUserError } from "../error/golem-error"; - -export { TaskState as JobState } from "../task/task"; - -export type RunJobOptions = { - market?: MarketOptions; - payment?: PaymentOptions; - agreement?: AgreementOptions; - network?: NetworkOptions; - package?: PackageOptions; - activity?: ActivityOptions; - work?: WorkOptions; -}; - -export interface JobEventsDict { - /** - * Emitted immediately after the job is created and initialization begins. - */ - created: () => void; - /** - * Emitted when the job finishes initialization and work begins. - */ - started: () => void; - /** - * Emitted when the job completes successfully and cleanup begins. - */ - success: () => void; - /** - * Emitted when the job fails and cleanup begins. - */ - error: (error: Error) => void; - /** - * Emitted when the job is canceled by the user. - */ - canceled: () => void; - /** - * Emitted when the job finishes cleanup after success, error or cancelation. - */ - ended: () => void; -} - -/** - * @experimental This API is experimental and subject to change. Use at your own risk. - * - * The Job class represents a single self-contained unit of work that can be run on the Golem Network. - * It is responsible for managing the lifecycle of the work and providing information about its state. - * It also provides an event emitter that can be used to listen for state changes. - */ -export class Job { - readonly events: EventEmitter = new EventEmitter(); - private abortController = new AbortController(); - - public results: Output | undefined; - public error: Error | undefined; - public state: JobState = JobState.New; - - /** - * Create a new Job instance. It is recommended to use {@link GolemNetwork} to create jobs instead of using this constructor directly. - * - * @param id - * @param yagnaApi - * @param defaultOptions - */ - constructor( - public readonly id: string, - private yagnaApi: YagnaApi, - private readonly defaultOptions: Partial = {}, - ) {} - - public isRunning() { - const inProgressStates = [JobState.Pending, JobState.Retry]; - - return inProgressStates.includes(this.state); - } - - /** - * Run your worker function on the Golem Network. This method will synchronously initialize all internal services and validate the job options. The work itself will be run asynchronously in the background. - * You can use the {@link Job.events} event emitter to listen for state changes. - * You can also use {@link Job.waitForResult} to wait for the job to finish and get the results. - * If you want to cancel the job, use {@link Job.cancel}. - * If you want to run multiple jobs in parallel, you can use {@link GolemNetwork.createJob} to create multiple jobs and run them in parallel. - * - * @param workOnGolem - Your worker function that will be run on the Golem Network. - * @param options - Configuration options for the job. These options will be merged with the options passed to the constructor. - */ - startWork(workOnGolem: Worker, options: RunJobOptions = {}) { - if (this.isRunning()) { - throw new GolemUserError(`Job ${this.id} is already running`); - } - - const packageOptions = Object.assign({}, this.defaultOptions.package, options.package); - if (!packageOptions.imageHash && !packageOptions.manifest && !packageOptions.imageTag) { - throw new GolemConfigError("You must specify either imageHash, imageTag or manifest in package options"); - } - - this.state = JobState.Pending; - this.events.emit("created"); - - const agreementService = new AgreementPoolService(this.yagnaApi, { - ...this.defaultOptions.agreement, - ...options.agreement, - }); - const marketService = new MarketService(agreementService, this.yagnaApi, { - ...this.defaultOptions.market, - ...options.market, - }); - const networkService = new NetworkService(this.yagnaApi, { ...this.defaultOptions.network, ...options.network }); - const paymentService = new PaymentService(this.yagnaApi, { ...this.defaultOptions.payment, ...options.payment }); - - const packageDescription = Package.create(packageOptions); - - // reset abort controller - this.abortController = new AbortController(); - - this.runWork({ - worker: workOnGolem, - marketService, - agreementService, - networkService, - paymentService, - packageDescription, - options, - signal: this.abortController.signal, - }) - .then((results) => { - this.results = results; - this.state = JobState.Done; - this.events.emit("success"); - }) - .catch((error) => { - this.error = error; - this.state = JobState.Rejected; - this.events.emit("error", error); - }) - .finally(async () => { - await Promise.all([agreementService.end(), networkService.end(), paymentService.end(), marketService.end()]); - this.events.emit("ended"); - }); - } - - private async runWork({ - worker, - marketService, - agreementService, - networkService, - paymentService, - packageDescription, - options, - signal, - }: { - worker: Worker; - marketService: MarketService; - agreementService: AgreementPoolService; - networkService: NetworkService; - paymentService: PaymentService; - packageDescription: Package; - options: RunJobOptions; - signal: AbortSignal; - }) { - if (signal.aborted) { - this.events.emit("canceled"); - throw new GolemAbortError("Canceled"); - } - - const allocation = await paymentService.createAllocation(); - - await Promise.all([ - marketService.run(packageDescription, allocation), - agreementService.run(), - networkService.run(), - paymentService.run(), - ]); - const agreement = await agreementService.getAgreement(); - // agreement is created, we can stop listening for new proposals - await marketService.end(); - - paymentService.acceptPayments(agreement); - - const activity = await Activity.create(agreement, this.yagnaApi, options.activity); - - const storageProvider = - this.defaultOptions.work?.storageProvider || options.work?.storageProvider || this.getDefaultStorageProvider(); - - const workContext = new WorkContext(activity, { - yagnaOptions: this.yagnaApi.yagnaOptions, - storageProvider, - networkNode: await networkService.addNode(agreement.getProviderInfo().id), - activityPreparingTimeout: - this.defaultOptions.work?.activityPreparingTimeout || options.work?.activityPreparingTimeout, - activityStateCheckingInterval: - this.defaultOptions.work?.activityStateCheckingInterval || options.work?.activityStateCheckingInterval, - }); - - this.events.emit("started"); - await workContext.before(); - - const onAbort = async () => { - await agreementService.releaseAgreement(agreement.id, false); - await activity.stop(); - this.events.emit("canceled"); - }; - if (signal.aborted) { - await onAbort(); - throw new GolemAbortError("Canceled"); - } - signal.addEventListener("abort", onAbort, { once: true }); - return worker(workContext); - } - - private getDefaultStorageProvider(): StorageProvider { - if (runtimeContextChecker.isNode) { - return new GftpStorageProvider(); - } - if (runtimeContextChecker.isBrowser) { - return new WebSocketBrowserStorageProvider(this.yagnaApi, {}); - } - return new NullStorageProvider(); - } - - /** - * Cancel the job. This method will stop the activity and wait for it to finish. - * Throws an error if the job is not running. - */ - async cancel() { - if (!this.isRunning) { - throw new GolemUserError(`Job ${this.id} is not running`); - } - this.abortController.abort(); - return new Promise((resolve) => { - this.events.once("ended", resolve); - }); - } - - /** - * Wait for the job to finish and return the results. - * Throws an error if the job was not started. - */ - async waitForResult() { - if (this.state === JobState.Done) { - return this.results; - } - if (this.state === JobState.Rejected) { - throw this.error; - } - if (!this.isRunning()) { - throw new GolemUserError(`Job ${this.id} is not running`); - } - return new Promise((resolve, reject) => { - this.events.once("ended", () => { - if (this.state === JobState.Done) { - resolve(this.results); - } else { - reject(this.error); - } - }); - }); - } -} diff --git a/src/market/agreement/agreement-event.ts b/src/market/agreement/agreement-event.ts new file mode 100644 index 000000000..4b8e3e89e --- /dev/null +++ b/src/market/agreement/agreement-event.ts @@ -0,0 +1,34 @@ +import { Agreement } from "./agreement"; + +export type AgreementApproved = { + type: "AgreementApproved"; + agreement: Agreement; + timestamp: Date; +}; + +export type AgreementTerminatedEvent = { + type: "AgreementTerminated"; + terminatedBy: "Provider" | "Requestor"; + reason: string; + agreement: Agreement; + timestamp: Date; +}; + +export type AgreementRejectedEvent = { + type: "AgreementRejected"; + agreement: Agreement; + reason: string; + timestamp: Date; +}; + +export type AgreementCancelledEvent = { + type: "AgreementCancelled"; + agreement: Agreement; + timestamp: Date; +}; + +export type AgreementEvent = + | AgreementApproved + | AgreementTerminatedEvent + | AgreementRejectedEvent + | AgreementCancelledEvent; diff --git a/src/market/agreement/agreement.test.ts b/src/market/agreement/agreement.test.ts new file mode 100644 index 000000000..7b04c7759 --- /dev/null +++ b/src/market/agreement/agreement.test.ts @@ -0,0 +1,55 @@ +import { Agreement, Demand, DemandSpecification } from "../index"; +import { MarketApi } from "ya-ts-client"; + +const agreementData: MarketApi.AgreementDTO = { + agreementId: "agreement-id", + demand: { + demandId: "demand-id", + requestorId: "requestor-id", + properties: {}, + constraints: "", + timestamp: "2024-01-01T00:00:00.000Z", + }, + offer: { + offerId: "offer-id", + providerId: "provider-id", + properties: { + "golem.node.id.name": "provider-name", + "golem.com.payment.platform.erc20-holesky-tglm.address": "0xProviderWallet", + }, + constraints: "", + timestamp: "2024-01-01T00:00:00.000Z", + }, + state: "Approved", + timestamp: "2024-01-01T00:00:00.000Z", + validTo: "2024-01-02T00:00:00.000Z", +}; + +const demand = new Demand( + "demand-id", + new DemandSpecification( + { + constraints: [], + properties: [], + }, + "erc20-holesky-tglm", + ), +); + +describe("Agreement", () => { + describe("get provider()", () => { + it("should be a instance ProviderInfo with provider details", () => { + const agreement = new Agreement(agreementData.agreementId, agreementData, demand); + expect(agreement.provider.id).toEqual("provider-id"); + expect(agreement.provider.name).toEqual("provider-name"); + expect(agreement.provider.walletAddress).toEqual("0xProviderWallet"); + }); + }); + + describe("getState()", () => { + it("should return state of agreement", () => { + const agreement = new Agreement(agreementData.agreementId, agreementData, demand); + expect(agreement.getState()).toEqual("Approved"); + }); + }); +}); diff --git a/src/market/agreement/agreement.ts b/src/market/agreement/agreement.ts new file mode 100644 index 000000000..8e171ab20 --- /dev/null +++ b/src/market/agreement/agreement.ts @@ -0,0 +1,71 @@ +import { MarketApi } from "ya-ts-client"; +import { Demand } from "../demand"; + +/** + * * `Proposal` - newly created by a Requestor (draft based on Proposal) + * * `Pending` - confirmed by a Requestor and send to Provider for approval + * * `Cancelled` by a Requestor + * * `Rejected` by a Provider + * * `Approved` by both sides + * * `Expired` - not approved, rejected nor cancelled within validity period + * * `Terminated` - finished after approval. + * + */ +export type AgreementState = "Proposal" | "Pending" | "Cancelled" | "Rejected" | "Approved" | "Expired" | "Terminated"; + +export interface ProviderInfo { + name: string; + id: string; + walletAddress: string; +} + +export interface AgreementOptions { + expirationSec?: number; + waitingForApprovalTimeoutSec?: number; +} + +export interface IAgreementRepository { + getById(id: string): Promise; +} + +/** + * Agreement module - an object representing the contract between the requestor and the provider. + */ +export class Agreement { + /** + * @param id + * @param model + * @param demand + */ + constructor( + public readonly id: string, + private readonly model: MarketApi.AgreementDTO, + public readonly demand: Demand, + ) {} + + /** + * Return agreement state + * @return state + */ + getState() { + return this.model.state; + } + + get provider(): ProviderInfo { + return { + id: this.model.offer.providerId, + name: this.model.offer.properties["golem.node.id.name"], + walletAddress: this.model.offer.properties[`golem.com.payment.platform.${this.demand.paymentPlatform}.address`], + }; + } + + /** + * Returns flag if the agreement is in the final state + * @description if the final state is true, agreement will not change state further anymore + * @return boolean + */ + isFinalState(): boolean { + const state = this.getState(); + return state !== "Pending" && state !== "Proposal"; + } +} diff --git a/src/market/agreement/index.ts b/src/market/agreement/index.ts new file mode 100644 index 000000000..6823933fe --- /dev/null +++ b/src/market/agreement/index.ts @@ -0,0 +1,2 @@ +export { Agreement, ProviderInfo, AgreementState } from "./agreement"; +export * from "./agreement-event"; diff --git a/src/market/api.ts b/src/market/api.ts new file mode 100644 index 000000000..e3238a644 --- /dev/null +++ b/src/market/api.ts @@ -0,0 +1,141 @@ +import { Observable } from "rxjs"; +import { Demand, DemandBodyPrototype, DemandSpecification } from "./demand"; +import { MarketProposalEvent, OfferCounterProposal, OfferProposal } from "./proposal"; +import { Agreement, AgreementEvent, AgreementState } from "./agreement"; +import { AgreementOptions } from "./agreement/agreement"; +import { ScanSpecification, ScannedOffer } from "./scan"; + +export type MarketEvents = { + demandSubscriptionStarted: (event: { demand: Demand }) => void; + demandSubscriptionRefreshed: (event: { demand: Demand }) => void; + demandSubscriptionStopped: (event: { demand: Demand }) => void; + + /** Emitted when offer proposal from the Provider is received */ + offerProposalReceived: (event: { offerProposal: OfferProposal }) => void; + + offerCounterProposalSent: (event: { offerProposal: OfferProposal; counterProposal: OfferCounterProposal }) => void; + errorSendingCounterProposal: (event: { offerProposal: OfferProposal; error: Error }) => void; + + /** Emitted when the Provider rejects the counter-proposal that the Requestor sent */ + offerCounterProposalRejected: (event: { counterProposal: OfferCounterProposal; reason: string }) => void; + + /** Not implemented */ + offerPropertyQueryReceived: () => void; + + offerProposalRejectedByProposalFilter: (event: { offerProposal: OfferProposal; reason?: string }) => void; + + /** Emitted when proposal price does not meet user criteria */ + offerProposalRejectedByPriceFilter: (event: { offerProposal: OfferProposal; reason?: string }) => void; + + agreementApproved: (event: { agreement: Agreement }) => void; + agreementRejected: (event: { agreement: Agreement; reason: string }) => void; + agreementTerminated: (event: { + agreement: Agreement; + reason: string; + terminatedBy: "Provider" | "Requestor"; + }) => void; + agreementCancelled: (event: { agreement: Agreement }) => void; +}; + +export interface IMarketApi { + /** + * Creates a new demand based on the given specification and publishes + * it to the market. + * Keep in mind that the demand lasts for a limited time and needs to be + * refreshed periodically (see `refreshDemand` method). + * Use `unpublishDemand` to remove the demand from the market. + */ + publishDemandSpecification(specification: DemandSpecification): Promise; + + /** + * Remove the given demand from the market. + */ + unpublishDemand(demand: Demand): Promise; + + /** + * "Publishes" the demand on the network and stats to listen (event polling) for the events representing the feedback + * + * The feedback can fall into four categories: + * + * - (Initial) We will receive initial offer proposals that were matched by the yagna node which we're using + * - (Negotiations) We will receive responses from providers with draft offer proposals if we decided to counter the initial proposal + * - (Negotiations) We will receive an event representing rejection of our counter-proposal by the provider + * - (Negotiations) We will receive a question from the provider about a certain property as part of the negotiation process (_protocol piece not by yagna 0.15_) + * + * @param demand + * + * @returns A complex object that allows subscribing to these categories of feedback mentioned above + */ + collectMarketProposalEvents(demand: Demand): Observable; + + /** + * Start looking at the Agreement related events + */ + collectAgreementEvents(): Observable; + + /** + * Sends a counter-proposal to the given proposal. Returns the newly created counter-proposal. + */ + counterProposal(receivedProposal: OfferProposal, specification: DemandSpecification): Promise; + + /** + * Sends a "reject" response for the proposal that was received from the Provider as part of the negotiation process + * + * On the protocol level this means that no further counter-proposals will be generated by the Requestor + * + * @param receivedProposal The proposal from the provider + * @param reason User readable reason that should be presented to the Provider + */ + rejectProposal(receivedProposal: OfferProposal, reason: string): Promise; + + /** + * Fetches payment related decorations, based on the given allocation ID. + * + * @param allocationId The ID of the allocation that will be used to pay for computations related to the demand + * + */ + getPaymentRelatedDemandDecorations(allocationId: string): Promise; + + /** + * Retrieves an agreement based on the provided ID. + */ + getAgreement(id: string): Promise; + + /** + * Request creating an agreement from the provided proposal + * + * Use this method if you want to decide what should happen with the agreement after it is created + * + * @return An agreement that's in a "Proposal" state (not yet usable for activity creation) + */ + createAgreement(proposal: OfferProposal, options?: AgreementOptions): Promise; + + /** + * Request creating an agreement from the provided proposal, send it to the Provider and wait for approval + * + * Use this method when you want to quickly finalize the deal with the Provider, but be ready for a rejection + * + * @return An agreement that's already in an "Approved" state and can be used to create activities on the Provider + */ + proposeAgreement(proposal: OfferProposal, options?: AgreementOptions): Promise; + + /** + * Confirms the agreement with the provider + */ + confirmAgreement(agreement: Agreement, options?: AgreementOptions): Promise; + + /** + * Terminates an agreement. + */ + terminateAgreement(agreement: Agreement, reason?: string): Promise; + + /** + * Retrieves the state of an agreement based on the provided agreement ID. + */ + getAgreementState(id: string): Promise; + + /** + * Scan the market for offers that match the given specification. + */ + scan(scanSpecification: ScanSpecification): Observable; +} diff --git a/src/market/builder.ts b/src/market/builder.ts deleted file mode 100644 index 41ed1d37b..000000000 --- a/src/market/builder.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { MarketProperty } from "ya-ts-client/dist/ya-payment/src/models"; -import { DemandOfferBase } from "ya-ts-client/dist/ya-market/src/models"; -import { GolemInternalError } from "../error/golem-error"; - -/** - * Properties and constraints to be added to a market object (i.e. a demand or an offer). - */ -export type MarketDecoration = { - properties: Array<{ key: string; value: string | number | boolean }>; - constraints: Array; -}; - -/** - * @hidden - */ -export enum ComparisonOperator { - Eq = "=", - Lt = "<", - Gt = ">", - GtEq = ">=", - LtEq = "<=", -} - -type Constraint = { - key: string; - value: string | number; - comparisonOperator: ComparisonOperator; -}; - -/** - * A helper class for creating market decorations for `Demand` published on the market. - * @hidden - */ -export class DecorationsBuilder { - private properties: Array = []; - private constraints: Array = []; - - addProperty(key: string, value: string | number | boolean) { - const findIndex = this.properties.findIndex((prop) => prop.key === key); - if (findIndex >= 0) { - this.properties[findIndex] = { key, value }; - } else { - this.properties.push({ key, value }); - } - return this; - } - addConstraint(key: string, value: string | number, comparisonOperator = ComparisonOperator.Eq) { - this.constraints.push({ key, value, comparisonOperator }); - return this; - } - getDecorations(): MarketDecoration { - return { - properties: this.properties, - constraints: this.constraints.map((c) => `(${c.key + c.comparisonOperator + c.value})`), - }; - } - getDemandRequest(): DemandOfferBase { - const decorations = this.getDecorations(); - let constraints: string; - if (!decorations.constraints.length) constraints = "(&)"; - else if (decorations.constraints.length == 1) constraints = decorations.constraints[0]; - else constraints = `(&${decorations.constraints.join("\n\t")})`; - const properties: Record = {}; - decorations.properties.forEach((prop) => (properties[prop.key] = prop.value)); - return { constraints, properties }; - } - private parseConstraint(constraint: string): Constraint { - for (const key in ComparisonOperator) { - const value = ComparisonOperator[key as keyof typeof ComparisonOperator]; - const parsedConstraint = constraint.slice(1, -1).split(value); - if (parsedConstraint.length === 2) { - return { - key: parsedConstraint[0], - value: parsedConstraint[1], - comparisonOperator: value, - }; - } - } - throw new GolemInternalError(`Unable to parse constraint "${constraint}"`); - } - addDecoration(decoration: MarketDecoration) { - if (decoration.properties) { - decoration.properties.forEach((prop) => { - this.addProperty(prop.key, prop.value); - }); - } - if (decoration.constraints) { - decoration.constraints.forEach((cons) => { - const { key, value, comparisonOperator } = { ...this.parseConstraint(cons) }; - this.addConstraint(key, value, comparisonOperator); - }); - } - return this; - } - addDecorations(decorations: MarketDecoration[]) { - decorations.forEach((d) => this.addDecoration(d)); - return this; - } -} diff --git a/src/market/config.test.ts b/src/market/config.test.ts deleted file mode 100644 index 00ad6fea1..000000000 --- a/src/market/config.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { DemandConfig } from "./config"; - -describe("Demand Config", () => { - describe("Positive cases", () => { - it("It will accept proper config values without an error", () => { - const config = new DemandConfig({ - expirationSec: 30 * 60, - debitNotesAcceptanceTimeoutSec: 20, - midAgreementPaymentTimeoutSec: 12 * 60 * 60, - }); - - expect(config).toBeDefined(); - }); - }); - describe("Negative cases", () => { - const INVALID_VALUES = [-1, 0, 1.23]; - - describe("Expiration time configuration", () => { - test.each(INVALID_VALUES)("It should throw an error when someone specifies %d as expiration time", (value) => { - expect( - () => - new DemandConfig({ - expirationSec: value, - }), - ).toThrow("The demand expiration time has to be a positive integer"); - }); - }); - - describe("Debit note acceptance timeout configuration", () => { - test.each(INVALID_VALUES)( - "It should throw an error when someone specifies %d as debit note accept timeout", - (value) => { - expect( - () => - new DemandConfig({ - debitNotesAcceptanceTimeoutSec: value, - }), - ).toThrow("The debit note acceptance timeout time has to be a positive integer"); - }, - ); - }); - - describe("Mid-agreement payments timeout configuration", () => { - test.each(INVALID_VALUES)( - "It should throw an error when someone specifies %d as mid-agreement payment timeout", - (value) => { - expect( - () => - new DemandConfig({ - midAgreementPaymentTimeoutSec: value, - }), - ).toThrow("The mid-agreement payment timeout time has to be a positive integer"); - }, - ); - }); - }); -}); diff --git a/src/market/config.ts b/src/market/config.ts deleted file mode 100644 index b7886d8f4..000000000 --- a/src/market/config.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { DemandOptions } from "./demand"; -import { EnvUtils, Logger, defaultLogger, YagnaOptions } from "../utils"; -import { MarketOptions, ProposalFilter } from "./service"; -import { GolemConfigError } from "../error/golem-error"; -import { acceptAll } from "./strategy"; - -const DEFAULTS = { - subnetTag: "public", - maxOfferEvents: 10, - offerFetchingIntervalSec: 20, - expirationSec: 30 * 60, // 30 min - debitNotesAcceptanceTimeoutSec: 2 * 60, // 2 minutes - midAgreementDebitNoteIntervalSec: 2 * 60, // 2 minutes - midAgreementPaymentTimeoutSec: 12 * 60 * 60, // 12 hours - proposalFilter: acceptAll(), -}; - -/** - * @internal - */ -export class DemandConfig { - public readonly yagnaOptions?: YagnaOptions; - public readonly expirationSec: number; - public readonly subnetTag: string; - public readonly maxOfferEvents: number; - public readonly offerFetchingIntervalSec: number; - public readonly logger: Logger; - public readonly eventTarget?: EventTarget; - public readonly debitNotesAcceptanceTimeoutSec: number; - public readonly midAgreementDebitNoteIntervalSec: number; - public readonly midAgreementPaymentTimeoutSec: number; - - constructor(options?: DemandOptions) { - this.logger = options?.logger || defaultLogger("market"); - this.eventTarget = options?.eventTarget; - - this.subnetTag = options?.subnetTag ?? EnvUtils.getYagnaSubnet() ?? DEFAULTS.subnetTag; - this.offerFetchingIntervalSec = options?.offerFetchingIntervalSec ?? DEFAULTS.offerFetchingIntervalSec; - this.maxOfferEvents = options?.maxOfferEvents ?? DEFAULTS.maxOfferEvents; - - this.expirationSec = options?.expirationSec ?? DEFAULTS.expirationSec; - - if (!this.isPositiveInt(this.expirationSec)) { - throw new GolemConfigError("The demand expiration time has to be a positive integer"); - } - - this.debitNotesAcceptanceTimeoutSec = - options?.debitNotesAcceptanceTimeoutSec ?? DEFAULTS.debitNotesAcceptanceTimeoutSec; - - if (!this.isPositiveInt(this.debitNotesAcceptanceTimeoutSec)) { - throw new GolemConfigError("The debit note acceptance timeout time has to be a positive integer"); - } - - this.midAgreementDebitNoteIntervalSec = - options?.midAgreementDebitNoteIntervalSec ?? DEFAULTS.midAgreementDebitNoteIntervalSec; - - if (!this.isPositiveInt(this.midAgreementDebitNoteIntervalSec)) { - throw new GolemConfigError("The debit note interval time has to be a positive integer"); - } - - this.midAgreementPaymentTimeoutSec = - options?.midAgreementPaymentTimeoutSec ?? DEFAULTS.midAgreementPaymentTimeoutSec; - - if (!this.isPositiveInt(this.midAgreementPaymentTimeoutSec)) { - throw new GolemConfigError("The mid-agreement payment timeout time has to be a positive integer"); - } - } - - private isPositiveInt(value: number) { - return value > 0 && Number.isInteger(value); - } -} - -/** - * @internal - */ -export class MarketConfig extends DemandConfig { - readonly debitNotesAcceptanceTimeoutSec: number; - public readonly proposalFilter: ProposalFilter; - - constructor(options?: MarketOptions) { - super(options); - - this.debitNotesAcceptanceTimeoutSec = - options?.debitNotesAcceptanceTimeoutSec ?? DEFAULTS.debitNotesAcceptanceTimeoutSec; - - this.proposalFilter = options?.proposalFilter ?? DEFAULTS.proposalFilter; - } -} diff --git a/src/market/demand.ts b/src/market/demand.ts deleted file mode 100644 index 28553f58b..000000000 --- a/src/market/demand.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { Package } from "../package"; -import { Allocation } from "../payment"; -import { DemandFactory } from "./factory"; -import { Proposal } from "./proposal"; -import { defaultLogger, Logger, sleep, YagnaApi, YagnaOptions } from "../utils"; -import { DemandConfig } from "./config"; -import { Events } from "../events"; -import { ProposalEvent, ProposalRejectedEvent } from "ya-ts-client/dist/ya-market/src/models"; -import { DemandOfferBase } from "ya-ts-client/dist/ya-market"; -import * as events from "../events/events"; -import { GolemMarketError, MarketErrorCode } from "./error"; -import { GolemError, GolemPlatformError } from "../error/golem-error"; - -export interface DemandDetails { - properties: Array<{ key: string; value: string | number | boolean }>; - constraints: Array; -} - -export interface DemandOptions { - subnetTag?: string; - yagnaOptions?: YagnaOptions; - - /** - * Determines the expiration time of the offer and the resulting activity in milliseconds. - * - * The value of this field is used to define how long the demand is valid for yagna to match against. - * In addition, it will determine how long the resulting activity will be active. - * - * For example: if `expirationSec` is set to 10 minutes, the demand was created and starting an activity - * required 2 minutes, this means that the activity will be running for 8 more minutes, and then will get terminated. - * - * **IMPORTANT** - * - * It is possible that a provider will reject engaging with that demand if it's configured without using a deadline. - * - * **GUIDE** - * - * If your activity is about to operate for 5-30 min, {@link expirationSec} is sufficient. - * - * If your activity is about to operate for 30min-10h, {@link debitNotesAcceptanceTimeoutSec} should be set as well. - * - * If your activity is about to operate longer than 10h, you need set both {@link debitNotesAcceptanceTimeoutSec} and {@link midAgreementPaymentTimeoutSec}. - */ - expirationSec?: number; - - logger?: Logger; - maxOfferEvents?: number; - - offerFetchingIntervalSec?: number; - - proposalTimeout?: number; - - eventTarget?: EventTarget; - - /** - * Maximum time for allowed provider-sent debit note acceptance (in seconds) - * - * Accepting debit notes from the provider is used as a health-check of the agreement between these parties. - * Failing to accept several debit notes in a row will be considered as a valida reason to terminate the agreement earlier - * than {@link expirationSec} defines. - * - * _Accepting debit notes during a long activity is considered a good practice in Golem Network._ - * The SDK will accept debit notes each 2 minutes by default. - */ - debitNotesAcceptanceTimeoutSec?: number; - - /** - * The interval between provider sent debit notes to negotiate. - * - * If it would not be defined, the activities created for your demand would - * probably live only 30 minutes, as that's the default value that the providers use to control engagements - * that are not using mid-agreement payments. - * - * As a requestor, you don't have to specify it, as the provider will propose a value that the SDK will simply - * accept without negotiations. - * - * _Accepting payable debit notes during a long activity is considered a good practice in Golem Network._ - * The SDK will accept debit notes each 2 minutes by default. - */ - midAgreementDebitNoteIntervalSec?: number; - - /** - * Maximum time to receive payment for any debit note. At the same time, the minimum interval between mid-agreement payments. - * - * Setting this is relevant in case activities which are running for a long time (like 10 hours and more). Providers control - * the threshold activity duration for which they would like to enforce mid-agreement payments. This value depends on the - * provider configuration. Checking proposal rejections from providers in yagna's logs can give you a hint about the - * market expectations. - * - * _Paying in regular intervals for the computation resources is considered a good practice in Golem Network._ - * The SDK will issue payments each 12h by default, and you can control this with this setting. - */ - midAgreementPaymentTimeoutSec?: number; -} - -/** - * Event type with which all offers and proposals coming from the market will be emitted. - * @hidden - */ -export const DEMAND_EVENT_TYPE = "ProposalReceived"; - -/** - * Demand module - an object which can be considered an "open" or public Demand, as it is not directed at a specific Provider, but rather is sent to the market so that the matching mechanism implementation can associate relevant Offers. - * It is a special entity type because it inherits from the `EventTarget` class. Therefore, after creating it, you can add listeners to it, which will listen to offers from the market on an event of a specific type: `DemandEventType`. - * @hidden - */ -export class Demand extends EventTarget { - private isRunning = true; - private logger: Logger; - private proposalReferences: ProposalReference[] = []; - - /** - * Create demand for given taskPackage - * - * Note: it is an "atomic" operation. - * When the demand is created, the SDK will use it to subscribe for provider offer proposals matching it. - * - * @param taskPackage - * @param allocation - * @param yagnaApi - * @param options - * - * @return Demand - */ - static async create( - taskPackage: Package, - allocation: Allocation, - yagnaApi: YagnaApi, - options?: DemandOptions, - ): Promise { - const factory = new DemandFactory(taskPackage, allocation, yagnaApi, options); - return factory.create(); - } - - /** - * @param id - demand ID - * @param demandRequest - {@link DemandOfferBase} - * @param allocation - {@link Allocation} - * @param yagnaApi - {@link YagnaApi} - * @param options - {@link DemandConfig} - * @hidden - */ - constructor( - public readonly id: string, - public readonly demandRequest: DemandOfferBase, - public readonly allocation: Allocation, - private yagnaApi: YagnaApi, - private options: DemandConfig, - ) { - super(); - this.logger = this.options.logger || defaultLogger("market"); - this.subscribe().catch((e) => this.logger.error("Unable to subscribe for demand events", e)); - } - - /** - * Stop subscribing for provider offer proposals for this demand - */ - async unsubscribe() { - this.isRunning = false; - await this.yagnaApi.market.unsubscribeDemand(this.id); - this.options.eventTarget?.dispatchEvent(new events.DemandUnsubscribed({ id: this.id })); - this.logger.debug(`Demand unsubscribed`, { id: this.id }); - } - - private findParentProposal(prevProposalId?: string): string | null { - if (!prevProposalId) return null; - for (const proposal of this.proposalReferences) { - if (proposal.counteringProposalId === prevProposalId) { - return proposal.id; - } - } - return null; - } - - private setCounteringProposalReference(id: string, counteringProposalId: string): void { - this.proposalReferences.push(new ProposalReference(id, counteringProposalId)); - } - - private async subscribe() { - while (this.isRunning) { - try { - const { data: events } = await this.yagnaApi.market.collectOffers( - this.id, - this.options.offerFetchingIntervalSec, - this.options.maxOfferEvents, - { - timeout: 0, - }, - ); - for (const event of events as Array) { - if (event.eventType === "ProposalRejectedEvent") { - this.logger.warn(`Proposal rejected`, { reason: event.reason?.message }); - this.options.eventTarget?.dispatchEvent( - new Events.ProposalRejected({ - id: event.proposalId, - parentId: this.findParentProposal(event.proposalId), - reason: event.reason?.message, - }), - ); - continue; - } else if (event.eventType !== "ProposalEvent") continue; - const proposal = new Proposal( - this, - event.proposal.state === "Draft" ? this.findParentProposal(event.proposal.prevProposalId) : null, - this.setCounteringProposalReference.bind(this), - this.yagnaApi.market, - event.proposal, - this.options.eventTarget, - ); - this.dispatchEvent(new DemandEvent(DEMAND_EVENT_TYPE, proposal)); - this.options.eventTarget?.dispatchEvent( - new Events.ProposalReceived({ - id: proposal.id, - parentId: this.findParentProposal(event.proposal.prevProposalId), - provider: proposal.provider, - details: proposal.details, - }), - ); - } - } catch (error) { - if (this.isRunning) { - const reason = error.response?.data?.message || error; - this.options.eventTarget?.dispatchEvent(new Events.CollectFailed({ id: this.id, reason })); - this.logger.warn(`Unable to collect offers.`, { reason }); - if (error.code === "ECONNREFUSED") { - // Yagna has been disconnected - this.dispatchEvent( - new DemandEvent( - DEMAND_EVENT_TYPE, - undefined, - new GolemPlatformError(`Unable to collect offers. ${reason}`, error), - ), - ); - break; - } - if (error.response?.status === 404) { - // Demand has expired - this.dispatchEvent( - new DemandEvent( - DEMAND_EVENT_TYPE, - undefined, - new GolemMarketError(`Demand expired. ${reason}`, MarketErrorCode.DemandExpired, this, error), - ), - ); - break; - } - await sleep(2); - } - } - } - } -} - -/** - * @hidden - */ -export class DemandEvent extends Event { - readonly proposal?: Proposal; - readonly error?: Error; - - /** - * Create a new instance of DemandEvent - * @param type A string with the name of the event: - * @param data object with proposal data: - * @param error optional error if occurred while subscription is active - */ - constructor(type: string, data?: (Proposal & EventInit) | undefined, error?: GolemError | undefined) { - super(type, data); - this.proposal = data; - this.error = error; - } -} - -class ProposalReference { - constructor( - readonly id: string, - readonly counteringProposalId: string, - ) {} -} diff --git a/src/market/demand/demand-body-builder.test.ts b/src/market/demand/demand-body-builder.test.ts new file mode 100644 index 000000000..3c525b4af --- /dev/null +++ b/src/market/demand/demand-body-builder.test.ts @@ -0,0 +1,137 @@ +import { ComparisonOperator, DemandBodyBuilder } from "./demand-body-builder"; +import { GolemInternalError } from "../../shared/error/golem-error"; + +describe("#DecorationsBuilder()", () => { + describe("addProperty()", () => { + it("should allow to add property", () => { + const builder = new DemandBodyBuilder(); + builder.addProperty("key", "value"); + expect(builder.getProduct().properties.length).toEqual(1); + }); + it("should replace already existing property", () => { + const builder = new DemandBodyBuilder(); + builder.addProperty("key", "value").addProperty("key", "value2"); + expect(builder.getProduct().properties.length).toEqual(1); + expect(builder.getProduct().properties[0].value).toEqual("value2"); + }); + it("should provide fluent API", () => { + const builder = new DemandBodyBuilder(); + const flAPI = builder.addProperty("key", "value"); + expect(flAPI).toBeInstanceOf(DemandBodyBuilder); + }); + }); + describe("addConstraint()", () => { + it("should allow to add constrain", () => { + const builder = new DemandBodyBuilder(); + builder.addConstraint("key", "value"); + expect(builder.getProduct().constraints.length).toEqual(1); + }); + it("should allow to add constrain with >=", () => { + const builder = new DemandBodyBuilder(); + builder.addConstraint("key", "value", ComparisonOperator.GtEq); + expect(builder.getProduct().constraints.length).toEqual(1); + }); + it("should allow to add constrain with <=", () => { + const builder = new DemandBodyBuilder(); + builder.addConstraint("key", "value", ComparisonOperator.LtEq); + expect(builder.getProduct().constraints.length).toEqual(1); + }); + it("should allow to add constrain with >", () => { + const builder = new DemandBodyBuilder(); + builder.addConstraint("key", "value", ComparisonOperator.Gt); + expect(builder.getProduct().constraints.length).toEqual(1); + }); + it("should allow to add constrain with <", () => { + const builder = new DemandBodyBuilder(); + builder.addConstraint("key", "value", ComparisonOperator.Lt); + expect(builder.getProduct().constraints.length).toEqual(1); + }); + it("should allow to add constrain with =", () => { + const builder = new DemandBodyBuilder(); + builder.addConstraint("key", "value", ComparisonOperator.Eq); + expect(builder.getProduct().constraints.length).toEqual(1); + }); + it("should provide fluent API", () => { + const builder = new DemandBodyBuilder(); + const flAPI = builder.addConstraint("key", "value"); + expect(flAPI).toBeInstanceOf(DemandBodyBuilder); + }); + }); + describe("addDecorations()", () => { + it("should allow to parse constrain with =, >=, <=, >, <", async () => { + const decoration = { + constraints: [ + "some_constraint=some_value", + "some_constraint>=some_value", + "some_constraint<=some_value", + "some_constraint>some_value", + "some_constraint { + const decoration = { + properties: [{ key: "prop_key", value: "value" }], + constraints: ["some_constraint=some_value"], + }; + const builder = new DemandBodyBuilder(); + builder.mergePrototype(decoration); + expect(builder.getProduct().constraints.length).toEqual(1); + expect(builder.getProduct().properties.length).toEqual(1); + }); + it("should provide fluent API", () => { + const decoration = { + properties: [{ key: "prop_key", value: "value" }], + constraints: ["some_constraint=some_value"], + }; + const builder = new DemandBodyBuilder(); + const flAPI = builder.mergePrototype(decoration); + expect(flAPI).toBeInstanceOf(DemandBodyBuilder); + }); + + it("should not allow to add invalid decorations", () => { + const decoration = { + properties: [{ key: "prop_key", value: "value" }], + constraints: ["some_invalid_constraint"], + }; + const builder = new DemandBodyBuilder(); + expect(() => builder.mergePrototype(decoration)).toThrow( + new GolemInternalError('Unable to parse constraint "some_invalid_constraint"'), + ); + }); + }); + describe("getDecorations()", () => { + it("should return correct decoration", () => { + const builder = new DemandBodyBuilder(); + builder + .addConstraint("key", "value", ComparisonOperator.Eq) + .addConstraint("key", "value", ComparisonOperator.GtEq) + .addConstraint("key", "value", ComparisonOperator.LtEq) + .addConstraint("key", "value", ComparisonOperator.Gt) + .addConstraint("key", "value", ComparisonOperator.Lt) + .addProperty("key", "value") + .addProperty("key2", "value"); + + expect(builder.getProduct().constraints.length).toEqual(5); + expect(builder.getProduct().properties.length).toEqual(2); + + expect(builder.getProduct().constraints).toEqual([ + "(key=value)", + "(key>=value)", + "(key<=value)", + "(key>value)", + "(key DemandSpecification -> DemandPrototype -> DemandDTO + */ +export class DemandBodyBuilder { + private properties: Array = []; + private constraints: Array = []; + + addProperty(key: string, value: DemandPropertyValue) { + const findIndex = this.properties.findIndex((prop) => prop.key === key); + if (findIndex >= 0) { + this.properties[findIndex] = { key, value }; + } else { + this.properties.push({ key, value }); + } + return this; + } + + addConstraint(key: string, value: string | number, comparisonOperator = ComparisonOperator.Eq) { + this.constraints.push({ key, value, comparisonOperator }); + return this; + } + + getProduct(): DemandBodyPrototype { + return { + properties: this.properties, + constraints: this.constraints.map((c) => `(${c.key + c.comparisonOperator + c.value})`), + }; + } + + mergePrototype(prototype: DemandBodyPrototype) { + if (prototype.properties) { + prototype.properties.forEach((prop) => { + this.addProperty(prop.key, prop.value); + }); + } + + if (prototype.constraints) { + prototype.constraints.forEach((cons) => { + const { key, value, comparisonOperator } = { ...this.parseConstraint(cons) }; + this.addConstraint(key, value, comparisonOperator); + }); + } + + return this; + } + + private parseConstraint(constraint: string): DemandConstraint { + for (const key in ComparisonOperator) { + const value = ComparisonOperator[key as keyof typeof ComparisonOperator]; + const parsedConstraint = constraint.slice(1, -1).split(value); + if (parsedConstraint.length === 2) { + return { + key: parsedConstraint[0], + value: parsedConstraint[1], + comparisonOperator: value, + }; + } + } + + throw new GolemInternalError(`Unable to parse constraint "${constraint}"`); + } +} diff --git a/src/market/demand/demand.ts b/src/market/demand/demand.ts new file mode 100644 index 000000000..42a26052b --- /dev/null +++ b/src/market/demand/demand.ts @@ -0,0 +1,119 @@ +import { WorkloadDemandDirectorConfigOptions } from "./options"; +import { BasicDemandDirectorConfigOptions } from "./directors/basic-demand-director-config"; +import { PaymentDemandDirectorConfigOptions } from "./directors/payment-demand-director-config"; +import { DemandBodyPrototype } from "./demand-body-builder"; + +/** + * This type represents a set of *parameters* that the SDK can set to particular *properties* and *constraints* + * of the demand that's used to subscribe for offers via Yagna + */ +export interface BasicDemandPropertyConfig { + /** + * Specify the name of a subnet of Golem Network that should be considered for offers + * + * Providers and Requestors can agree to a subnet tag, that they can put on their Offer and Demands + * so that they can create "segments" within the network for ease of finding themselves. + * + * Please note that this subnetTag is public and visible to everyone. + */ + subnetTag?: string; + + /** + * Determines the expiration time of the offer and the resulting activity in milliseconds. + * + * The value of this field is used to define how long the demand is valid for yagna to match against. + * In addition, it will determine how long the resulting activity will be active. + * + * For example: if `expirationSec` is set to 10 minutes, the demand was created and starting an activity + * required 2 minutes, this means that the activity will be running for 8 more minutes, and then will get terminated. + * + * **IMPORTANT** + * + * It is possible that a provider will reject engaging with that demand if it's configured without using a deadline. + * + * **GUIDE** + * + * If your activity is about to operate for 5-30 min, {@link expirationSec} is sufficient. + * + * If your activity is about to operate for 30min-10h, {@link debitNotesAcceptanceTimeoutSec} should be set as well. + * + * If your activity is about to operate longer than 10h, you need set both {@link debitNotesAcceptanceTimeoutSec} and {@link midAgreementPaymentTimeoutSec}. + */ + expirationSec: number; + + /** + * Maximum time for allowed provider-sent debit note acceptance (in seconds) + * + * Accepting debit notes from the provider is used as a health-check of the agreement between these parties. + * Failing to accept several debit notes in a row will be considered as a valida reason to terminate the agreement earlier + * than {@link expirationSec} defines. + * + * _Accepting debit notes during a long activity is considered a good practice in Golem Network._ + * The SDK will accept debit notes each 2 minutes by default. + */ + debitNotesAcceptanceTimeoutSec: number; + + /** + * The interval between provider sent debit notes to negotiate. + * + * If it would not be defined, the activities created for your demand would + * probably live only 30 minutes, as that's the default value that the providers use to control engagements + * that are not using mid-agreement payments. + * + * As a requestor, you don't have to specify it, as the provider will propose a value that the SDK will simply + * accept without negotiations. + * + * _Accepting payable debit notes during a long activity is considered a good practice in Golem Network._ + * The SDK will accept debit notes each 2 minutes by default. + */ + midAgreementDebitNoteIntervalSec: number; + + /** + * Maximum time to receive payment for any debit note. At the same time, the minimum interval between mid-agreement payments. + * + * Setting this is relevant in case activities which are running for a long time (like 10 hours and more). Providers control + * the threshold activity duration for which they would like to enforce mid-agreement payments. This value depends on the + * provider configuration. Checking proposal rejections from providers in yagna's logs can give you a hint about the + * market expectations. + * + * _Paying in regular intervals for the computation resources is considered a good practice in Golem Network._ + * The SDK will issue payments each 12h by default, and you can control this with this setting. + */ + midAgreementPaymentTimeoutSec: number; +} + +export type OrderDemandOptions = Partial<{ + /** Demand properties related to the activities that will be executed on providers */ + workload: Partial; + /** Demand properties that determine payment related terms & conditions of the agreement */ + payment: Partial; +}> & + /** Demand properties that determine most common paramters of the agreement (based on golemsp implementation */ + Partial; + +export interface IDemandRepository { + getById(id: string): Demand | undefined; + + add(demand: Demand): Demand; + + getAll(): Demand[]; +} + +export class DemandSpecification { + constructor( + /** Represents the low level demand request body that will be used to subscribe for offers matching our "computational resource needs" */ + public readonly prototype: DemandBodyPrototype, + public readonly paymentPlatform: string, + ) {} +} + +export class Demand { + constructor( + public readonly id: string, + public readonly details: DemandSpecification, + ) {} + + get paymentPlatform(): string { + return this.details.paymentPlatform; + } +} diff --git a/src/market/demand/directors/base-config.ts b/src/market/demand/directors/base-config.ts new file mode 100644 index 000000000..108ff3aa4 --- /dev/null +++ b/src/market/demand/directors/base-config.ts @@ -0,0 +1,10 @@ +/** + * Basic config utility class + * + * Helps in building more specific config classes + */ +export class BaseConfig { + protected isPositiveInt(value: number) { + return value > 0 && Number.isInteger(value); + } +} diff --git a/src/market/demand/directors/basic-demand-director-config.test.ts b/src/market/demand/directors/basic-demand-director-config.test.ts new file mode 100644 index 000000000..f3757fa5e --- /dev/null +++ b/src/market/demand/directors/basic-demand-director-config.test.ts @@ -0,0 +1,11 @@ +import { BasicDemandDirectorConfig } from "./basic-demand-director-config"; + +describe("BasicDemandDirectorConfig", () => { + test("it sets the subnet tag property", () => { + const config = new BasicDemandDirectorConfig({ + subnetTag: "public", + }); + + expect(config.subnetTag).toBe("public"); + }); +}); diff --git a/src/market/demand/directors/basic-demand-director-config.ts b/src/market/demand/directors/basic-demand-director-config.ts new file mode 100644 index 000000000..796171d77 --- /dev/null +++ b/src/market/demand/directors/basic-demand-director-config.ts @@ -0,0 +1,19 @@ +import { BaseConfig } from "./base-config"; +import * as EnvUtils from "../../../shared/utils/env"; + +export interface BasicDemandDirectorConfigOptions { + /** Determines which subnet tag should be used for the offer/demand matching */ + subnetTag: string; +} + +export class BasicDemandDirectorConfig extends BaseConfig implements BasicDemandDirectorConfigOptions { + public readonly subnetTag: string = EnvUtils.getYagnaSubnet(); + + constructor(options?: Partial) { + super(); + + if (options?.subnetTag) { + this.subnetTag = options.subnetTag; + } + } +} diff --git a/src/market/demand/directors/basic-demand-director.ts b/src/market/demand/directors/basic-demand-director.ts new file mode 100644 index 000000000..b329e5925 --- /dev/null +++ b/src/market/demand/directors/basic-demand-director.ts @@ -0,0 +1,17 @@ +import { DemandBodyBuilder } from "../demand-body-builder"; +import { IDemandDirector } from "../../market.module"; +import { BasicDemandDirectorConfig } from "./basic-demand-director-config"; + +export class BasicDemandDirector implements IDemandDirector { + constructor(private config: BasicDemandDirectorConfig = new BasicDemandDirectorConfig()) {} + + apply(builder: DemandBodyBuilder) { + builder + .addProperty("golem.srv.caps.multi-activity", true) + .addProperty("golem.node.debug.subnet", this.config.subnetTag); + + builder + .addConstraint("golem.com.pricing.model", "linear") + .addConstraint("golem.node.debug.subnet", this.config.subnetTag); + } +} diff --git a/src/market/demand/directors/payment-demand-director-config.test.ts b/src/market/demand/directors/payment-demand-director-config.test.ts new file mode 100644 index 000000000..7b0b9fa79 --- /dev/null +++ b/src/market/demand/directors/payment-demand-director-config.test.ts @@ -0,0 +1,27 @@ +import { PaymentDemandDirectorConfig } from "./payment-demand-director-config"; + +describe("PaymentDemandDirectorConfig", () => { + it("should throw user error if debitNotesAcceptanceTimeoutSec option is invalid", () => { + expect(() => { + new PaymentDemandDirectorConfig({ + debitNotesAcceptanceTimeoutSec: -3, + }); + }).toThrow("The debit note acceptance timeout time has to be a positive integer"); + }); + + it("should throw user error if midAgreementDebitNoteIntervalSec option is invalid", () => { + expect(() => { + new PaymentDemandDirectorConfig({ + midAgreementDebitNoteIntervalSec: -3, + }); + }).toThrow("The debit note interval time has to be a positive integer"); + }); + + it("should throw user error if midAgreementPaymentTimeoutSec option is invalid", () => { + expect(() => { + new PaymentDemandDirectorConfig({ + midAgreementPaymentTimeoutSec: -3, + }); + }).toThrow("The mid-agreement payment timeout time has to be a positive integer"); + }); +}); diff --git a/src/market/demand/directors/payment-demand-director-config.ts b/src/market/demand/directors/payment-demand-director-config.ts new file mode 100644 index 000000000..816d59ce0 --- /dev/null +++ b/src/market/demand/directors/payment-demand-director-config.ts @@ -0,0 +1,34 @@ +import { BaseConfig } from "./base-config"; +import { GolemConfigError } from "../../../shared/error/golem-error"; + +export interface PaymentDemandDirectorConfigOptions { + midAgreementDebitNoteIntervalSec: number; + midAgreementPaymentTimeoutSec: number; + debitNotesAcceptanceTimeoutSec: number; +} + +export class PaymentDemandDirectorConfig extends BaseConfig implements PaymentDemandDirectorConfigOptions { + public readonly debitNotesAcceptanceTimeoutSec = 2 * 60; // 2 minutes + public readonly midAgreementDebitNoteIntervalSec = 2 * 60; // 2 minutes + public readonly midAgreementPaymentTimeoutSec = 12 * 60 * 60; // 12 hours + + constructor(options?: Partial) { + super(); + + if (options) { + Object.assign(this, options); + } + + if (!this.isPositiveInt(this.debitNotesAcceptanceTimeoutSec)) { + throw new GolemConfigError("The debit note acceptance timeout time has to be a positive integer"); + } + + if (!this.isPositiveInt(this.midAgreementDebitNoteIntervalSec)) { + throw new GolemConfigError("The debit note interval time has to be a positive integer"); + } + + if (!this.isPositiveInt(this.midAgreementPaymentTimeoutSec)) { + throw new GolemConfigError("The mid-agreement payment timeout time has to be a positive integer"); + } + } +} diff --git a/src/market/demand/directors/payment-demand-director.ts b/src/market/demand/directors/payment-demand-director.ts new file mode 100644 index 000000000..5767bf69b --- /dev/null +++ b/src/market/demand/directors/payment-demand-director.ts @@ -0,0 +1,27 @@ +import { DemandBodyBuilder } from "../demand-body-builder"; +import { IDemandDirector } from "../../market.module"; +import { PaymentDemandDirectorConfig } from "./payment-demand-director-config"; +import { Allocation } from "../../../payment"; +import { IMarketApi } from "../../api"; + +export class PaymentDemandDirector implements IDemandDirector { + constructor( + private allocation: Allocation, + private marketApiAdapter: IMarketApi, + private config: PaymentDemandDirectorConfig = new PaymentDemandDirectorConfig(), + ) {} + + async apply(builder: DemandBodyBuilder) { + // Configure mid-agreement payments + builder + .addProperty("golem.com.scheme.payu.debit-note.interval-sec?", this.config.midAgreementDebitNoteIntervalSec) + .addProperty("golem.com.scheme.payu.payment-timeout-sec?", this.config.midAgreementPaymentTimeoutSec) + .addProperty("golem.com.payment.debit-notes.accept-timeout?", this.config.debitNotesAcceptanceTimeoutSec); + + // Configure payment platform + const { constraints, properties } = await this.marketApiAdapter.getPaymentRelatedDemandDecorations( + this.allocation.id, + ); + builder.mergePrototype({ constraints, properties }); + } +} diff --git a/src/market/demand/directors/workload-demand-director-config.ts b/src/market/demand/directors/workload-demand-director-config.ts new file mode 100644 index 000000000..1c8c2d6cd --- /dev/null +++ b/src/market/demand/directors/workload-demand-director-config.ts @@ -0,0 +1,53 @@ +import { WorkloadDemandDirectorConfigOptions } from "../options"; +import { GolemConfigError } from "../../../shared/error/golem-error"; +import { BaseConfig } from "./base-config"; + +export enum PackageFormat { + GVMKitSquash = "gvmkit-squash", +} + +type RequiredWorkloadDemandConfigOptions = { + /** Number of seconds after which the agreement resulting from this demand will no longer be valid */ + expirationSec: number; +}; + +export class WorkloadDemandDirectorConfig extends BaseConfig { + readonly packageFormat: string = PackageFormat.GVMKitSquash; + readonly engine: string = "vm"; + readonly minMemGib: number = 0.5; + readonly minStorageGib: number = 2; + readonly minCpuThreads: number = 1; + readonly minCpuCores: number = 1; + readonly capabilities: string[] = []; + + readonly expirationSec: number; + + readonly manifest?: string; + readonly manifestSig?: string; + readonly manifestSigAlgorithm?: string; + readonly manifestCert?: string; + readonly useHttps?: boolean = false; + readonly imageHash?: string; + readonly imageTag?: string; + readonly imageUrl?: string; + + constructor(options: Partial & RequiredWorkloadDemandConfigOptions) { + super(); + + Object.assign(this, options); + + this.expirationSec = options.expirationSec; + + if (!this.imageHash && !this.manifest && !this.imageTag && !this.imageUrl) { + throw new GolemConfigError("You must define a package or manifest option"); + } + + if (this.imageUrl && !this.imageHash) { + throw new GolemConfigError("If you provide an imageUrl, you must also provide it's SHA3-224 hash in imageHash"); + } + + if (!this.isPositiveInt(this.expirationSec)) { + throw new GolemConfigError("The expirationSec param has to be a positive integer"); + } + } +} diff --git a/src/market/demand/directors/workload-demand-director.test.ts b/src/market/demand/directors/workload-demand-director.test.ts new file mode 100644 index 000000000..91cd2f5a6 --- /dev/null +++ b/src/market/demand/directors/workload-demand-director.test.ts @@ -0,0 +1,88 @@ +import { DemandBodyBuilder } from "../demand-body-builder"; +import { WorkloadDemandDirector } from "./workload-demand-director"; +import { WorkloadDemandDirectorConfig } from "./workload-demand-director-config"; + +describe("ActivityDemandDirector", () => { + test("should create properties with task_package and package_format", async () => { + const builder = new DemandBodyBuilder(); + + const director = new WorkloadDemandDirector( + new WorkloadDemandDirectorConfig({ + imageHash: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", + expirationSec: 600, + }), + ); + await director.apply(builder); + + const decorations = builder.getProduct(); + + expect(decorations.properties).toEqual( + expect.arrayContaining([ + { + key: "golem.srv.comp.task_package", + value: + "hash:sha3:529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4:http://registry.golem.network/download/529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", + }, + { key: "golem.srv.comp.vm.package_format", value: "gvmkit-squash" }, + ]), + ); + }); + + test("should create package with manifest decorations", async () => { + const builder = new DemandBodyBuilder(); + + const manifest = "XNBdCI6ICIyMTAwLTAxLTAxVDAwOjAxOjAwLjAwMDAwMFoiLAogICJtZXRhZGF0YSI6IHsKICAgICJuYW1lI="; + const manifestSig = "GzbdJDaW6FTajVYCKKZZvwpwVNBK3o40r/okna87wV9CVWW0+WUFwe="; + const manifestCert = "HCkExVUVDZ3dOUjI5c1pXMGdSbUZqZEc5eWVURW1NQ1FHQTFVRUF3d2RSMjlzWl="; + const manifestSigAlgorithm = "sha256"; + const capabilities = ["inet", "manifest-support"]; + + const director = new WorkloadDemandDirector( + new WorkloadDemandDirectorConfig({ + manifest, + manifestSig, + manifestCert, + manifestSigAlgorithm, + capabilities, + expirationSec: 600, + }), + ); + await director.apply(builder); + + const decorations = builder.getProduct(); + + expect(decorations.properties).toEqual( + expect.arrayContaining([ + { key: "golem.srv.comp.payload", value: manifest }, + { key: "golem.srv.comp.payload.sig", value: manifestSig }, + { key: "golem.srv.comp.payload.cert", value: manifestCert }, + { key: "golem.srv.comp.payload.sig.algorithm", value: manifestSigAlgorithm }, + { key: "golem.srv.comp.vm.package_format", value: "gvmkit-squash" }, + ]), + ); + + expect(decorations.constraints).toEqual( + expect.arrayContaining([ + "(golem.inf.mem.gib>=0.5)", + "(golem.inf.storage.gib>=2)", + "(golem.runtime.name=vm)", + "(golem.inf.cpu.cores>=1)", + "(golem.inf.cpu.threads>=1)", + "(golem.runtime.capabilities=inet)", + "(golem.runtime.capabilities=manifest-support)", + ]), + ); + }); + + test("should throw an error if user providers a negative expirationSec value", () => { + expect( + () => + new WorkloadDemandDirector( + new WorkloadDemandDirectorConfig({ + imageHash: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", + expirationSec: -3, + }), + ), + ).toThrow("The expirationSec param has to be a positive integer"); + }); +}); diff --git a/src/market/demand/directors/workload-demand-director.ts b/src/market/demand/directors/workload-demand-director.ts new file mode 100644 index 000000000..97ebbf034 --- /dev/null +++ b/src/market/demand/directors/workload-demand-director.ts @@ -0,0 +1,87 @@ +import { WorkloadDemandDirectorConfig } from "./workload-demand-director-config"; +import { ComparisonOperator, DemandBodyBuilder } from "../demand-body-builder"; +import { GolemError, GolemPlatformError } from "../../../shared/error/golem-error"; +import { IDemandDirector } from "../../market.module"; +import { EnvUtils } from "../../../shared/utils"; + +export class WorkloadDemandDirector implements IDemandDirector { + constructor(private config: WorkloadDemandDirectorConfig) {} + + public async apply(builder: DemandBodyBuilder) { + builder.addProperty("golem.srv.comp.expiration", Date.now() + this.config.expirationSec * 1000); + + builder + .addProperty("golem.srv.comp.vm.package_format", this.config.packageFormat) + .addConstraint("golem.runtime.name", this.config.engine); + + if (this.config.capabilities.length) + this.config.capabilities.forEach((cap) => builder.addConstraint("golem.runtime.capabilities", cap)); + + builder + .addConstraint("golem.inf.mem.gib", this.config.minMemGib, ComparisonOperator.GtEq) + .addConstraint("golem.inf.storage.gib", this.config.minStorageGib, ComparisonOperator.GtEq) + .addConstraint("golem.inf.cpu.cores", this.config.minCpuCores, ComparisonOperator.GtEq) + .addConstraint("golem.inf.cpu.threads", this.config.minCpuThreads, ComparisonOperator.GtEq); + + if (this.config.imageUrl) { + const taskPackage = await this.resolveTaskPackageFromCustomUrl(); + builder.addProperty("golem.srv.comp.task_package", taskPackage); + } else if (this.config.imageHash || this.config.imageTag) { + const taskPackage = await this.resolveTaskPackageUrl(); + builder.addProperty("golem.srv.comp.task_package", taskPackage); + } + + this.addManifestDecorations(builder); + } + + private async resolveTaskPackageFromCustomUrl(): Promise { + if (!this.config.imageUrl) { + throw new GolemPlatformError("Tried to resolve task package from custom url, but no url was provided"); + } + if (!this.config.imageHash) { + throw new GolemPlatformError( + "Tried to resolve task package from custom url, but no hash was provided. Please calculate the SHA3-224 hash of the image and provide it as `imageHash`", + ); + } + return `hash:sha3:${this.config.imageHash}:${this.config.imageUrl}`; + } + + private async resolveTaskPackageUrl(): Promise { + const repoUrl = EnvUtils.getRepoUrl(); + + const useHttps = this.config.useHttps; + + const isDev = EnvUtils.isDevMode(); + + let hash = this.config.imageHash; + const tag = this.config.imageTag; + + const url = `${repoUrl}/v1/image/info?${isDev ? "dev=true" : "count=true"}&${tag ? `tag=${tag}` : `hash=${hash}`}`; + + try { + const response = await fetch(url); + if (!response.ok) { + throw new GolemPlatformError(`Unable to get image ${await response.text()}`); + } + + const data = await response.json(); + + const imageUrl = useHttps ? data.https : data.http; + hash = data.sha3; + + return `hash:sha3:${hash}:${imageUrl}`; + } catch (error) { + if (error instanceof GolemError) throw error; + throw new GolemPlatformError(`Failed to fetch image: ${error}`); + } + } + + private addManifestDecorations(builder: DemandBodyBuilder): void { + if (!this.config.manifest) return; + builder.addProperty("golem.srv.comp.payload", this.config.manifest); + if (this.config.manifestSig) builder.addProperty("golem.srv.comp.payload.sig", this.config.manifestSig); + if (this.config.manifestSigAlgorithm) + builder.addProperty("golem.srv.comp.payload.sig.algorithm", this.config.manifestSigAlgorithm); + if (this.config.manifestCert) builder.addProperty("golem.srv.comp.payload.cert", this.config.manifestCert); + } +} diff --git a/src/market/demand/index.ts b/src/market/demand/index.ts new file mode 100644 index 000000000..b8afde293 --- /dev/null +++ b/src/market/demand/index.ts @@ -0,0 +1,2 @@ +export * from "./demand"; +export * from "./demand-body-builder"; diff --git a/src/market/demand/options.ts b/src/market/demand/options.ts new file mode 100644 index 000000000..9cd8b5ce8 --- /dev/null +++ b/src/market/demand/options.ts @@ -0,0 +1,68 @@ +import { RequireAtLeastOne } from "../../shared/utils/types"; + +/** + * Specifies a set of options related to computation resources that will be used to form the demand + */ +export type ResourceDemandOptions = { + /** Minimum required memory to execute application GB */ + minMemGib: number; + /** Minimum required disk storage to execute tasks in GB */ + minStorageGib: number; + /** Minimum required CPU threads */ + minCpuThreads: number; + /** Minimum required CPU cores */ + minCpuCores: number; +}; + +/** + * Specifies a set of options related to runtime configuration that will be used to form the demand + */ +export type RuntimeDemandOptions = { + /** Type of engine required: vm, wasm, vm-nvidia, etc... */ + engine: string; + + /** Required providers capabilities to run application: example: ["vpn"] */ + capabilities: string[]; +}; + +/** + * Specifies a set of options related to computation manifest that can be used to form the demand + */ +export type ManifestDemandOptions = { + manifest: string; + /** Signature of base64 encoded Computation Payload Manifest **/ + manifestSig: string; + /** Algorithm of manifest signature, e.g. "sha256" **/ + manifestSigAlgorithm: string; + /** Certificate - base64 encoded public certificate (DER or PEM) matching key used to generate signature **/ + manifestCert: string; +}; + +/** + * Specifies a set of options related to the Golem VM Image (GVMI) that will be used to form the demand + */ +export type ImageDemandOptions = { + /** + * If you want a provider to download the image from your local filesystem or + * a different registry than the default one, you can provide the image url here. + * Note that to use this option you need to also provide the image SHA3-224 hash. + */ + imageUrl?: string; + + /** finds package by its contents hash */ + imageHash?: string; + + /** finds package by registry tag */ + imageTag?: string; + + /** + * Force the image download url that will be passed to the provider to use HTTPS. + * This option is only relevant when you use `imageHash` or `imageTag` options. + * Default is false + */ + useHttps?: boolean; +}; + +export type WorkloadDemandDirectorConfigOptions = RuntimeDemandOptions & + ResourceDemandOptions & + RequireAtLeastOne; diff --git a/src/market/draft-offer-proposal-pool.test.ts b/src/market/draft-offer-proposal-pool.test.ts new file mode 100644 index 000000000..52158e1a7 --- /dev/null +++ b/src/market/draft-offer-proposal-pool.test.ts @@ -0,0 +1,165 @@ +import { DraftOfferProposalPool } from "./draft-offer-proposal-pool"; +import { instance, mock, when } from "@johanblumenberg/ts-mockito"; +import { OfferProposal } from "./index"; +import { GolemAbortError, GolemTimeoutError } from "../shared/error/golem-error"; + +describe("Draft Offer Proposal Pool", () => { + // GIVEN + const mockProposal = mock(OfferProposal); + // Most of the time we're testing the case when the Proposal is in `Draft` status + when(mockProposal.isDraft()).thenReturn(true); + + // NOTE: ts-mockito instance + JS Set.add() doesn't play along, 2x instance(mockProposal) produces "the same" value for (Set.add) + + const secondMockProposal = mock(OfferProposal); + // Most of the time we're testing the case when the Proposal is in `Draft` status + when(secondMockProposal.isDraft()).thenReturn(true); + + describe("Adding proposals", () => { + describe("Positive cases", () => { + it("It's not possible to add the same proposal twice to the pool", () => { + const pool = new DraftOfferProposalPool(); + + const proposal = instance(mockProposal); + + pool.add(proposal); + pool.add(proposal); + + expect(pool.count()).toEqual(1); + }); + + it("It's possible to add different proposals to the pool", () => { + const pool = new DraftOfferProposalPool(); + + pool.add(instance(mockProposal)); + pool.add(instance(secondMockProposal)); + + expect(pool.count()).toEqual(2); + }); + }); + + describe("Negative cases", () => { + it("Will throw an error if the proposal is not in Draft state", async () => { + const pool = new DraftOfferProposalPool(); + + const proposalMock = mock(OfferProposal); + when(proposalMock.isDraft()).thenReturn(false); + + expect(() => pool.add(instance(proposalMock))).toThrow("Cannot add a non-draft proposal to the pool"); + }); + }); + }); + + describe("Acquiring proposals", () => { + describe("Positive cases", () => { + it("Acquire does not change the size of the pool", async () => { + const pool = new DraftOfferProposalPool(); + + pool.add(instance(mockProposal)); + pool.add(instance(secondMockProposal)); + + expect(pool.count()).toEqual(2); + const leased = await pool.acquire(); + expect(pool.count()).toEqual(2); + await pool.release(leased); + expect(pool.count()).toEqual(2); + }); + + it("Is not possible to acquire the same instance twice", async () => { + const pool = new DraftOfferProposalPool(); + + pool.add(instance(mockProposal)); + pool.add(instance(secondMockProposal)); + + const a = await pool.acquire(); + const b = await pool.acquire(); + + expect(a).not.toBe(b); + }); + + it("ascquire a proposal using offerProposalSelector", async () => { + when(mockProposal.provider).thenReturn({ + name: "provider-1", + walletAddress: "1", + id: "1", + }); + when(secondMockProposal.provider).thenReturn({ + name: "provider-2", + walletAddress: "2", + id: "2", + }); + const proposal1 = instance(mockProposal); + const proposal2 = instance(secondMockProposal); + const scores = { + "provider-2": 100, + "provider-1": 50, + }; + const bestProviderSelector = (scores: { [providerName: string]: number }) => (proposals: OfferProposal[]) => { + proposals.sort((a, b) => ((scores?.[a.provider.name] || 0) >= (scores?.[b.provider.name] || 0) ? -1 : 1)); + return proposals[0]; + }; + const pool = new DraftOfferProposalPool({ selectOfferProposal: bestProviderSelector(scores) }); + + pool.add(proposal1); + pool.add(proposal2); + + const a = await pool.acquire(); + + expect(a).toBe(proposal2); + }); + }); + describe("Negative cases", () => { + it("should abort the acquiring proposal by timeout", async () => { + const pool = new DraftOfferProposalPool(); + await expect(pool.acquire(1)).rejects.toThrow(new GolemTimeoutError("Could not provide any proposal in time")); + }); + it("should abort the acquiring proposal by signal", async () => { + const pool = new DraftOfferProposalPool(); + const ac = new AbortController(); + ac.abort(); + await expect(pool.acquire(ac.signal)).rejects.toThrow( + new GolemAbortError("The acquiring of proposals has been aborted"), + ); + }); + }); + }); + + describe("Is ready", () => { + it("Returns true when the min number of elements is available in the pool, false otherwise", async () => { + const pool = new DraftOfferProposalPool({ minCount: 1 }); + expect(pool.isReady()).toEqual(false); + + pool.add(instance(mockProposal)); + expect(pool.isReady()).toEqual(true); + }); + }); + + describe("Clearing the pool", () => { + it("should remove all the items from the pool, triggering respective events", async () => { + const pool = new DraftOfferProposalPool(); + + const addedCallback = jest.fn(); + const removedCallback = jest.fn(); + const clearedCallback = jest.fn(); + + pool.events.on("added", addedCallback); + pool.events.on("removed", removedCallback); + pool.events.on("cleared", clearedCallback); + + const p1 = instance(mockProposal); + const p2 = instance(secondMockProposal); + + pool.add(p1); + pool.add(p2); + + expect(addedCallback).toHaveBeenCalledTimes(2); + expect(pool.count()).toEqual(2); + + await pool.clear(); + + expect(clearedCallback).toHaveBeenCalled(); + expect(removedCallback).toHaveBeenCalledTimes(2); + expect(pool.count()).toEqual(0); + }); + }); +}); diff --git a/src/market/draft-offer-proposal-pool.ts b/src/market/draft-offer-proposal-pool.ts new file mode 100644 index 000000000..be9aae010 --- /dev/null +++ b/src/market/draft-offer-proposal-pool.ts @@ -0,0 +1,242 @@ +import { OfferProposal, OfferProposalFilter } from "./proposal"; +import AsyncLock from "async-lock"; +import { EventEmitter } from "eventemitter3"; +import { GolemMarketError, MarketErrorCode } from "./error"; +import { createAbortSignalFromTimeout, defaultLogger, Logger, sleep } from "../shared/utils"; +import { Observable, Subscription } from "rxjs"; +import { GolemAbortError, GolemTimeoutError } from "../shared/error/golem-error"; + +export type OfferProposalSelector = (proposals: OfferProposal[]) => OfferProposal; + +export interface ProposalPoolOptions { + /** + * A user-defined function that will be used by {@link DraftOfferProposalPool.acquire} to pick the best fitting offer proposal from available ones + */ + selectOfferProposal?: OfferProposalSelector; + + /** + * User defined filter function which will determine if the offer proposal is valid for use. + * + * Offer proposals are validated before being handled to the caller of {@link DraftOfferProposalPool.acquire} + */ + validateOfferProposal?: OfferProposalFilter; + + /** + * Min number of proposals in pool so that it can be considered as ready to use + * + * @default 0 + */ + minCount?: number; + + logger?: Logger; +} + +export interface ProposalPoolEvents { + added: (event: { proposal: OfferProposal }) => void; + removed: (event: { proposal: OfferProposal }) => void; + acquired: (event: { proposal: OfferProposal }) => void; + released: (event: { proposal: OfferProposal }) => void; + cleared: () => void; +} + +/** + * Pool of draft offer proposals that are ready to be promoted to agreements with Providers + * + * Reaching this pool means that the related initial proposal which was delivered by Yagna in response + * to the subscription with the Demand has been fully negotiated between the Provider and Requestor. + * + * This pool should contain only offer proposals that can be used to pursue the final Agreement between the + * parties. + * + * Technically, the "market" part of you application should populate this pool with such offer proposals. + */ +export class DraftOfferProposalPool { + public readonly events = new EventEmitter(); + + private logger: Logger; + private readonly lock: AsyncLock = new AsyncLock(); + + /** {@link ProposalPoolOptions.minCount} */ + private readonly minCount: number = 0; + + /** {@link ProposalPoolOptions.selectOfferProposal} */ + private readonly selectOfferProposal: OfferProposalSelector = (proposals: OfferProposal[]) => proposals[0]; + + /** {@link ProposalPoolOptions.validateOfferProposal} */ + private readonly validateOfferProposal: OfferProposalFilter = (proposal: OfferProposal) => proposal !== undefined; + + /** + * The proposals that were not yet leased to anyone and are available for lease + */ + private available = new Set(); + + /** + * The proposal that were already leased to someone and shouldn't be leased again + */ + private leased = new Set(); + + public constructor(private options?: ProposalPoolOptions) { + if (options?.selectOfferProposal) { + this.selectOfferProposal = options.selectOfferProposal; + } + if (options?.validateOfferProposal) { + this.validateOfferProposal = options.validateOfferProposal; + } + + if (options?.minCount && options.minCount >= 0) { + this.minCount = options.minCount; + } + + this.logger = this.logger = options?.logger || defaultLogger("proposal-pool"); + } + + /** + * Pushes the provided proposal to the list of proposals available for lease + */ + public add(proposal: OfferProposal) { + if (!proposal.isDraft()) { + this.logger.error("Cannot add a non-draft proposal to the pool", { proposalId: proposal.id }); + throw new GolemMarketError("Cannot add a non-draft proposal to the pool", MarketErrorCode.InvalidProposal); + } + + this.available.add(proposal); + + this.events.emit("added", { proposal }); + } + + /** + * Attempts to obtain a single proposal from the pool + * @param signalOrTimeout - the timeout in milliseconds or an AbortSignal that will be used to cancel the acquiring + */ + public acquire(signalOrTimeout?: number | AbortSignal): Promise { + const signal = createAbortSignalFromTimeout(signalOrTimeout); + return this.lock.acquire("proposal-pool", async () => { + let proposal: OfferProposal | null = null; + + while (proposal === null) { + if (signal.aborted) { + throw signal.reason.name === "TimeoutError" + ? new GolemTimeoutError("Could not provide any proposal in time") + : new GolemAbortError("The acquiring of proposals has been aborted", signal.reason); + } + // Try to get one + proposal = this.available.size > 0 ? this.selectOfferProposal([...this.available]) : null; + + if (proposal) { + // Validate + if (!this.validateOfferProposal(proposal)) { + // Drop if not valid + this.removeFromAvailable(proposal); + // Keep searching + proposal = null; + } + } + // if not found or not valid wait a while for next try + if (!proposal) { + await sleep(1); + } + } + + this.available.delete(proposal); + this.leased.add(proposal); + + this.events.emit("acquired", { proposal }); + + return proposal; + }); + } + + /** + * Releases the proposal back to the pool + * + * Validates if the proposal is still usable before putting it back to the list of available ones + * @param proposal + */ + public release(proposal: OfferProposal): Promise { + return this.lock.acquire("proposal-pool", () => { + this.leased.delete(proposal); + + if (this.validateOfferProposal(proposal)) { + this.available.add(proposal); + this.events.emit("released", { proposal }); + } else { + this.events.emit("removed", { proposal }); + } + }); + } + + public remove(proposal: OfferProposal): Promise { + return this.lock.acquire("proposal-pool", () => { + if (this.leased.has(proposal)) { + this.leased.delete(proposal); + this.events.emit("removed", { proposal }); + } + + if (this.available.has(proposal)) { + this.available.delete(proposal); + this.events.emit("removed", { proposal }); + } + }); + } + + /** + * Returns the number of all items in the pool (available + leased out) + */ + public count() { + return this.availableCount() + this.leasedCount(); + } + + /** + * Returns the number of items that are possible to lease from the pool + */ + public availableCount() { + return this.available.size; + } + + /** + * Returns the number of items that were leased out of the pool + */ + public leasedCount() { + return this.leased.size; + } + + /** + * Tells if the pool is ready to take items from + */ + public isReady() { + return this.count() >= this.minCount; + } + + /** + * Clears the pool entirely + */ + public async clear() { + return this.lock.acquire("proposal-pool", () => { + for (const proposal of this.available) { + this.available.delete(proposal); + this.events.emit("removed", { proposal }); + } + + for (const proposal of this.leased) { + this.leased.delete(proposal); + this.events.emit("removed", { proposal }); + } + + this.available = new Set(); + this.leased = new Set(); + this.events.emit("cleared"); + }); + } + + protected removeFromAvailable(proposal: OfferProposal): void { + this.available.delete(proposal); + this.events.emit("removed", { proposal }); + } + + public readFrom(source: Observable): Subscription { + return source.subscribe({ + next: (proposal) => this.add(proposal), + error: (err) => this.logger.error("Error while collecting proposals", err), + }); + } +} diff --git a/src/market/error.ts b/src/market/error.ts index 831e62b48..90eb47ef3 100644 --- a/src/market/error.ts +++ b/src/market/error.ts @@ -1,24 +1,27 @@ -import { GolemModuleError } from "../error/golem-error"; -import { Demand } from "./demand"; +import { GolemModuleError } from "../shared/error/golem-error"; export enum MarketErrorCode { - ServiceNotInitialized, - MissingAllocation, - SubscriptionFailed, - InvalidProposal, - ProposalResponseFailed, - ProposalRejectionFailed, - DemandExpired, - AgreementTerminationFailed, - AgreementCreationFailed, - AgreementApprovalFailed, + CouldNotGetAgreement = "CouldNotGetAgreement", + CouldNotGetProposal = "CouldNotGetProposal", + ServiceNotInitialized = "ServiceNotInitialized", + MissingAllocation = "MissingAllocation", + SubscriptionFailed = "SubscriptionFailed", + InvalidProposal = "InvalidProposal", + ProposalResponseFailed = "ProposalResponseFailed", + ProposalRejectionFailed = "ProposalRejectionFailed", + DemandExpired = "DemandExpired", + ResourceRentalTerminationFailed = "ResourceRentalTerminationFailed", + ResourceRentalCreationFailed = "ResourceRentalCreationFailed", + AgreementApprovalFailed = "AgreementApprovalFailed", + NoProposalAvailable = "NoProposalAvailable", + InternalError = "InternalError", + ScanFailed = "ScanFailed", } export class GolemMarketError extends GolemModuleError { constructor( message: string, public code: MarketErrorCode, - public demand?: Demand, public previous?: Error, ) { super(message, code, previous); diff --git a/src/market/factory.test.ts b/src/market/factory.test.ts deleted file mode 100644 index ce1fa3fc2..000000000 --- a/src/market/factory.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { DemandFactory } from "./factory"; -import { anything, capture, imock, instance, mock, when } from "@johanblumenberg/ts-mockito"; -import { Package } from "../package"; -import { Allocation } from "../payment"; -import { YagnaApi } from "../utils"; -import { RequestorApi as MarketRequestorApi } from "ya-ts-client/dist/ya-market/src/api/requestor-api"; - -describe("Demand Factory", () => { - describe("mid-agreement payments support", () => { - describe("default behaviour", () => { - it("it configures mid-agreement payments by default", async () => { - // Given - const pkg = mock(Package); - const allocation = mock(Allocation); - - const market = mock(MarketRequestorApi); - const api = imock(); - - // When - when(api.market).thenReturn(instance(market)); - - when(pkg.getDemandDecoration()).thenResolve({ - properties: [], - constraints: [], - }); - - when(allocation.getDemandDecoration()).thenResolve({ - properties: [], - constraints: [], - }); - - when(market.subscribeDemand(anything())).thenResolve({ - config: {}, - headers: {}, - status: 200, - statusText: "OK", - data: "subscription-id", - }); - - const factory = new DemandFactory(instance(pkg), instance(allocation), instance(api)); - const demand = await factory.create(); - - // Then - const [demandRequestBody] = capture(market.subscribeDemand).last(); - - // The properties responsible for mid-agreements payments are set - expect(demandRequestBody.properties["golem.com.payment.debit-notes.accept-timeout?"]).toBeDefined(); - expect(demandRequestBody.properties["golem.com.scheme.payu.debit-note.interval-sec?"]).toBeDefined(); - expect(demandRequestBody.properties["golem.com.scheme.payu.payment-timeout-sec?"]).toBeDefined(); - expect(demandRequestBody.properties["golem.srv.comp.expiration"]).toBeDefined(); - - expect(demand).toBeDefined(); - }); - }); - }); -}); diff --git a/src/market/factory.ts b/src/market/factory.ts deleted file mode 100644 index bd5fb2986..000000000 --- a/src/market/factory.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Package } from "../package"; -import { Allocation } from "../payment"; -import { Demand, DemandOptions } from "./demand"; -import { DemandConfig } from "./config"; -import * as events from "../events/events"; -import { DecorationsBuilder, MarketDecoration } from "./builder"; -import { YagnaApi } from "../utils"; -import { GolemMarketError, MarketErrorCode } from "./error"; - -/** - * @internal - */ -export class DemandFactory { - private options: DemandConfig; - - constructor( - private readonly taskPackage: Package, - private readonly allocation: Allocation, - private readonly yagnaApi: YagnaApi, - options?: DemandOptions, - ) { - this.options = new DemandConfig(options); - } - - async create(): Promise { - try { - const decorations = await this.getDecorations(); - const demandRequest = new DecorationsBuilder().addDecorations(decorations).getDemandRequest(); - const { data: id } = await this.yagnaApi.market.subscribeDemand(demandRequest); - this.options.eventTarget?.dispatchEvent( - new events.DemandSubscribed({ - id, - details: new DecorationsBuilder().addDecorations(decorations).getDecorations(), - }), - ); - this.options.logger.info(`Demand published on the market`); - return new Demand(id, demandRequest, this.allocation, this.yagnaApi, this.options); - } catch (error) { - const reason = error.response?.data?.message || error.toString(); - this.options.eventTarget?.dispatchEvent(new events.DemandFailed({ reason })); - throw new GolemMarketError( - `Could not publish demand on the market. ${reason}`, - MarketErrorCode.SubscriptionFailed, - undefined, - error, - ); - } - } - - private async getDecorations(): Promise { - const taskDecorations = await this.taskPackage.getDemandDecoration(); - const allocationDecoration = await this.allocation.getDemandDecoration(); - const baseDecoration = this.getBaseDecorations(); - return [taskDecorations, allocationDecoration, baseDecoration]; - } - - private getBaseDecorations(): MarketDecoration { - const builder = new DecorationsBuilder(); - - // Configure basic properties - builder - .addProperty("golem.srv.caps.multi-activity", true) - .addProperty("golem.srv.comp.expiration", Date.now() + this.options.expirationSec * 1000) - .addProperty("golem.node.debug.subnet", this.options.subnetTag) - .addProperty("golem.com.payment.debit-notes.accept-timeout?", this.options.debitNotesAcceptanceTimeoutSec) - .addConstraint("golem.com.pricing.model", "linear") - .addConstraint("golem.node.debug.subnet", this.options.subnetTag); - - // Configure mid-agreement payments - builder - .addProperty("golem.com.scheme.payu.debit-note.interval-sec?", this.options.midAgreementDebitNoteIntervalSec) - .addProperty("golem.com.scheme.payu.payment-timeout-sec?", this.options.midAgreementPaymentTimeoutSec); - - return builder.getDecorations(); - } -} diff --git a/src/market/helpers.spec.ts b/src/market/helpers.spec.ts index 66f49af17..f0badc979 100644 --- a/src/market/helpers.spec.ts +++ b/src/market/helpers.spec.ts @@ -1,7 +1,7 @@ import { MockPropertyPolicy, imock, instance, when } from "@johanblumenberg/ts-mockito"; import { getHealthyProvidersWhiteList } from "./helpers"; -import { GolemInternalError } from "../error/golem-error"; +import { GolemInternalError } from "../shared/error/golem-error"; const mockFetch = jest.spyOn(global, "fetch"); const response = imock(); diff --git a/src/market/helpers.ts b/src/market/helpers.ts index a38b1706f..8802c42ed 100644 --- a/src/market/helpers.ts +++ b/src/market/helpers.ts @@ -1,4 +1,4 @@ -import { GolemInternalError } from "../error/golem-error"; +import { GolemInternalError } from "../shared/error/golem-error"; /** * Helps to obtain a whitelist of providers which were health-tested. @@ -9,7 +9,7 @@ import { GolemInternalError } from "../error/golem-error"; */ export async function getHealthyProvidersWhiteList(): Promise { try { - const response = await fetch("https://provider-health.golem.network/v1/provider-whitelist"); + const response = await fetch("https://reputation.dev-test.golem.network/v1/provider-whitelist"); if (response.ok) { return response.json(); diff --git a/src/market/index.ts b/src/market/index.ts index 78537518a..61cbc3585 100644 --- a/src/market/index.ts +++ b/src/market/index.ts @@ -1,8 +1,15 @@ -export { MarketService, ProposalFilter, MarketOptions } from "./service"; -export { Demand, DEMAND_EVENT_TYPE, DemandOptions, DemandEvent } from "./demand"; -export { Proposal, ProposalDetails } from "./proposal"; -export { MarketDecoration } from "./builder"; -export { DemandConfig } from "./config"; -export * as ProposalFilterFactory from "./strategy"; +export { OfferProposalFilter } from "./proposal/offer-proposal"; +export { Demand, BasicDemandPropertyConfig, DemandSpecification } from "./demand/demand"; +export { OfferProposal, ProposalDTO } from "./proposal/offer-proposal"; +export * as OfferProposalFilterFactory from "./strategy"; export { GolemMarketError, MarketErrorCode } from "./error"; export * as MarketHelpers from "./helpers"; +export * from "./draft-offer-proposal-pool"; +export * from "./market.module"; +export * from "./api"; +export * from "./agreement"; +export { BasicDemandDirector } from "./demand/directors/basic-demand-director"; +export { PaymentDemandDirector } from "./demand/directors/payment-demand-director"; +export { WorkloadDemandDirector } from "./demand/directors/workload-demand-director"; +export * from "./proposal/market-proposal-event"; +export * from "./scan"; diff --git a/src/market/market.module.test.ts b/src/market/market.module.test.ts new file mode 100644 index 000000000..35cb21bc2 --- /dev/null +++ b/src/market/market.module.test.ts @@ -0,0 +1,642 @@ +import { _, imock, instance, mock, reset, spy, verify, when } from "@johanblumenberg/ts-mockito"; +import { Logger, YagnaApi } from "../shared/utils"; +import { MarketModuleImpl } from "./market.module"; +import { Demand, DemandSpecification } from "./demand"; +import { Subject, take } from "rxjs"; +import { MarketProposalEvent, OfferProposal, ProposalProperties } from "./proposal"; +import { MarketApiAdapter } from "../shared/yagna/"; +import { IActivityApi, IFileServer } from "../activity"; +import { StorageProvider } from "../shared/storage"; +import { GolemMarketError } from "./error"; +import { Allocation, IPaymentApi } from "../payment"; +import { INetworkApi, NetworkModule } from "../network"; +import { DraftOfferProposalPool } from "./draft-offer-proposal-pool"; +import { Agreement, AgreementEvent, ProviderInfo } from "./agreement"; +import { waitAndCall, waitForCondition } from "../shared/utils/wait"; +import { MarketOrderSpec } from "../golem-network"; +import { GolemAbortError } from "../shared/error/golem-error"; + +const mockMarketApiAdapter = mock(MarketApiAdapter); +const mockYagna = mock(YagnaApi); +const mockAgreement = mock(Agreement); + +const testAgreementEvent$ = new Subject(); + +let marketModule: MarketModuleImpl; + +const DEMAND_REFRESH_INTERVAL_SEC = 60; + +beforeEach(() => { + jest.useFakeTimers(); + jest.resetAllMocks(); + + reset(mockMarketApiAdapter); + reset(mockAgreement); + + when(mockMarketApiAdapter.collectAgreementEvents()).thenReturn(testAgreementEvent$); + + marketModule = new MarketModuleImpl( + { + activityApi: instance(imock()), + paymentApi: instance(imock()), + networkApi: instance(imock()), + yagna: instance(mockYagna), + logger: instance(imock()), + marketApi: instance(mockMarketApiAdapter), + fileServer: instance(imock()), + storageProvider: instance(imock()), + networkModule: instance(imock()), + }, + { + demandRefreshIntervalSec: DEMAND_REFRESH_INTERVAL_SEC, + }, + ); +}); + +describe("Market module", () => { + describe("buildDemand()", () => { + it("should build a demand", async () => { + const allocation = { + id: "allocation-id", + paymentPlatform: "erc20-holesky-tglm", + } as Allocation; + + when(mockMarketApiAdapter.getPaymentRelatedDemandDecorations("allocation-id")).thenResolve({ + properties: [ + { + key: "golem.com.payment.platform.erc20-holesky-tglm.address", + value: "0x123", + }, + { + key: "golem.com.payment.protocol.version", + value: "2", + }, + ], + constraints: [ + "(golem.com.payment.platform.erc20-holesky-tglm.address=*)", + "(golem.com.payment.protocol.version>1)", + ], + }); + + const rentalDurationHours = 1; + const demandSpecification = await marketModule.buildDemandDetails( + { + workload: { + imageHash: "AAAAHASHAAAA", + imageUrl: "https://custom.image.url/", + }, + payment: { + debitNotesAcceptanceTimeoutSec: 42, + midAgreementDebitNoteIntervalSec: 42, + midAgreementPaymentTimeoutSec: 42, + }, + }, + { + rentHours: rentalDurationHours, + pricing: { + model: "burn-rate", + avgGlmPerHour: 1, + }, + }, + allocation, + ); + + const expectedConstraints = [ + "(golem.com.pricing.model=linear)", + "(golem.node.debug.subnet=public)", + "(golem.runtime.name=vm)", + "(golem.inf.mem.gib>=0.5)", + "(golem.inf.storage.gib>=2)", + "(golem.inf.cpu.cores>=1)", + "(golem.inf.cpu.threads>=1)", + "(golem.com.payment.platform.erc20-holesky-tglm.address=*)", + "(golem.com.payment.protocol.version>1)", + ]; + + const expectedProperties = [ + { + key: "golem.srv.caps.multi-activity", + value: true, + }, + { + key: "golem.node.debug.subnet", + value: "public", + }, + { + key: "golem.srv.comp.expiration", + value: Date.now() + rentalDurationHours * 60 * 60 * 1000, + }, + { + key: "golem.srv.comp.vm.package_format", + value: "gvmkit-squash", + }, + { + key: "golem.srv.comp.task_package", + value: "hash:sha3:AAAAHASHAAAA:https://custom.image.url/", + }, + { + key: "golem.com.scheme.payu.debit-note.interval-sec?", + value: 42, + }, + { + key: "golem.com.scheme.payu.payment-timeout-sec?", + value: 42, + }, + { + key: "golem.com.payment.debit-notes.accept-timeout?", + value: 42, + }, + { + key: "golem.com.payment.platform.erc20-holesky-tglm.address", + value: "0x123", + }, + { + key: "golem.com.payment.protocol.version", + value: "2", + }, + ]; + + expect(demandSpecification.paymentPlatform).toBe(allocation.paymentPlatform); + expect(demandSpecification.prototype.constraints).toEqual(expect.arrayContaining(expectedConstraints)); + expect(demandSpecification.prototype.properties).toEqual(expectedProperties); + }); + }); + + describe("publishDemand()", () => { + it("should publish a demand", (done) => { + const mockSpecification = mock(DemandSpecification); + when(mockMarketApiAdapter.publishDemandSpecification(mockSpecification)).thenCall(async (specification) => { + return new Demand("demand-id", specification); + }); + + const demand$ = marketModule.publishAndRefreshDemand(mockSpecification); + demand$.pipe(take(1)).subscribe({ + next: (demand) => { + try { + expect(demand).toEqual(new Demand("demand-id", mockSpecification)); + done(); + } catch (error) { + done(error); + } + }, + error: (error) => done(error), + }); + }); + + it("should emit a new demand every specified interval", (done) => { + const mockSpecification = mock(DemandSpecification); + const mockSpecificationInstance = instance(mockSpecification); + const mockDemand0 = new Demand("demand-id-0", mockSpecificationInstance); + const mockDemand1 = new Demand("demand-id-1", mockSpecificationInstance); + const mockDemand2 = new Demand("demand-id-2", mockSpecificationInstance); + + when(mockMarketApiAdapter.publishDemandSpecification(_)) + .thenResolve(mockDemand0) + .thenResolve(mockDemand1) + .thenResolve(mockDemand2); + when(mockMarketApiAdapter.unpublishDemand(_)).thenResolve(); + + const demand$ = marketModule.publishAndRefreshDemand(mockSpecificationInstance); + const demands: Demand[] = []; + demand$.pipe(take(3)).subscribe({ + next: (demand) => { + demands.push(demand); + jest.advanceTimersByTime(DEMAND_REFRESH_INTERVAL_SEC * 1000); + }, + complete: () => { + try { + expect(demands).toEqual([mockDemand0, mockDemand1, mockDemand2]); + verify(mockMarketApiAdapter.unpublishDemand(demands[0])).once(); + verify(mockMarketApiAdapter.unpublishDemand(demands[1])).once(); + verify(mockMarketApiAdapter.unpublishDemand(demands[2])).once(); + verify(mockMarketApiAdapter.unpublishDemand(_)).times(3); + done(); + } catch (error) { + done(error); + } + }, + error: (error) => done(error), + }); + }); + + it("should throw an error if the demand cannot be subscribed", (done) => { + const mockSpecification = mock(DemandSpecification); + const details = instance(mockSpecification); + + when(mockMarketApiAdapter.publishDemandSpecification(_)).thenReject(new Error("Triggered")); + + const demand$ = marketModule.publishAndRefreshDemand(details); + + demand$.subscribe({ + error: (err: GolemMarketError) => { + try { + expect(err.message).toEqual("Could not publish demand on the market"); + expect(err.previous?.message).toEqual("Triggered"); + done(); + } catch (assertionError) { + done(assertionError); + } + }, + }); + }); + }); + + describe("startCollectingProposals()", () => { + const initialOfferProperties: ProposalProperties = { + "golem.activity.caps.transfer.protocol": ["http"], + "golem.com.payment.chosen-platform": "erc20-hoeslky-glm", + "golem.com.payment.debit-notes.accept-timeout?": 120, + "golem.com.payment.protocol.version": 2, + "golem.com.pricing.model": "linear", + "golem.com.pricing.model.linear.coeffs": [0.0, 0.0, 0.0], + "golem.com.scheme": "payu", + "golem.com.usage.vector": [], + "golem.inf.cpu.architecture": "", + "golem.inf.cpu.brand": "", + "golem.inf.cpu.capabilities": [], + "golem.inf.cpu.model": "", + "golem.inf.cpu.vendor": "GenuineIntel", + "golem.node.id.name": "", + "golem.runtime.capabilities": [], + "golem.runtime.name": "", + "golem.runtime.version": "", + "golem.srv.caps.multi-activity": false, + "golem.srv.comp.expiration": 0, + "golem.srv.comp.task_package": "", + "golem.inf.cpu.cores": 2, + "golem.inf.cpu.threads": 2, + "golem.inf.mem.gib": 1, + "golem.inf.storage.gib": 1, + }; + + test("should negotiate any initial proposal", async () => { + jest.useRealTimers(); + + const spec = new DemandSpecification( + { + properties: [], + constraints: [], + }, + "erc20-holesky-tglm", + ); + + const providerInfo: ProviderInfo = { + id: "test-provider-id", + name: "test-provider-name", + walletAddress: "0xTestWallet", + }; + + const mockInitialOfferProposal = mock(OfferProposal); + when(mockInitialOfferProposal.isInitial()).thenReturn(true); + when(mockInitialOfferProposal.isValid()).thenReturn(true); + when(mockInitialOfferProposal.provider).thenReturn(providerInfo); + when(mockInitialOfferProposal.properties).thenReturn(initialOfferProperties); + when(mockInitialOfferProposal.pricing).thenReturn({ + cpuSec: 0.4 / 3600, + envSec: 0.4 / 3600, + start: 0.4, + }); + + const mockDraftOfferProposal = mock(OfferProposal); + when(mockDraftOfferProposal.isDraft()).thenReturn(true); + when(mockDraftOfferProposal.isValid()).thenReturn(true); + when(mockDraftOfferProposal.provider).thenReturn(providerInfo); + when(mockDraftOfferProposal.properties).thenReturn(initialOfferProperties); + when(mockDraftOfferProposal.pricing).thenReturn({ + cpuSec: 0.4 / 3600, + envSec: 0.4 / 3600, + start: 0.4, + }); + + const initialProposal = instance(mockInitialOfferProposal); + const draftProposal = instance(mockDraftOfferProposal); + + const demandOfferEvent$ = new Subject(); + + when(mockMarketApiAdapter.collectMarketProposalEvents(_)).thenReturn(demandOfferEvent$); + + // When + const draftProposal$ = marketModule.collectDraftOfferProposals({ + demandSpecification: spec, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }); + + const draftListener = jest.fn(); + + // Control the test using a subscription + const testSub = draftProposal$.subscribe(draftListener); + + // We need this because the actual demand publishing is async, so the result of that publishing will be available + // on the next tick. Only then it makes sense to push the proposals into the test subjects. + setImmediate(() => { + // Emit the values on the subjects + demandOfferEvent$.next({ + type: "ProposalReceived", + proposal: initialProposal, + timestamp: new Date(), + }); + demandOfferEvent$.next({ + type: "ProposalReceived", + proposal: draftProposal, + timestamp: new Date(), + }); + }); + + await waitForCondition(() => draftListener.mock.calls.length > 0); + testSub.unsubscribe(); + + expect(draftListener).toHaveBeenCalledWith(draftProposal); + + verify(mockMarketApiAdapter.counterProposal(initialProposal, spec)).once(); + // Right now we don't expect counter draft proposals (advanced negotiations) + verify(mockMarketApiAdapter.counterProposal(draftProposal, spec)).never(); + }); + + test("should reduce proposals from the same provider", async () => { + jest.useRealTimers(); + + const spec = new DemandSpecification( + { + properties: [], + constraints: [], + }, + "erc20-holesky-tglm", + ); + + const providerInfo: ProviderInfo = { + id: "test-provider-id", + name: "test-provider-name", + walletAddress: "0xTestWallet", + }; + + const mockInitialOfferProposal = mock(OfferProposal); + when(mockInitialOfferProposal.isInitial()).thenReturn(true); + when(mockInitialOfferProposal.isValid()).thenReturn(true); + when(mockInitialOfferProposal.provider).thenReturn(providerInfo); + when(mockInitialOfferProposal.properties).thenReturn(initialOfferProperties); + when(mockInitialOfferProposal.pricing).thenReturn({ + cpuSec: 0.4 / 3600, + envSec: 0.4 / 3600, + start: 0.4, + }); + + const initialProposal = instance(mockInitialOfferProposal); + + const demandOfferEvent$ = new Subject(); + + when(mockMarketApiAdapter.collectMarketProposalEvents(_)).thenReturn(demandOfferEvent$); + + // When + const draftProposal$ = marketModule.collectDraftOfferProposals({ + demandSpecification: spec, + proposalsBatchReleaseTimeoutMs: 1, + minProposalsBatchSize: 3, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }); + + // Drop 3 initial proposals about the same thing + setImmediate(() => { + demandOfferEvent$.next({ + type: "ProposalReceived", + proposal: initialProposal, + timestamp: new Date(), + }); + demandOfferEvent$.next({ + type: "ProposalReceived", + proposal: initialProposal, + timestamp: new Date(), + }); + demandOfferEvent$.next({ + type: "ProposalReceived", + proposal: initialProposal, + timestamp: new Date(), + }); + }); + + const testSub = draftProposal$.subscribe(); + + await waitAndCall(() => testSub.unsubscribe(), 0.2); + + verify(mockMarketApiAdapter.counterProposal(initialProposal, spec)).once(); + }); + }); + + describe("signAgreementFromPool()", () => { + beforeEach(() => { + jest.useRealTimers(); + }); + it("should keep acquiring proposals until one is successfully signed", async () => { + const badProposal0 = {} as OfferProposal; + const badProposal1 = {} as OfferProposal; + const goodProposal = {} as OfferProposal; + const mockPool = mock(DraftOfferProposalPool); + when(mockPool.acquire(_)).thenResolve(badProposal0).thenResolve(badProposal1).thenResolve(goodProposal); + when(mockPool.remove(_)).thenResolve(); + const goodAgreement = {} as Agreement; + const marketSpy = spy(marketModule); + when(marketSpy.proposeAgreement(goodProposal, _)).thenResolve(goodAgreement); + when(marketSpy.proposeAgreement(badProposal0, _)).thenReject(new Error("Failed to sign proposal")); + when(marketSpy.proposeAgreement(badProposal1, _)).thenReject(new Error("Failed to sign proposal")); + + const signedProposal = await marketModule.signAgreementFromPool(instance(mockPool)); + + verify(mockPool.acquire(_)).thrice(); + verify(marketSpy.proposeAgreement(badProposal0, _)).once(); + verify(mockPool.remove(badProposal0)).once(); + verify(marketSpy.proposeAgreement(badProposal1, _)).once(); + verify(mockPool.remove(badProposal1)).once(); + verify(marketSpy.proposeAgreement(goodProposal, _)).once(); + verify(mockPool.remove(goodProposal)).once(); + expect(signedProposal).toBe(goodAgreement); + }); + it("should release the proposal if the operation is cancelled between acquiring and signing", async () => { + const ac = new AbortController(); + const error = new Error("Operation cancelled"); + const proposal = {} as OfferProposal; + const mockPool = mock(DraftOfferProposalPool); + when(mockPool.acquire(_)).thenCall(async () => { + ac.abort(error); + return proposal; + }); + const marketSpy = spy(marketModule); + + await expect(marketModule.signAgreementFromPool(instance(mockPool), {}, ac.signal)).rejects.toMatchError( + new GolemAbortError("The signing of the agreement has been aborted", error), + ); + + verify(mockPool.acquire(_)).once(); + verify(mockPool.release(proposal)).once(); + verify(mockPool.remove(_)).never(); + verify(marketSpy.proposeAgreement(_)).never(); + }); + it("should abort immediately if the given signal is already aborted", async () => { + const mockPool = mock(DraftOfferProposalPool); + const signal = AbortSignal.abort(); + await expect(marketModule.signAgreementFromPool(instance(mockPool), {}, signal)).rejects.toThrow( + "The signing of the agreement has been aborted", + ); + verify(mockPool.acquire()).never(); + }); + it("should abort after a set timeout", async () => { + const mockPool = mock(DraftOfferProposalPool); + when(mockPool.acquire()).thenResolve({} as OfferProposal); + when(mockPool.remove(_)).thenResolve(); + const marketSpy = spy(marketModule); + when(marketSpy.proposeAgreement(_)).thenReject(new Error("Failed to sign proposal")); + + await expect(marketModule.signAgreementFromPool(instance(mockPool), {}, 50)).rejects.toThrow( + "Could not sign any agreement in time", + ); + }); + it("respects the timeout on draft proposal pool acquire and forwards the error", async () => { + const mockAcquire: DraftOfferProposalPool["acquire"] = jest + .fn() + .mockImplementation( + () => new Promise((_, reject) => setTimeout(() => reject(new Error("Failed to acquire")), 10)), + ); + const mockPool = { + acquire: mockAcquire, + } as DraftOfferProposalPool; + expect(marketModule.signAgreementFromPool(mockPool)).rejects.toThrow("Failed to acquire"); + }); + }); + + describe("emitted events", () => { + describe("agreement related events", () => { + test("Emits 'agreementConfirmed'", () => { + // Given + const agreement = instance(mockAgreement); + + const listener = jest.fn(); + marketModule.events.on("agreementApproved", listener); + + // When + testAgreementEvent$.next({ + type: "AgreementApproved", + agreement, + timestamp: new Date(), + }); + + // Then + expect(listener).toHaveBeenCalled(); + }); + + test("Emits 'agreementTerminated'", () => { + // Given + const agreement = instance(mockAgreement); + + const listener = jest.fn(); + marketModule.events.on("agreementTerminated", listener); + + // When + testAgreementEvent$.next({ + type: "AgreementTerminated", + agreement, + terminatedBy: "Provider", + reason: "Because I can", + timestamp: new Date(), + }); + + // Then + expect(listener).toHaveBeenCalled(); + }); + + test("Emits 'agreementRejected'", () => { + // Given + const agreement = instance(mockAgreement); + + const listener = jest.fn(); + marketModule.events.on("agreementRejected", listener); + + // When + testAgreementEvent$.next({ + type: "AgreementRejected", + agreement, + reason: "I didn't like it", + timestamp: new Date(), + }); + + // Then + expect(listener).toHaveBeenCalled(); + }); + + test("Emits 'agreementCancelled'", () => { + // Given + const agreement = instance(mockAgreement); + + const listener = jest.fn(); + marketModule.events.on("agreementCancelled", listener); + + // When + testAgreementEvent$.next({ + type: "AgreementCancelled", + agreement, + timestamp: new Date(), + }); + + // Then + expect(listener).toHaveBeenCalled(); + }); + }); + }); + describe("estimateBudget()", () => { + it("estimates budget for max number of agreeements", () => { + const order: MarketOrderSpec = { + demand: { + workload: { + imageTag: "image", + minCpuThreads: 5, + }, + }, + market: { + rentHours: 5, + pricing: { + model: "linear", + maxStartPrice: 1, + maxEnvPerHourPrice: 2, + maxCpuPerHourPrice: 0.5, + }, + }, + }; + const maxAgreements = 10; + const cpuPrice = 0.5 * 5 * 5; // 5 threads for 0.5 per hour for 5 hours + const envPrice = 2 * 5; // 2 per hour for 5 hours + const totalPricePerMachine = 1 + cpuPrice + envPrice; + const expectedBudget = totalPricePerMachine * maxAgreements; + + const budget = marketModule.estimateBudget({ order, maxAgreements }); + expect(budget).toBeCloseTo(expectedBudget, 5); + }); + it("estimates budget for non-linear pricing model", () => { + const order: MarketOrderSpec = { + demand: { + workload: { + imageTag: "image", + }, + }, + market: { + rentHours: 5, + pricing: { + model: "burn-rate", + avgGlmPerHour: 2, + }, + }, + }; + const maxAgreements = 3; + const expectedBudget = 5 * 2 * maxAgreements; + + const budget = marketModule.estimateBudget({ order, maxAgreements }); + expect(budget).toBeCloseTo(expectedBudget, 5); + }); + }); +}); diff --git a/src/market/market.module.ts b/src/market/market.module.ts new file mode 100644 index 000000000..87eed3708 --- /dev/null +++ b/src/market/market.module.ts @@ -0,0 +1,736 @@ +import { EventEmitter } from "eventemitter3"; +import { Agreement } from "./agreement"; +import { + Demand, + DraftOfferProposalPool, + GolemMarketError, + IMarketApi, + MarketErrorCode, + MarketEvents, + MarketProposalEvent, + OfferProposalSelector, +} from "./index"; +import { + createAbortSignalFromTimeout, + defaultLogger, + Logger, + runOnNextEventLoopIteration, + YagnaApi, +} from "../shared/utils"; +import { Allocation, IPaymentApi } from "../payment"; +import { filter, map, Observable, OperatorFunction, switchMap, tap } from "rxjs"; +import { + OfferCounterProposal, + OfferProposal, + OfferProposalFilter, + OfferProposalReceivedEvent, + ProposalsBatch, +} from "./proposal"; +import { DemandBodyBuilder, DemandSpecification, OrderDemandOptions } from "./demand"; +import { IActivityApi, IFileServer } from "../activity"; +import { StorageProvider } from "../shared/storage"; +import { WorkloadDemandDirectorConfig } from "./demand/directors/workload-demand-director-config"; +import { BasicDemandDirector } from "./demand/directors/basic-demand-director"; +import { PaymentDemandDirector } from "./demand/directors/payment-demand-director"; +import { WorkloadDemandDirector } from "./demand/directors/workload-demand-director"; +import { WorkloadDemandDirectorConfigOptions } from "./demand/options"; +import { BasicDemandDirectorConfig } from "./demand/directors/basic-demand-director-config"; +import { PaymentDemandDirectorConfig } from "./demand/directors/payment-demand-director-config"; +import { GolemAbortError, GolemTimeoutError, GolemUserError } from "../shared/error/golem-error"; +import { MarketOrderSpec } from "../golem-network"; +import { INetworkApi, NetworkModule } from "../network"; +import { AgreementOptions } from "./agreement/agreement"; +import { ScanDirector, ScanOptions, ScanSpecification, ScannedOffer } from "./scan"; + +export type DemandEngine = "vm" | "vm-nvidia" | "wasmtime"; + +export type PricingOptions = + | { + model: "linear"; + maxStartPrice: number; + maxCpuPerHourPrice: number; + maxEnvPerHourPrice: number; + } + | { + model: "burn-rate"; + avgGlmPerHour: number; + }; + +export interface OrderMarketOptions { + /** How long you want to rent the resources in hours */ + rentHours: number; + + /** Pricing strategy that will be used to filter the offers from the market */ + pricing: PricingOptions; + + /** A user-defined filter function which will determine if the offer proposal is valid for use. */ + offerProposalFilter?: OfferProposalFilter; + + /** A user-defined function that will be used to pick the best fitting offer proposal from available ones */ + offerProposalSelector?: OfferProposalSelector; +} + +export interface MarketModuleOptions { + /** + * Number of seconds after which the demand will be un-subscribed and subscribed again to get fresh + * offers from the market + * + * @default 30 minutes + */ + demandRefreshIntervalSec: number; +} + +export interface MarketModule { + events: EventEmitter; + + /** + * Build a DemandSpecification based on the given options and allocation. + * You can obtain an allocation using the payment module. + * The method returns a DemandSpecification that can be used to publish the demand to the market, + * for example using the `publishDemand` method. + */ + buildDemandDetails( + demandOptions: OrderDemandOptions, + orderOptions: OrderMarketOptions, + allocation: Allocation, + ): Promise; + + /** + * Build a ScanSpecification that can be used to scan the market for offers. + * The difference between this method and `buildDemandDetails` is that this method does not require an + * allocation, doesn't inherit payment properties from `GolemNetwork` settings and doesn't provide any defaults. + * If you wish to set the payment platform, you need to specify it in the ScanOptions. + */ + buildScanSpecification(options: ScanOptions): ScanSpecification; + + /** + * Publishes the demand to the market and handles refreshing it when needed. + * Each time the demand is refreshed, a new demand is emitted by the observable. + * Keep in mind that since this method returns an observable, nothing will happen until you subscribe to it. + * Unsubscribing will remove the demand from the market. + */ + publishAndRefreshDemand(demandSpec: DemandSpecification): Observable; + + /** + * Return an observable that will emit values representing various events related to this demand + */ + collectMarketProposalEvents(demand: Demand): Observable; + + /** + * Subscribes to the proposals for the given demand. + * If an error occurs, the observable will emit an error and complete. + * Keep in mind that since this method returns an observable, nothing will happen until you subscribe to it. + * + * This method will just yield all the proposals that will be found for that demand without any additional logic. + * + * The {@link collectDraftOfferProposals} is a more specialized variant of offer collection, which includes negotiations + * and demand re-subscription logic + */ + collectAllOfferProposals(demand: Demand): Observable; + + /** + * Sends a counter-offer to the provider. Note that to get the provider's response to your + * counter you should listen to events returned by `collectDemandOfferEvents`. + * + * @returns The counter-proposal that the requestor made to the Provider + */ + negotiateProposal( + receivedProposal: OfferProposal, + counterDemandSpec: DemandSpecification, + ): Promise; + + /** + * Internally + * + * - ya-ts-client createAgreement + * - ya-ts-client approveAgreement + * - ya-ts-client "wait for approval" + * + * @param proposal + * + * @return Returns when the provider accepts the agreement, rejects otherwise. The resulting agreement is ready to create activities from. + */ + proposeAgreement(proposal: OfferProposal): Promise; + + /** + * @return The Agreement that has been terminated via Yagna + */ + terminateAgreement(agreement: Agreement, reason?: string): Promise; + + /** + * Acquire a proposal from the pool and sign an agreement with the provider. If signing the agreement fails, + * destroy the proposal and try again with another one. The method returns an agreement that's ready to be used. + * Optionally, you can provide a timeout in milliseconds or an AbortSignal that can be used to cancel the operation + * early. If the operation is cancelled, the method will throw an error. + * Note that this method will respect the acquire timeout set in the pool and will throw an error if no proposal + * is available within the specified time. + * + * @example + * ```ts + * const agreement = await marketModule.signAgreementFromPool(draftProposalPool, 10_000); // throws TimeoutError if the operation takes longer than 10 seconds + * ``` + * @example + * ```ts + * const signal = AbortSignal.timeout(10_000); + * const agreement = await marketModule.signAgreementFromPool(draftProposalPool, signal); // throws TimeoutError if the operation takes longer than 10 seconds + * ``` + * @param draftProposalPool - The pool of draft proposals to acquire from + * @param agreementOptions - options used to sign the agreement such as expiration or waitingForApprovalTimeout + * @param signalOrTimeout - The timeout in milliseconds or an AbortSignal that will be used to cancel the operation + */ + signAgreementFromPool( + draftProposalPool: DraftOfferProposalPool, + agreementOptions?: AgreementOptions, + signalOrTimeout?: number | AbortSignal, + ): Promise; + + /** + * Creates a demand for the given package and allocation and starts collecting, filtering and negotiating proposals. + * The method returns an observable that emits a batch of draft proposals every time the buffer is full. + * The method will automatically negotiate the proposals until they are moved to the `Draft` state. + * Keep in mind that since this method returns an observable, nothing will happen until you subscribe to it. + * Unsubscribing from the observable will stop the process and remove the demand from the market. + */ + collectDraftOfferProposals(options: { + demandSpecification: DemandSpecification; + pricing: PricingOptions; + filter?: OfferProposalFilter; + minProposalsBatchSize?: number; + proposalsBatchReleaseTimeoutMs?: number; + }): Observable; + + /** + * Estimate the budget for the given order and maximum numbers of agreemnets. + * Keep in mind that this is just an estimate and the actual cost may vary. + * The method returns the estimated budget in GLM. + * @param params + */ + estimateBudget({ maxAgreements, order }: { maxAgreements: number; order: MarketOrderSpec }): number; + + /** + * Fetch the most up-to-date agreement details from the yagna + */ + fetchAgreement(agreementId: string): Promise; + + /** + * Scan the market for offers that match the given demand specification. + */ + scan(scanSpecification: ScanSpecification): Observable; +} + +/** + * Represents a director that can instruct DemandDetailsBuilder + * + * Demand is a complex concept in Golem. Requestors can place arbitrary properties and constraints on such + * market entity. While the demand request on the Golem Protocol level is a flat list of properties (key, value) and constraints, + * from the Requestor side they form logical groups that make sense together. + * + * The idea behind Directors is that you can encapsulate this grouping knowledge along with validation logic etc to prepare + * all the final demand request body properties in a more controlled and organized manner. + */ +export interface IDemandDirector { + apply(builder: DemandBodyBuilder): Promise | void; +} + +export class MarketModuleImpl implements MarketModule { + public events = new EventEmitter(); + + private readonly logger = defaultLogger("market"); + private readonly marketApi: IMarketApi; + private fileServer: IFileServer; + private options: MarketModuleOptions; + + constructor( + private readonly deps: { + logger: Logger; + yagna: YagnaApi; + paymentApi: IPaymentApi; + activityApi: IActivityApi; + marketApi: IMarketApi; + networkApi: INetworkApi; + networkModule: NetworkModule; + fileServer: IFileServer; + storageProvider: StorageProvider; + }, + options?: Partial, + ) { + this.logger = deps.logger; + this.marketApi = deps.marketApi; + this.fileServer = deps.fileServer; + + this.options = { + ...{ demandRefreshIntervalSec: 30 * 60 }, + ...options, + }; + + this.collectAndEmitAgreementEvents(); + } + + async buildDemandDetails( + demandOptions: OrderDemandOptions, + orderOptions: OrderMarketOptions, + allocation: Allocation, + ): Promise { + const builder = new DemandBodyBuilder(); + + // Instruct the builder what's required + const basicConfig = new BasicDemandDirectorConfig({ + subnetTag: demandOptions.subnetTag, + }); + + const basicDirector = new BasicDemandDirector(basicConfig); + basicDirector.apply(builder); + + const workloadOptions = demandOptions.workload + ? await this.applyLocalGVMIServeSupport(demandOptions.workload) + : demandOptions.workload; + + const workloadConfig = new WorkloadDemandDirectorConfig({ + ...workloadOptions, + expirationSec: orderOptions.rentHours * 60 * 60, // hours to seconds + }); + const workloadDirector = new WorkloadDemandDirector(workloadConfig); + await workloadDirector.apply(builder); + + const paymentConfig = new PaymentDemandDirectorConfig(demandOptions.payment); + const paymentDirector = new PaymentDemandDirector(allocation, this.deps.marketApi, paymentConfig); + await paymentDirector.apply(builder); + + return new DemandSpecification(builder.getProduct(), allocation.paymentPlatform); + } + + buildScanSpecification(options: ScanOptions): ScanSpecification { + const builder = new DemandBodyBuilder(); + const director = new ScanDirector(options); + director.apply(builder); + return builder.getProduct(); + } + + /** + * Augments the user-provided options with additional logic + * + * Use Case: serve the GVMI from the requestor and avoid registry + */ + private async applyLocalGVMIServeSupport(options: Partial) { + if (options.imageUrl?.startsWith("file://")) { + const sourcePath = options.imageUrl?.replace("file://", ""); + + const publishInfo = this.fileServer.getPublishInfo(sourcePath) ?? (await this.fileServer.publishFile(sourcePath)); + const { fileUrl: imageUrl, fileHash: imageHash } = publishInfo; + + this.logger.debug("Applied local GVMI serve support", { + sourcePath, + publishInfo, + }); + + return { + ...options, + imageUrl, + imageHash, + }; + } + + return options; + } + + /** + * Publishes the specified demand and re-publishes it based on demandSpecification.expirationSec interval + */ + publishAndRefreshDemand(demandSpecification: DemandSpecification): Observable { + return new Observable((subscriber) => { + let currentDemand: Demand; + + const subscribeToOfferProposals = async () => { + try { + currentDemand = await this.deps.marketApi.publishDemandSpecification(demandSpecification); + subscriber.next(currentDemand); + this.events.emit("demandSubscriptionStarted", { + demand: currentDemand, + }); + this.logger.debug("Subscribing for proposals matched with the demand", { demand: currentDemand }); + return currentDemand; + } catch (err) { + const golemMarketError = new GolemMarketError( + `Could not publish demand on the market`, + MarketErrorCode.SubscriptionFailed, + err, + ); + + subscriber.error(golemMarketError); + } + }; + + const unsubscribeFromOfferProposals = async (demand: Demand) => { + try { + await this.deps.marketApi.unpublishDemand(demand); + this.logger.info("Unpublished demand", { demandId: demand.id }); + this.logger.debug("Unpublished demand", demand); + this.events.emit("demandSubscriptionStopped", { + demand, + }); + } catch (err) { + const golemMarketError = new GolemMarketError( + `Could not publish demand on the market`, + MarketErrorCode.SubscriptionFailed, + err, + ); + + subscriber.error(golemMarketError); + } + }; + + void subscribeToOfferProposals(); + + const interval = setInterval(() => { + Promise.all([unsubscribeFromOfferProposals(currentDemand), subscribeToOfferProposals()]) + .then(([, demand]) => { + if (demand) { + this.events.emit("demandSubscriptionRefreshed", { + demand, + }); + this.logger.info("Refreshed subscription for offer proposals with the new demand", { demand }); + } + }) + .catch((err) => { + this.logger.error("Error while re-publishing demand for offers", err); + subscriber.error(err); + }); + }, this.options.demandRefreshIntervalSec * 1000); + + return () => { + clearInterval(interval); + if (currentDemand) { + void unsubscribeFromOfferProposals(currentDemand); + } + }; + }); + } + + collectMarketProposalEvents(demand: Demand): Observable { + return this.deps.marketApi.collectMarketProposalEvents(demand).pipe( + tap((event) => this.logger.debug("Received demand offer event from yagna", { event })), + tap((event) => this.emitMarketProposalEvents(event)), + ); + } + + collectAllOfferProposals(demand: Demand): Observable { + return this.collectMarketProposalEvents(demand).pipe( + filter((event): event is OfferProposalReceivedEvent => event.type === "ProposalReceived"), + map((event: OfferProposalReceivedEvent) => event.proposal), + ); + } + + async negotiateProposal( + offerProposal: OfferProposal, + counterDemand: DemandSpecification, + ): Promise { + try { + const counterProposal = await this.deps.marketApi.counterProposal(offerProposal, counterDemand); + this.logger.debug("Counter proposal sent", counterProposal); + this.events.emit("offerCounterProposalSent", { + offerProposal, + counterProposal, + }); + return counterProposal; + } catch (error) { + this.events.emit("errorSendingCounterProposal", { + offerProposal, + error, + }); + throw error; + } + } + + async proposeAgreement(proposal: OfferProposal, options?: AgreementOptions): Promise { + const agreement = await this.marketApi.proposeAgreement(proposal, options); + + this.logger.info("Proposed and got approval for agreement", { + agreementId: agreement.id, + provider: agreement.provider, + }); + + return agreement; + } + + async terminateAgreement(agreement: Agreement, reason?: string): Promise { + await this.marketApi.terminateAgreement(agreement, reason); + + this.logger.info("Terminated agreement", { + agreementId: agreement.id, + provider: agreement.provider, + reason, + }); + + return agreement; + } + + collectDraftOfferProposals(options: { + demandSpecification: DemandSpecification; + pricing: PricingOptions; + filter?: OfferProposalFilter; + minProposalsBatchSize?: number; + proposalsBatchReleaseTimeoutMs?: number; + }): Observable { + return this.publishAndRefreshDemand(options.demandSpecification).pipe( + // For each fresh demand, start to watch the related market conversation events + switchMap((freshDemand) => this.collectMarketProposalEvents(freshDemand)), + // Select only events for proposal received + filter((event) => event.type === "ProposalReceived"), + // Convert event to proposal + map((event) => (event as OfferProposalReceivedEvent).proposal), + // We are interested only in Initial and Draft proposals, that are valid + filter((proposal) => (proposal.isInitial() || proposal.isDraft()) && proposal.isValid()), + // If they are accepted by the pricing criteria + filter((proposal) => this.filterProposalsByPricingOptions(options.pricing, proposal)), + // If they are accepted by the user filter + filter((proposal) => (options?.filter ? this.filterProposalsByUserFilter(options.filter, proposal) : true)), + // Batch initial proposals and deduplicate them by provider key, pass-though proposals in other states + this.reduceInitialProposalsByProviderKey({ + minProposalsBatchSize: options?.minProposalsBatchSize, + proposalsBatchReleaseTimeoutMs: options?.proposalsBatchReleaseTimeoutMs, + }), + // Tap-in negotiator logic and negotiate initial proposals + tap((proposal) => { + if (proposal.isInitial()) { + this.negotiateProposal(proposal, options.demandSpecification).catch((err) => + this.logger.error("Failed to negotiate the proposal", err), + ); + } + }), + // Continue only with drafts + filter((proposal) => proposal.isDraft()), + ); + } + + private emitMarketProposalEvents(event: MarketProposalEvent) { + const { type } = event; + switch (type) { + case "ProposalReceived": + this.events.emit("offerProposalReceived", { + offerProposal: event.proposal, + }); + break; + case "ProposalRejected": + this.events.emit("offerCounterProposalRejected", { + counterProposal: event.counterProposal, + reason: event.reason, + }); + break; + case "PropertyQueryReceived": + this.events.emit("offerPropertyQueryReceived"); + break; + default: + this.logger.warn("Unsupported event type in event", { event }); + break; + } + } + + async signAgreementFromPool( + draftProposalPool: DraftOfferProposalPool, + agreementOptions?: AgreementOptions, + signalOrTimeout?: number | AbortSignal, + ): Promise { + this.logger.info("Trying to sign an agreement ..."); + const signal = createAbortSignalFromTimeout(signalOrTimeout); + + const getProposal = async () => { + try { + signal.throwIfAborted(); + const proposal = await draftProposalPool.acquire(signal); + if (signal.aborted) { + await draftProposalPool.release(proposal); + signal.throwIfAborted(); + } + return proposal; + } catch (error) { + if (signal.aborted) { + throw signal.reason.name === "TimeoutError" + ? new GolemTimeoutError("Could not sign any agreement in time") + : new GolemAbortError("The signing of the agreement has been aborted", error); + } + throw error; + } + }; + + const tryProposing = async (): Promise => { + const proposal = await getProposal(); + try { + const agreement = await this.proposeAgreement(proposal, agreementOptions); + // agreement is valid, proposal can be destroyed + await draftProposalPool.remove(proposal).catch((error) => { + this.logger.warn("Signed the agreement but failed to remove the proposal from the pool", { error }); + }); + return agreement; + } catch (error) { + this.logger.debug("Failed to propose agreement, retrying", { error }); + // We failed to propose the agreement, destroy the proposal and try again with another one + await draftProposalPool.remove(proposal).catch((error) => { + this.logger.warn("Failed to remove the proposal from the pool after unsuccessful agreement proposal", { + error, + }); + }); + return runOnNextEventLoopIteration(tryProposing); + } + }; + return tryProposing(); + } + + /** + * Reduce initial proposals to a set grouped by the provider's key to avoid duplicate offers + */ + private reduceInitialProposalsByProviderKey(options?: { + minProposalsBatchSize?: number; + proposalsBatchReleaseTimeoutMs?: number; + }): OperatorFunction { + return (input) => + new Observable((observer) => { + let isCancelled = false; + const proposalsBatch = new ProposalsBatch({ + minBatchSize: options?.minProposalsBatchSize, + releaseTimeoutMs: options?.proposalsBatchReleaseTimeoutMs, + }); + const subscription = input.subscribe((proposal) => { + if (proposal.isInitial()) { + proposalsBatch + .addProposal(proposal) + .catch((err) => this.logger.error("Failed to add the initial proposal to the batch", err)); + } else { + observer.next(proposal); + } + }); + + const batch = async () => { + if (isCancelled) { + return; + } + this.logger.debug("Waiting for reduced proposals..."); + try { + await proposalsBatch.waitForProposals(); + const proposals = await proposalsBatch.getProposals(); + if (proposals.length > 0) { + this.logger.debug("Received batch of proposals", { count: proposals.length }); + proposals.forEach((proposal) => observer.next(proposal)); + } + } catch (error) { + observer.error(error); + } + batch(); + }; + batch(); + + return () => { + isCancelled = true; + subscription.unsubscribe(); + }; + }); + } + + estimateBudget({ order, maxAgreements }: { order: MarketOrderSpec; maxAgreements: number }): number { + const pricingModel = order.market.pricing.model; + + // TODO: Don't assume for the user, at least not on pure golem-js level + const minCpuThreads = order.demand.workload?.minCpuThreads ?? 1; + + const { rentHours } = order.market; + + switch (pricingModel) { + case "linear": { + const { maxCpuPerHourPrice, maxStartPrice, maxEnvPerHourPrice } = order.market.pricing; + + const threadCost = maxAgreements * rentHours * minCpuThreads * maxCpuPerHourPrice; + const startCost = maxAgreements * maxStartPrice; + const envCost = maxAgreements * rentHours * maxEnvPerHourPrice; + + return startCost + envCost + threadCost; + } + case "burn-rate": + return maxAgreements * rentHours * order.market.pricing.avgGlmPerHour; + default: + throw new GolemUserError(`Unsupported pricing model ${pricingModel}`); + } + } + + async fetchAgreement(agreementId: string): Promise { + return this.marketApi.getAgreement(agreementId); + } + + /** + * Subscribes to an observable that maps yagna events into our domain events + * and emits these domain events via EventEmitter + */ + private collectAndEmitAgreementEvents() { + this.marketApi.collectAgreementEvents().subscribe((event) => { + switch (event.type) { + case "AgreementApproved": + this.events.emit("agreementApproved", { + agreement: event.agreement, + }); + break; + case "AgreementCancelled": + this.events.emit("agreementCancelled", { + agreement: event.agreement, + }); + break; + case "AgreementTerminated": + this.events.emit("agreementTerminated", { + agreement: event.agreement, + reason: event.reason, + terminatedBy: event.terminatedBy, + }); + break; + case "AgreementRejected": + this.events.emit("agreementRejected", { + agreement: event.agreement, + reason: event.reason, + }); + break; + } + }); + } + + private filterProposalsByUserFilter(filter: OfferProposalFilter, proposal: OfferProposal) { + try { + const result = filter(proposal); + + if (!result) { + this.events.emit("offerProposalRejectedByProposalFilter", { + offerProposal: proposal, + }); + this.logger.debug("The offer was rejected by the user filter", { id: proposal.id }); + } + + return result; + } catch (err) { + this.logger.error("Executing user provided proposal filter resulted with an error", err); + throw err; + } + } + + private filterProposalsByPricingOptions(pricing: PricingOptions, proposal: OfferProposal) { + let isPriceValid = true; + if (pricing.model === "linear") { + isPriceValid = + proposal.pricing.cpuSec <= pricing.maxCpuPerHourPrice / 3600 && + proposal.pricing.envSec <= pricing.maxEnvPerHourPrice / 3600 && + proposal.pricing.start <= pricing.maxStartPrice; + } else if (pricing.model === "burn-rate") { + isPriceValid = + proposal.pricing.start + proposal.pricing.envSec * 3600 + proposal.pricing.cpuSec * 3600 <= + pricing.avgGlmPerHour; + } + if (!isPriceValid) { + this.events.emit("offerProposalRejectedByPriceFilter", { + offerProposal: proposal, + }); + this.logger.debug("The offer was ignored because the price was too high", { + id: proposal.id, + pricing: proposal.pricing, + }); + } + return isPriceValid; + } + + scan(scanSpecification: ScanSpecification): Observable { + return this.deps.marketApi.scan(scanSpecification); + } +} diff --git a/src/market/proposal.ts b/src/market/proposal.ts deleted file mode 100644 index 9f8cada33..000000000 --- a/src/market/proposal.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { Proposal as ProposalModel, ProposalAllOfStateEnum } from "ya-ts-client/dist/ya-market/src/models"; -import { RequestorApi } from "ya-ts-client/dist/ya-market/api"; -import { Events } from "../events"; -import { GolemMarketError, MarketErrorCode } from "./error"; -import { ProviderInfo } from "../agreement"; -import { Demand } from "./demand"; - -export type PricingInfo = { - cpuSec: number; - envSec: number; - start: number; -}; - -// eslint-disable-next-line @typescript-eslint/naming-convention -export type ProposalProperties = Record & { - "golem.activity.caps.transfer.protocol": string[]; - "golem.com.payment.debit-notes.accept-timeout?": number; - "golem.com.payment.platform.erc20-polygon-glm.address"?: string; - "golem.com.payment.platform.erc20-holesky-tglm.address"?: string; - "golem.com.payment.platform.erc20-mumbai-tglm.address"?: string; - "golem.com.pricing.model": "linear"; - "golem.com.pricing.model.linear.coeffs": number[]; - "golem.com.scheme": string; - "golem.com.scheme.payu.debit-note.interval-sec?"?: number; - "golem.com.scheme.payu.payment-timeout-sec?"?: number; - "golem.com.usage.vector": string[]; - "golem.inf.cpu.architecture": string; - "golem.inf.cpu.brand": string; - "golem.inf.cpu.capabilities": string[]; - "golem.inf.cpu.cores": number; - "golem.inf.cpu.model": string; - "golem.inf.cpu.threads": number; - "golem.inf.cpu.vendor": string[]; - "golem.inf.mem.gib": number; - "golem.inf.storage.gib": number; - "golem.node.debug.subnet": string; - "golem.node.id.name": string; - "golem.node.net.is-public": boolean; - "golem.runtime.capabilities": string[]; - "golem.runtime.name": string; - "golem.runtime.version": string; - "golem.srv.caps.multi-activity": boolean; - "golem.srv.caps.payload-manifest": boolean; -}; - -export interface ProposalDetails { - transferProtocol: string[]; - cpuBrand: string; - cpuCapabilities: string[]; - cpuCores: number; - cpuThreads: number; - memory: number; - storage: number; - publicNet: boolean; - runtimeCapabilities: string[]; - runtimeName: string; - state: ProposalAllOfStateEnum; -} - -/** - * Proposal module - an object representing an offer in the state of a proposal from the provider. - */ -export class Proposal { - id: string; - readonly issuerId: string; - readonly provider: ProviderInfo; - readonly properties: ProposalProperties; - readonly constraints: string; - readonly timestamp: string; - counteringProposalId: string | null; - private readonly state: ProposalAllOfStateEnum; - private readonly prevProposalId: string | undefined; - - /** - * Create proposal for given subscription ID - * - * @param demand - * @param parentId - * @param setCounteringProposalReference - * @param api - * @param model - * @param eventTarget - */ - constructor( - public readonly demand: Demand, - private readonly parentId: string | null, - private readonly setCounteringProposalReference: (id: string, parentId: string) => void | null, - private readonly api: RequestorApi, - model: ProposalModel, - private eventTarget?: EventTarget, - ) { - this.id = model.proposalId; - this.issuerId = model.issuerId; - this.properties = model.properties as ProposalProperties; - this.constraints = model.constraints; - this.state = model.state; - this.prevProposalId = model.prevProposalId; - this.timestamp = model.timestamp; - this.counteringProposalId = null; - this.provider = this.getProviderInfo(); - - // Run validation to ensure that the Proposal is in a complete and correct state - this.validate(); - } - - get details(): ProposalDetails { - return { - transferProtocol: this.properties["golem.activity.caps.transfer.protocol"], - cpuBrand: this.properties["golem.inf.cpu.brand"], - cpuCapabilities: this.properties["golem.inf.cpu.capabilities"], - cpuCores: this.properties["golem.inf.cpu.cores"], - cpuThreads: this.properties["golem.inf.cpu.threads"], - memory: this.properties["golem.inf.mem.gib"], - storage: this.properties["golem.inf.storage.gib"], - publicNet: this.properties["golem.node.net.is-public"], - runtimeCapabilities: this.properties["golem.runtime.capabilities"], - runtimeName: this.properties["golem.runtime.name"], - state: this.state, - }; - } - - get pricing(): PricingInfo { - const usageVector = this.properties["golem.com.usage.vector"]; - const priceVector = this.properties["golem.com.pricing.model.linear.coeffs"]; - - const envIdx = usageVector.findIndex((ele) => ele === "golem.usage.duration_sec"); - const cpuIdx = usageVector.findIndex((ele) => ele === "golem.usage.cpu_sec"); - - const envSec = priceVector[envIdx] ?? 0.0; - const cpuSec = priceVector[cpuIdx] ?? 0.0; - const start = priceVector[priceVector.length - 1]; - - return { - cpuSec, - envSec, - start, - }; - } - - /** - * Validates if the proposal satisfies basic business rules, is complete and thus safe to interact with - * - * Use this method before executing any important logic, to ensure that you're working with correct, complete data - */ - protected validate(): void | never { - const usageVector = this.properties["golem.com.usage.vector"]; - const priceVector = this.properties["golem.com.pricing.model.linear.coeffs"]; - - if (!usageVector || usageVector.length === 0) { - throw new GolemMarketError( - "Broken proposal: the `golem.com.usage.vector` does not contain price information", - MarketErrorCode.InvalidProposal, - this.demand, - ); - } - - if (!priceVector || priceVector.length === 0) { - throw new GolemMarketError( - "Broken proposal: the `golem.com.pricing.model.linear.coeffs` does not contain pricing information", - MarketErrorCode.InvalidProposal, - this.demand, - ); - } - - if (usageVector.length < priceVector.length - 1) { - throw new GolemMarketError( - "Broken proposal: the `golem.com.usage.vector` has less pricing information than `golem.com.pricing.model.linear.coeffs`", - MarketErrorCode.InvalidProposal, - this.demand, - ); - } - - if (priceVector.length < usageVector.length) { - throw new GolemMarketError( - "Broken proposal: the `golem.com.pricing.model.linear.coeffs` should contain 3 price values", - MarketErrorCode.InvalidProposal, - this.demand, - ); - } - } - - isInitial(): boolean { - return this.state === ProposalAllOfStateEnum.Initial; - } - - isDraft(): boolean { - return this.state === ProposalAllOfStateEnum.Draft; - } - - isExpired(): boolean { - return this.state === ProposalAllOfStateEnum.Expired; - } - - isRejected(): boolean { - return this.state === ProposalAllOfStateEnum.Rejected; - } - - async reject(reason = "no reason") { - try { - // eslint-disable-next-line @typescript-eslint/ban-types - await this.api.rejectProposalOffer(this.demand.id, this.id, { message: reason as {} }); - this.eventTarget?.dispatchEvent( - new Events.ProposalRejected({ - id: this.id, - provider: this.provider, - parentId: this.id, - reason, - }), - ); - } catch (error) { - throw new GolemMarketError( - `Failed to reject proposal. ${error?.response?.data?.message || error}`, - MarketErrorCode.ProposalRejectionFailed, - this.demand, - error, - ); - } - } - - async respond(chosenPlatform: string) { - try { - (this.demand.demandRequest.properties as ProposalProperties)["golem.com.payment.chosen-platform"] = - chosenPlatform; - const { data: counteringProposalId } = await this.api.counterProposalDemand( - this.demand.id, - this.id, - this.demand.demandRequest, - { timeout: 20000 }, - ); - if (this.setCounteringProposalReference) { - this.setCounteringProposalReference(this.id, counteringProposalId); - } - this.eventTarget?.dispatchEvent( - new Events.ProposalResponded({ - id: this.id, - provider: this.provider, - counteringProposalId: counteringProposalId, - }), - ); - return counteringProposalId; - } catch (error) { - const reason = error?.response?.data?.message || error.toString(); - this.eventTarget?.dispatchEvent( - new Events.ProposalFailed({ - id: this.id, - provider: this.provider, - parentId: this.id, - reason, - }), - ); - throw new GolemMarketError( - `Failed to respond proposal. ${reason}`, - MarketErrorCode.ProposalResponseFailed, - this.demand, - error, - ); - } - } - - hasPaymentPlatform(paymentPlatform: string): boolean { - return this.getProviderPaymentPlatforms().includes(paymentPlatform); - } - - /** - * Proposal cost estimation based on CPU, Env and startup costs - */ - getEstimatedCost(): number { - const threadsNo = this.properties["golem.inf.cpu.threads"] || 1; - return this.pricing.start + this.pricing.cpuSec * threadsNo + this.pricing.envSec; - } - - private getProviderPaymentPlatforms(): string[] { - return ( - Object.keys(this.properties) - .filter((prop) => prop.startsWith("golem.com.payment.platform.")) - .map((prop) => prop.split(".")[4]) || [] - ); - } - - private getProviderInfo(): ProviderInfo { - return { - id: this.issuerId, - name: this.properties["golem.node.id.name"], - walletAddress: this.properties[ - `golem.com.payment.platform.${this.demand.allocation.paymentPlatform}.address` - ] as string, - }; - } -} diff --git a/src/market/proposal/index.ts b/src/market/proposal/index.ts new file mode 100644 index 000000000..8e26abaa9 --- /dev/null +++ b/src/market/proposal/index.ts @@ -0,0 +1,6 @@ +export * from "./market-proposal-event"; +export * from "./market-proposal"; +export * from "./offer-proposal"; +export * from "./offer-counter-proposal"; +export * from "./proposals_batch"; +export * from "./proposal-properties"; diff --git a/src/market/proposal/market-proposal-event.ts b/src/market/proposal/market-proposal-event.ts new file mode 100644 index 000000000..e75f07a02 --- /dev/null +++ b/src/market/proposal/market-proposal-event.ts @@ -0,0 +1,25 @@ +import { OfferProposal } from "./offer-proposal"; +import { OfferCounterProposal } from "./offer-counter-proposal"; + +export type OfferProposalReceivedEvent = { + type: "ProposalReceived"; + proposal: OfferProposal; + timestamp: Date; +}; + +export type OfferCounterProposalRejectedEvent = { + type: "ProposalRejected"; + counterProposal: OfferCounterProposal; + reason: string; + timestamp: Date; +}; + +export type OfferPropertyQueryReceivedEvent = { + type: "PropertyQueryReceived"; + timestamp: Date; +}; + +export type MarketProposalEvent = + | OfferProposalReceivedEvent + | OfferCounterProposalRejectedEvent + | OfferPropertyQueryReceivedEvent; diff --git a/src/market/proposal/market-proposal.ts b/src/market/proposal/market-proposal.ts new file mode 100644 index 000000000..1632c7193 --- /dev/null +++ b/src/market/proposal/market-proposal.ts @@ -0,0 +1,71 @@ +import { ProposalProperties } from "./proposal-properties"; +import { MarketApi } from "ya-ts-client"; +import { ProposalState } from "./offer-proposal"; +import { Demand } from "../demand"; + +export interface IProposalRepository { + add(proposal: MarketProposal): MarketProposal; + + getById(id: string): MarketProposal | undefined; + + getByDemandAndId(demand: Demand, id: string): Promise; +} + +/** + * Base representation of a market proposal that can be issued either by the Provider (offer proposal) + * or Requestor (counter-proposal) + */ +export abstract class MarketProposal { + public readonly id: string; + /** + * Reference to the previous proposal in the "negotiation chain" + * + * If null, this means that was the initial offer that the negotiations started from + */ + public readonly previousProposalId: string | null = null; + + public abstract readonly issuer: "Provider" | "Requestor"; + + public readonly properties: ProposalProperties; + + protected constructor(protected readonly model: MarketApi.ProposalDTO) { + this.id = model.proposalId; + this.previousProposalId = model.prevProposalId ?? null; + this.properties = model.properties as ProposalProperties; + } + + public get state(): ProposalState { + return this.model.state; + } + + public get timestamp(): Date { + return new Date(Date.parse(this.model.timestamp)); + } + + isInitial(): boolean { + return this.model.state === "Initial"; + } + + isDraft(): boolean { + return this.model.state === "Draft"; + } + + isExpired(): boolean { + return this.model.state === "Expired"; + } + + isRejected(): boolean { + return this.model.state === "Rejected"; + } + + public isValid(): boolean { + try { + this.validate(); + return true; + } catch (err) { + return false; + } + } + + protected abstract validate(): void | never; +} diff --git a/src/market/proposal/offer-counter-proposal.ts b/src/market/proposal/offer-counter-proposal.ts new file mode 100644 index 000000000..c59ad1b48 --- /dev/null +++ b/src/market/proposal/offer-counter-proposal.ts @@ -0,0 +1,14 @@ +import { MarketProposal } from "./market-proposal"; +import { MarketApi } from "ya-ts-client"; + +export class OfferCounterProposal extends MarketProposal { + public readonly issuer = "Requestor"; + + constructor(model: MarketApi.ProposalDTO) { + super(model); + } + + protected validate(): void { + return; + } +} diff --git a/src/market/proposal/offer-proposal.ts b/src/market/proposal/offer-proposal.ts new file mode 100644 index 000000000..881043350 --- /dev/null +++ b/src/market/proposal/offer-proposal.ts @@ -0,0 +1,160 @@ +import { MarketApi } from "ya-ts-client"; +import { GolemMarketError, MarketErrorCode } from "../error"; +import { ProviderInfo } from "../agreement"; +import { Demand } from "../demand"; +import { GolemInternalError } from "../../shared/error/golem-error"; +import { MarketProposal } from "./market-proposal"; + +export type OfferProposalFilter = (proposal: OfferProposal) => boolean; + +export type PricingInfo = { + cpuSec: number; + envSec: number; + start: number; +}; + +export type ProposalState = "Initial" | "Draft" | "Rejected" | "Accepted" | "Expired"; + +export type ProposalDTO = Partial<{ + transferProtocol: string[]; + cpuBrand: string; + cpuCapabilities: string[]; + cpuCores: number; + cpuThreads: number; + memory: number; + storage: number; + publicNet: boolean; + runtimeCapabilities: string[]; + runtimeName: string; + state: ProposalState; +}>; + +/** + * Entity representing the offer presented by the Provider to the Requestor + * + * Issue: The final proposal that gets promoted to an agreement comes from the provider + * Right now the last time I can access it directly is when I receive the counter from the provider, + * later it's impossible for me to get it via the API `{"message":"Path deserialize error: Id [2cb0b2820c6142fab5af7a8e90da09f0] has invalid owner type."}` + * + * FIXME #yagna should allow obtaining proposals via the API even if I'm not the owner! + */ +export class OfferProposal extends MarketProposal { + public readonly issuer = "Provider"; + + constructor( + model: MarketApi.ProposalDTO, + public readonly demand: Demand, + ) { + super(model); + + this.validate(); + } + + get pricing(): PricingInfo { + const usageVector = this.properties["golem.com.usage.vector"]; + const priceVector = this.properties["golem.com.pricing.model.linear.coeffs"]; + + if (!usageVector) { + throw new GolemInternalError( + "The proposal does not contain 'golem.com.usage.vector' property. We can't estimate the costs.", + ); + } + + if (!priceVector) { + throw new GolemInternalError( + "The proposal does not contain 'golem.com.pricing.model.linear.coeffs' property. We can't estimate costs.", + ); + } + + const envIdx = usageVector.findIndex((ele) => ele === "golem.usage.duration_sec"); + const cpuIdx = usageVector.findIndex((ele) => ele === "golem.usage.cpu_sec"); + + const envSec = priceVector[envIdx] ?? 0.0; + const cpuSec = priceVector[cpuIdx] ?? 0.0; + const start = priceVector[priceVector.length - 1]; + + return { + cpuSec, + envSec, + start, + }; + } + + getDto(): ProposalDTO { + return { + transferProtocol: this.properties["golem.activity.caps.transfer.protocol"], + cpuBrand: this.properties["golem.inf.cpu.brand"], + cpuCapabilities: this.properties["golem.inf.cpu.capabilities"], + cpuCores: this.properties["golem.inf.cpu.cores"], + cpuThreads: this.properties["golem.inf.cpu.threads"], + memory: this.properties["golem.inf.mem.gib"], + storage: this.properties["golem.inf.storage.gib"], + publicNet: this.properties["golem.node.net.is-public"], + runtimeCapabilities: this.properties["golem.runtime.capabilities"], + runtimeName: this.properties["golem.runtime.name"], + state: this.state, + }; + } + + /** + * Proposal cost estimation based on CPU, Env and startup costs + */ + getEstimatedCost(): number { + const threadsNo = this.properties["golem.inf.cpu.threads"] || 1; + return this.pricing.start + this.pricing.cpuSec * threadsNo + this.pricing.envSec; + } + + public get provider(): ProviderInfo { + return { + id: this.model.issuerId, + name: this.properties["golem.node.id.name"], + walletAddress: this.properties[`golem.com.payment.platform.${this.demand.paymentPlatform}.address`] as string, + }; + } + + /** + * Validates if the proposal satisfies basic business rules, is complete and thus safe to interact with + * + * Use this method before executing any important logic, to ensure that you're working with correct, complete data + */ + protected validate(): void | never { + const usageVector = this.properties["golem.com.usage.vector"]; + const priceVector = this.properties["golem.com.pricing.model.linear.coeffs"]; + + if (!usageVector || usageVector.length === 0) { + throw new GolemMarketError( + "Broken proposal: the `golem.com.usage.vector` does not contain valid information about structure of the usage counters vector", + MarketErrorCode.InvalidProposal, + ); + } + + if (!priceVector || priceVector.length === 0) { + throw new GolemMarketError( + "Broken proposal: the `golem.com.pricing.model.linear.coeffs` does not contain pricing information", + MarketErrorCode.InvalidProposal, + ); + } + + if (usageVector.length < priceVector.length - 1) { + throw new GolemMarketError( + "Broken proposal: the `golem.com.usage.vector` has less pricing information than `golem.com.pricing.model.linear.coeffs`", + MarketErrorCode.InvalidProposal, + ); + } + + if (priceVector.length < usageVector.length) { + throw new GolemMarketError( + "Broken proposal: the `golem.com.pricing.model.linear.coeffs` should contain 3 price values", + MarketErrorCode.InvalidProposal, + ); + } + } + + private getProviderPaymentPlatforms(): string[] { + return ( + Object.keys(this.properties) + .filter((prop) => prop.startsWith("golem.com.payment.platform.")) + .map((prop) => prop.split(".")[4]) || [] + ); + } +} diff --git a/src/market/proposal/proposal-properties.ts b/src/market/proposal/proposal-properties.ts new file mode 100644 index 000000000..9503f9830 --- /dev/null +++ b/src/market/proposal/proposal-properties.ts @@ -0,0 +1,109 @@ +/** + * Describes the type representing properties from the perspective of Golem Market Protocol + * + * Golem Protocol defines "properties" as a flat list of key/value pairs. + * + * The protocol itself does not dictate what properties should or shouldn't be defined. Such details + * are left for the Provider and Requestor to agree upon outside the protocol. + * + * The mentioned agreements can be done in a P2P manner between the involved entities, or both parties + * can decide to adhere to a specific "standard" which determines which properties are "mandatory". + * + * Specific property definitions and requirements were provided in Golem _standards_ and _architecture proposals_. + * + * @link https://github.com/golemfactory/golem-architecture/tree/master/standards Golem standards + * @link https://github.com/golemfactory/golem-architecture/tree/master/gaps Golem Architecture Proposals + */ +export type GenericGolemProtocolPropertyType = Record; +/** + * Properties defined by GAP-3 + * + * @link https://github.com/golemfactory/golem-architecture/blob/master/gaps/gap-3_mid_agreement_payments/gap-3_mid_agreement_payments.md + */ +export type Gap3MidAgreementPaymentProps = { + "golem.com.scheme.payu.debit-note.interval-sec?"?: number; + "golem.com.scheme.payu.payment-timeout-sec?"?: number; +}; +/** + * @link https://github.com/golemfactory/golem-architecture/tree/master/standards/0-commons + */ +export type StandardCommonProps = { + "golem.activity.caps.transfer.protocol": ("http" | "https" | "gftp")[]; + "golem.inf.cpu.architecture": string; + "golem.inf.cpu.brand": string; + "golem.inf.cpu.capabilities": string[]; + "golem.inf.cpu.cores": number; + "golem.inf.cpu.model": string; + "golem.inf.cpu.threads": number; + "golem.inf.cpu.vendor": string; + "golem.inf.mem.gib": number; + "golem.inf.storage.gib": number; + "golem.runtime.capabilities": string[]; + "golem.runtime.name": string; + "golem.runtime.version": string; +}; + +/** + * https://github.com/golemfactory/golem-architecture/blob/master/standards/2-service/srv.md + */ +export type StandardNodeProps = { + "golem.node.id.name": string; + /** @deprecated Do not rely on this, it's mentioned in the standard, but not implemented FIXME #yagna */ + "golem.node.geo.country_code"?: string; +}; + +/** + * @link https://github.com/golemfactory/golem-architecture/blob/master/standards/3-commercial/com.md + */ +export type StandardCommercialProps = { + "golem.com.payment.debit-notes.accept-timeout?": number; + /** @example "erc20-polygon-glm" */ + "golem.com.payment.chosen-platform": string; + "golem.com.payment.platform.erc20-polygon-glm.address"?: string; + "golem.com.payment.platform.erc20-holesky-tglm.address"?: string; + "golem.com.payment.platform.erc20-mumbai-tglm.address"?: string; + "golem.com.payment.protocol.version": number; + /** @example payu */ + "golem.com.scheme": string; + /** @deprecated replaced by `golem.com.scheme.payu.debit-note.interval-sec?` in GAP-3 */ + "golem.com.scheme.payu.interval_sec"?: number; + "golem.com.pricing.model": "linear"; + "golem.com.pricing.model.linear.coeffs": number[]; + "golem.com.usage.vector": string[]; +}; + +/** + * @link https://github.com/golemfactory/golem-architecture/blob/master/standards/2-service/srv.md + */ +export type StandardServiceProps = { + "golem.srv.caps.multi-activity": boolean; +}; + +/** + * @link https://github.com/golemfactory/golem-architecture/blob/master/standards/2-service/srv/comp.md + */ +export type StandardComputationPlatformProps = { + "golem.srv.comp.expiration": number; + "golem.srv.comp.task_package": string; +}; + +export type ProposalProperties = + // Start from most generic definition of what a property is + GenericGolemProtocolPropertyType & + // Attach standard specific property sets + StandardCommonProps & + StandardNodeProps & + StandardCommercialProps & + StandardServiceProps & + StandardComputationPlatformProps & + // Attach GAP specific property sets + Gap3MidAgreementPaymentProps & + /** + * These are around byt not really specified in any standard + * FIXME #yagna - Standardize? + */ + Partial<{ + "golem.node.debug.subnet": string; + "golem.node.net.is-public": boolean; + "golem.srv.caps.payload-manifest": boolean; + }>; diff --git a/src/market/proposal.test.ts b/src/market/proposal/proposal.test.ts similarity index 81% rename from src/market/proposal.test.ts rename to src/market/proposal/proposal.test.ts index 2580ab789..5cc6fffa5 100644 --- a/src/market/proposal.test.ts +++ b/src/market/proposal/proposal.test.ts @@ -1,39 +1,37 @@ -import { Proposal as ProposalModel, ProposalAllOfStateEnum } from "ya-ts-client/dist/ya-market/src/models"; -import { Proposal, ProposalProperties } from "./proposal"; -import { RequestorApi } from "ya-ts-client/dist/ya-market/api"; -import { Demand } from "./demand"; -import { instance, mock, when } from "@johanblumenberg/ts-mockito"; -import { Allocation } from "../payment"; -import { GolemMarketError, MarketErrorCode } from "./error"; - -jest.mock("ya-ts-client/dist/ya-market/api"); +import { MarketApi } from "ya-ts-client"; +import { OfferProposal } from "./offer-proposal"; +import { Demand } from "../demand"; +import { instance, mock, reset, when } from "@johanblumenberg/ts-mockito"; +import { Allocation } from "../../payment"; +import { GolemMarketError, MarketErrorCode } from "../error"; +import { ProposalProperties } from "./proposal-properties"; const allocationMock = mock(Allocation); -when(allocationMock.paymentPlatform).thenReturn("test-payment-platform"); const demandMock = mock(Demand); -when(demandMock.allocation).thenReturn(instance(allocationMock)); -const testDemand = instance(demandMock); -const mockApi = new RequestorApi(); - -const mockCounteringProposalReference = jest.fn(); +const testDemand = instance(demandMock); -const buildTestProposal = (props: Partial): Proposal => { - const model: ProposalModel = { +const buildTestProposal = (props: Partial): OfferProposal => { + const model: MarketApi.ProposalDTO = { constraints: "", issuerId: "", proposalId: "", - state: ProposalAllOfStateEnum.Initial, + state: "Initial", timestamp: "", properties: props, }; - const proposal = new Proposal(testDemand, null, mockCounteringProposalReference, mockApi, model); - - return proposal; + return new OfferProposal(model, testDemand); }; describe("Proposal", () => { + beforeEach(() => { + reset(allocationMock); + reset(demandMock); + + when(allocationMock.paymentPlatform).thenReturn("test-payment-platform"); + }); + describe("Validation", () => { test("throws an error when linear pricing vector is missing", () => { expect(() => @@ -44,7 +42,6 @@ describe("Proposal", () => { new GolemMarketError( "Broken proposal: the `golem.com.pricing.model.linear.coeffs` does not contain pricing information", MarketErrorCode.InvalidProposal, - testDemand, ), ); }); @@ -59,7 +56,6 @@ describe("Proposal", () => { new GolemMarketError( "Broken proposal: the `golem.com.pricing.model.linear.coeffs` does not contain pricing information", MarketErrorCode.InvalidProposal, - testDemand, ), ); }); @@ -74,7 +70,6 @@ describe("Proposal", () => { new GolemMarketError( "Broken proposal: the `golem.com.pricing.model.linear.coeffs` should contain 3 price values", MarketErrorCode.InvalidProposal, - testDemand, ), ); }); @@ -87,9 +82,8 @@ describe("Proposal", () => { }), ).toThrow( new GolemMarketError( - "Broken proposal: the `golem.com.usage.vector` does not contain price information", + "Broken proposal: the `golem.com.usage.vector` does not contain valid information about structure of the usage counters vector", MarketErrorCode.InvalidProposal, - testDemand, ), ); }); @@ -101,9 +95,8 @@ describe("Proposal", () => { }), ).toThrow( new GolemMarketError( - "Broken proposal: the `golem.com.usage.vector` does not contain price information", + "Broken proposal: the `golem.com.usage.vector` does not contain valid information about structure of the usage counters vector", MarketErrorCode.InvalidProposal, - testDemand, ), ); }); @@ -118,7 +111,6 @@ describe("Proposal", () => { new GolemMarketError( "Broken proposal: the `golem.com.usage.vector` has less pricing information than `golem.com.pricing.model.linear.coeffs`", MarketErrorCode.InvalidProposal, - testDemand, ), ); }); diff --git a/src/market/proposals_batch.test.ts b/src/market/proposal/proposals_batch.test.ts similarity index 89% rename from src/market/proposals_batch.test.ts rename to src/market/proposal/proposals_batch.test.ts index 018d390db..3233c9e2c 100644 --- a/src/market/proposals_batch.test.ts +++ b/src/market/proposal/proposals_batch.test.ts @@ -1,7 +1,8 @@ import { ProposalsBatch } from "./proposals_batch"; import { mock, instance, when } from "@johanblumenberg/ts-mockito"; -import { Proposal, ProposalProperties } from "./proposal"; +import { OfferProposal } from "./offer-proposal"; import { ProviderInfo } from "../agreement"; +import { ProposalProperties } from "./proposal-properties"; const mockedProviderInfo: ProviderInfo = { id: "provider-id-1", @@ -13,7 +14,7 @@ describe("ProposalsBatch", () => { describe("Adding Proposals", () => { it("should add the proposal to the batch from new provider", async () => { const proposalsBatch = new ProposalsBatch({ minBatchSize: 1 }); - const mockedProposal = mock(Proposal); + const mockedProposal = mock(OfferProposal); when(mockedProposal.provider).thenReturn(mockedProviderInfo); when(mockedProposal.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -28,7 +29,7 @@ describe("ProposalsBatch", () => { }); it("should not add the proposal to the batch from the existing provider and the same hardware configuration", async () => { const proposalsBatch = new ProposalsBatch({ releaseTimeoutMs: 100 }); - const mockedProposal = mock(Proposal); + const mockedProposal = mock(OfferProposal); when(mockedProposal.provider).thenReturn(mockedProviderInfo); when(mockedProposal.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -46,7 +47,7 @@ describe("ProposalsBatch", () => { it("should add the proposal to the batch from the existing provider and different hardware configuration", async () => { const proposalsBatch = new ProposalsBatch({ minBatchSize: 2 }); - const mockedProposal1 = mock(Proposal); + const mockedProposal1 = mock(OfferProposal); when(mockedProposal1.provider).thenReturn(mockedProviderInfo); when(mockedProposal1.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -54,7 +55,7 @@ describe("ProposalsBatch", () => { ["golem.inf.mem.gib"]: 1, ["golem.inf.storage.gib"]: 1, } as ProposalProperties); - const mockedProposal2 = mock(Proposal); + const mockedProposal2 = mock(OfferProposal); when(mockedProposal2.provider).thenReturn(mockedProviderInfo); when(mockedProposal2.properties).thenReturn({ ["golem.inf.cpu.cores"]: 77, @@ -76,7 +77,7 @@ describe("ProposalsBatch", () => { describe("Reading Proposals", () => { it("should read the set of proposals grouped by provider key distinguished by provider id, cpu, threads, memory and storage", async () => { const proposalsBatch = new ProposalsBatch({ releaseTimeoutMs: 100 }); - const mockedProposal1 = mock(Proposal); + const mockedProposal1 = mock(OfferProposal); when(mockedProposal1.provider).thenReturn(mockedProviderInfo); when(mockedProposal1.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -84,7 +85,7 @@ describe("ProposalsBatch", () => { ["golem.inf.mem.gib"]: 1, ["golem.inf.storage.gib"]: 1, } as ProposalProperties); - const mockedProposal2 = mock(Proposal); + const mockedProposal2 = mock(OfferProposal); when(mockedProposal2.provider).thenReturn(mockedProviderInfo); when(mockedProposal2.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -92,7 +93,7 @@ describe("ProposalsBatch", () => { ["golem.inf.mem.gib"]: 1, ["golem.inf.storage.gib"]: 1, } as ProposalProperties); - const mockedProposal3 = mock(Proposal); + const mockedProposal3 = mock(OfferProposal); when(mockedProposal3.provider).thenReturn(mockedProviderInfo); when(mockedProposal3.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -100,7 +101,7 @@ describe("ProposalsBatch", () => { ["golem.inf.mem.gib"]: 1, ["golem.inf.storage.gib"]: 1, } as ProposalProperties); - const mockedProposal4 = mock(Proposal); + const mockedProposal4 = mock(OfferProposal); when(mockedProposal4.provider).thenReturn(mockedProviderInfo); when(mockedProposal4.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -108,7 +109,7 @@ describe("ProposalsBatch", () => { ["golem.inf.mem.gib"]: 77, ["golem.inf.storage.gib"]: 1, } as ProposalProperties); - const mockedProposal5 = mock(Proposal); + const mockedProposal5 = mock(OfferProposal); when(mockedProposal5.provider).thenReturn(mockedProviderInfo); when(mockedProposal5.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -116,7 +117,7 @@ describe("ProposalsBatch", () => { ["golem.inf.mem.gib"]: 1, ["golem.inf.storage.gib"]: 77, } as ProposalProperties); - const mockedProposal6 = mock(Proposal); + const mockedProposal6 = mock(OfferProposal); when(mockedProposal6.provider).thenReturn({ id: "provider-77" } as ProviderInfo); when(mockedProposal6.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -150,7 +151,7 @@ describe("ProposalsBatch", () => { }); it("should read the set of proposal grouped by provider key and reduced proposals from teh same provider to the lowest price and highest time", async () => { const proposalsBatch = new ProposalsBatch({ releaseTimeoutMs: 100 }); - const mockedProposal1 = mock(Proposal); + const mockedProposal1 = mock(OfferProposal); when(mockedProposal1.provider).thenReturn(mockedProviderInfo); when(mockedProposal1.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -163,8 +164,8 @@ describe("ProposalsBatch", () => { envSec: 1, start: 1, }); - when(mockedProposal1.timestamp).thenReturn("2024-01-01T00:00:00.000Z"); - const mockedProposal2 = mock(Proposal); + when(mockedProposal1.timestamp).thenReturn(new Date(Date.parse("2024-01-01T00:00:00.000Z"))); + const mockedProposal2 = mock(OfferProposal); when(mockedProposal2.provider).thenReturn(mockedProviderInfo); when(mockedProposal2.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -177,8 +178,8 @@ describe("ProposalsBatch", () => { envSec: 1, start: 1, }); - when(mockedProposal2.timestamp).thenReturn("2024-01-01T07:07:07.007Z"); - const mockedProposal3 = mock(Proposal); + when(mockedProposal2.timestamp).thenReturn(new Date(Date.parse("2024-01-01T07:07:07.007Z"))); + const mockedProposal3 = mock(OfferProposal); when(mockedProposal3.provider).thenReturn(mockedProviderInfo); when(mockedProposal3.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, @@ -191,7 +192,7 @@ describe("ProposalsBatch", () => { envSec: 2, start: 2, }); - when(mockedProposal3.timestamp).thenReturn("2024-01-01T07:07:07.007Z"); + when(mockedProposal3.timestamp).thenReturn(new Date(Date.parse("2024-01-01T07:07:07.007Z"))); const proposal1 = instance(mockedProposal1); const proposal2 = instance(mockedProposal2); const proposal3 = instance(mockedProposal3); @@ -208,7 +209,7 @@ describe("ProposalsBatch", () => { }); it("should drain batch after reading proposals", async () => { const proposalsBatch = new ProposalsBatch({ releaseTimeoutMs: 100 }); - const mockedProposal = mock(Proposal); + const mockedProposal = mock(OfferProposal); when(mockedProposal.provider).thenReturn(mockedProviderInfo); when(mockedProposal.properties).thenReturn({ ["golem.inf.cpu.cores"]: 1, diff --git a/src/market/proposals_batch.ts b/src/market/proposal/proposals_batch.ts similarity index 86% rename from src/market/proposals_batch.ts rename to src/market/proposal/proposals_batch.ts index 950340628..2d8baff31 100644 --- a/src/market/proposals_batch.ts +++ b/src/market/proposal/proposals_batch.ts @@ -1,4 +1,4 @@ -import { Proposal } from "./proposal"; +import { OfferProposal } from "./offer-proposal"; import AsyncLock from "async-lock"; export type ProposalsBatchOptions = { @@ -19,7 +19,7 @@ const DEFAULTS = { */ export class ProposalsBatch { /** Batch of proposals mapped by provider key and related set of initial proposals */ - private batch = new Map>(); + private batch = new Map>(); /** Lock used to synchronize adding and getting proposals from the batch */ private lock: AsyncLock = new AsyncLock(); private config: Required; @@ -35,12 +35,12 @@ export class ProposalsBatch { * Add proposal to the batch grouped by provider key * which consist of providerId, cores, threads, mem and storage */ - async addProposal(proposal: Proposal) { + async addProposal(proposal: OfferProposal) { const providerKey = this.getProviderKey(proposal); await this.lock.acquire("proposals-batch", () => { let proposals = this.batch.get(providerKey); if (!proposals) { - proposals = new Set(); + proposals = new Set(); this.batch.set(providerKey, proposals); } proposals.add(proposal); @@ -51,7 +51,7 @@ export class ProposalsBatch { * Returns the batched proposals from the internal buffer and empties it */ public async getProposals() { - const proposals: Proposal[] = []; + const proposals: OfferProposal[] = []; await this.lock.acquire("proposals-batch", () => { this.batch.forEach((providersProposals) => proposals.push(this.getBestProposal(providersProposals))); @@ -90,12 +90,12 @@ export class ProposalsBatch { /** * Selects the best proposal from the set according to the lowest price and the youngest proposal age */ - private getBestProposal(proposals: Set): Proposal { - const sortByLowerPriceAndHigherTime = (p1: Proposal, p2: Proposal) => { + private getBestProposal(proposals: Set): OfferProposal { + const sortByLowerPriceAndHigherTime = (p1: OfferProposal, p2: OfferProposal) => { const p1Price = p1.getEstimatedCost(); const p2Price = p2.getEstimatedCost(); - const p1Time = new Date(p1.timestamp).valueOf(); - const p2Time = new Date(p2.timestamp).valueOf(); + const p1Time = p1.timestamp.valueOf(); + const p2Time = p2.timestamp.valueOf(); return p1Price !== p2Price ? p1Price - p2Price : p2Time - p1Time; }; return [...proposals].sort(sortByLowerPriceAndHigherTime)[0]; @@ -104,7 +104,7 @@ export class ProposalsBatch { /** * Provider key used to group proposals so that they can be distinguished based on ID and hardware configuration */ - private getProviderKey(proposal: Proposal): string { + private getProviderKey(proposal: OfferProposal): string { return [ proposal.provider.id, proposal.properties["golem.inf.cpu.cores"], diff --git a/src/market/scan/index.ts b/src/market/scan/index.ts new file mode 100644 index 000000000..a6e78b177 --- /dev/null +++ b/src/market/scan/index.ts @@ -0,0 +1,3 @@ +export * from "./types"; +export * from "./scan-director"; +export * from "./scanned-proposal"; diff --git a/src/market/scan/scan-director.ts b/src/market/scan/scan-director.ts new file mode 100644 index 000000000..56b5dcd89 --- /dev/null +++ b/src/market/scan/scan-director.ts @@ -0,0 +1,59 @@ +import { ComparisonOperator, DemandBodyBuilder } from "../demand"; +import { ScanOptions } from "./types"; + +export class ScanDirector { + constructor(private options: ScanOptions) {} + + public async apply(builder: DemandBodyBuilder) { + this.addWorkloadDecorations(builder); + this.addGenericDecorations(builder); + this.addPaymentDecorations(builder); + } + + private addPaymentDecorations(builder: DemandBodyBuilder): void { + if (!this.options.payment) return; + const network = this.options.payment.network; + const driver = this.options.payment.driver || "erc20"; + const token = this.options.payment.token || ["mainnet", "polygon"].includes(network) ? "glm" : "tglm"; + builder.addConstraint(`golem.com.payment.platform.${driver}-${network}-${token}.address`, "*"); + } + + private addWorkloadDecorations(builder: DemandBodyBuilder): void { + if (this.options.workload?.engine) { + builder.addConstraint("golem.runtime.name", this.options.workload?.engine); + } + if (this.options.workload?.capabilities) + this.options.workload?.capabilities.forEach((cap) => builder.addConstraint("golem.runtime.capabilities", cap)); + + if (this.options.workload?.minMemGib) { + builder.addConstraint("golem.inf.mem.gib", this.options.workload?.minMemGib, ComparisonOperator.GtEq); + } + if (this.options.workload?.maxMemGib) { + builder.addConstraint("golem.inf.mem.gib", this.options.workload?.maxMemGib, ComparisonOperator.LtEq); + } + if (this.options.workload?.minStorageGib) { + builder.addConstraint("golem.inf.storage.gib", this.options.workload?.minStorageGib, ComparisonOperator.GtEq); + } + if (this.options.workload?.maxStorageGib) { + builder.addConstraint("golem.inf.storage.gib", this.options.workload?.maxStorageGib, ComparisonOperator.LtEq); + } + if (this.options.workload?.minCpuThreads) { + builder.addConstraint("golem.inf.cpu.threads", this.options.workload?.minCpuThreads, ComparisonOperator.GtEq); + } + if (this.options.workload?.maxCpuThreads) { + builder.addConstraint("golem.inf.cpu.threads", this.options.workload?.maxCpuThreads, ComparisonOperator.LtEq); + } + if (this.options.workload?.minCpuCores) { + builder.addConstraint("golem.inf.cpu.cores", this.options.workload?.minCpuCores, ComparisonOperator.GtEq); + } + if (this.options.workload?.maxCpuCores) { + builder.addConstraint("golem.inf.cpu.cores", this.options.workload?.maxCpuCores, ComparisonOperator.LtEq); + } + } + + private addGenericDecorations(builder: DemandBodyBuilder): void { + if (this.options.subnetTag) { + builder.addConstraint("golem.node.debug.subnet", this.options.subnetTag); + } + } +} diff --git a/src/market/scan/scanned-proposal.ts b/src/market/scan/scanned-proposal.ts new file mode 100644 index 000000000..c0797b28c --- /dev/null +++ b/src/market/scan/scanned-proposal.ts @@ -0,0 +1,84 @@ +import { PricingInfo, ProposalProperties } from "../proposal"; +import { GolemInternalError } from "../../shared/error/golem-error"; +import type { MarketApi } from "ya-ts-client"; + +type ScannedOfferDTO = MarketApi.OfferDTO; + +export class ScannedOffer { + constructor(private readonly model: ScannedOfferDTO) {} + + get properties(): ProposalProperties { + return this.model.properties as ProposalProperties; + } + + get constraints(): string { + return this.model.constraints; + } + + get pricing(): PricingInfo { + const usageVector = this.properties["golem.com.usage.vector"]; + const priceVector = this.properties["golem.com.pricing.model.linear.coeffs"]; + + if (!usageVector) { + throw new GolemInternalError( + "The proposal does not contain 'golem.com.usage.vector' property. We can't estimate the costs.", + ); + } + + if (!priceVector) { + throw new GolemInternalError( + "The proposal does not contain 'golem.com.pricing.model.linear.coeffs' property. We can't estimate costs.", + ); + } + + const envIdx = usageVector.findIndex((ele) => ele === "golem.usage.duration_sec"); + const cpuIdx = usageVector.findIndex((ele) => ele === "golem.usage.cpu_sec"); + + const envSec = priceVector[envIdx] ?? 0.0; + const cpuSec = priceVector[cpuIdx] ?? 0.0; + const start = priceVector[priceVector.length - 1]; + + return { + cpuSec, + envSec, + start, + }; + } + + get provider() { + return { + id: this.model.providerId, + name: this.properties["golem.node.id.name"] || "", + }; + } + get transferProtocol() { + return this.properties["golem.activity.caps.transfer.protocol"]; + } + get cpuBrand() { + return this.properties["golem.inf.cpu.brand"]; + } + get cpuCapabilities() { + return this.properties["golem.inf.cpu.capabilities"]; + } + get cpuCores() { + return this.properties["golem.inf.cpu.cores"]; + } + get cpuThreads() { + return this.properties["golem.inf.cpu.threads"]; + } + get memory() { + return this.properties["golem.inf.mem.gib"]; + } + get storage() { + return this.properties["golem.inf.storage.gib"]; + } + get publicNet() { + return this.properties["golem.node.net.is-public"]; + } + get runtimeCapabilities() { + return this.properties["golem.runtime.capabilities"]; + } + get runtimeName() { + return this.properties["golem.runtime.name"]; + } +} diff --git a/src/market/scan/types.ts b/src/market/scan/types.ts new file mode 100644 index 000000000..5141c2e14 --- /dev/null +++ b/src/market/scan/types.ts @@ -0,0 +1,26 @@ +export type ScanOptions = { + workload?: { + engine?: string; + capabilities?: string[]; + minMemGib?: number; + maxMemGib?: number; + minStorageGib?: number; + maxStorageGib?: number; + minCpuThreads?: number; + maxCpuThreads?: number; + minCpuCores?: number; + maxCpuCores?: number; + }; + subnetTag?: string; + payment?: { + network: string; + /** @default erc20 */ + driver?: string; + /** @default "glm" if network is mainnet or polygon, "tglm" otherwise */ + token?: string; + }; +}; + +export type ScanSpecification = { + constraints: string[]; +}; diff --git a/src/market/service.ts b/src/market/service.ts deleted file mode 100644 index 2bedfcb4b..000000000 --- a/src/market/service.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { defaultLogger, Logger, sleep, YagnaApi } from "../utils"; -import { Package } from "../package"; -import { Proposal } from "./proposal"; -import { AgreementPoolService } from "../agreement"; -import { Allocation } from "../payment"; -import { Demand, DEMAND_EVENT_TYPE, DemandEvent, DemandOptions } from "./demand"; -import { MarketConfig } from "./config"; -import { GolemMarketError, MarketErrorCode } from "./error"; -import { ProposalsBatch } from "./proposals_batch"; - -export type ProposalFilter = (proposal: Proposal) => boolean; - -export interface MarketOptions extends DemandOptions { - /** - * A custom filter checking the proposal from the market for each provider and its hardware configuration. - * Duplicate proposals from one provider are reduced to the cheapest one. - */ - proposalFilter?: ProposalFilter; - /** The minimum number of proposals after which the batch of proposal will be processed in order to avoid duplicates */ - minProposalsBatchSize?: number; - /** The maximum waiting time for proposals to be batched in order to avoid duplicates */ - proposalsBatchReleaseTimeoutMs?: number; -} - -/** - * Market Service - * @description Service used in {@link TaskExecutor} - * @internal - */ -export class MarketService { - private readonly options: MarketConfig; - private demand?: Demand; - private allocation?: Allocation; - private logger: Logger; - private taskPackage?: Package; - private maxResubscribeRetries = 5; - private proposalsCount = { - initial: 0, - confirmed: 0, - rejected: 0, - }; - private proposalsBatch: ProposalsBatch; - private isRunning = false; - - constructor( - private readonly agreementPoolService: AgreementPoolService, - private readonly yagnaApi: YagnaApi, - options?: MarketOptions, - ) { - this.options = new MarketConfig(options); - this.logger = this.options?.logger || defaultLogger("market"); - this.proposalsBatch = new ProposalsBatch({ - minBatchSize: options?.minProposalsBatchSize, - releaseTimeoutMs: options?.proposalsBatchReleaseTimeoutMs, - }); - } - - async run(taskPackage: Package, allocation: Allocation) { - this.isRunning = true; - this.taskPackage = taskPackage; - this.allocation = allocation; - await this.createDemand(); - this.startProcessingProposalsBatch().catch((e) => this.logger.error("Error processing proposal batch", e)); - this.logger.info("Market Service has started"); - } - - async end() { - this.isRunning = false; - if (this.demand) { - this.demand.removeEventListener(DEMAND_EVENT_TYPE, this.demandEventListener.bind(this)); - await this.demand.unsubscribe().catch((e) => this.logger.error(`Could not unsubscribe demand.`, e)); - } - this.logger.info("Market Service has been stopped"); - } - - getProposalsCount() { - return this.proposalsCount; - } - - private async createDemand(): Promise { - if (!this.taskPackage || !this.allocation) - throw new GolemMarketError( - "The service has not been started correctly.", - MarketErrorCode.ServiceNotInitialized, - this.demand, - ); - this.demand = await Demand.create(this.taskPackage, this.allocation, this.yagnaApi, this.options); - this.demand.addEventListener(DEMAND_EVENT_TYPE, this.demandEventListener.bind(this)); - this.proposalsCount = { - initial: 0, - confirmed: 0, - rejected: 0, - }; - this.logger.debug(`New demand has been created`, { id: this.demand.id }); - return true; - } - - private demandEventListener(event: Event) { - const proposal = (event as DemandEvent).proposal; - const error = (event as DemandEvent).error; - if (error instanceof GolemMarketError && error.code === MarketErrorCode.DemandExpired) { - this.logger.error("Demand expired. Trying to subscribe a new one..."); - this.resubscribeDemand().catch((e) => this.logger?.warn(`Could not resubscribe demand.`, e)); - return; - } - if (error || !proposal) { - this.logger.error("Collecting offers failed. Trying to subscribe a new demand..."); - this.resubscribeDemand().catch((e) => this.logger?.warn(`Could not resubscribe demand.`, e)); - return; - } - if (proposal.isInitial()) this.proposalsBatch.addProposal(proposal); - else if (proposal.isDraft()) this.processDraftProposal(proposal); - else if (proposal.isExpired()) this.logger.debug(`Proposal hes expired`, { id: proposal.id }); - else if (proposal.isRejected()) { - this.proposalsCount.rejected++; - this.logger.debug(`Proposal hes rejected`, { id: proposal.id }); - } - } - - private async resubscribeDemand() { - if (this.demand) { - this.demand.removeEventListener(DEMAND_EVENT_TYPE, this.demandEventListener.bind(this)); - await this.demand.unsubscribe().catch((e) => this.logger.error(`Could not unsubscribe demand.`, e)); - } - let attempt = 1; - let success = false; - while (!success && attempt <= this.maxResubscribeRetries) { - success = Boolean(await this.createDemand().catch((e) => this.logger.error(`Could not resubscribe demand.`, e))); - ++attempt; - await sleep(20); - } - } - - private async processInitialProposal(proposal: Proposal) { - if (!this.allocation) - throw new GolemMarketError( - "Allocation is missing. The service has not been started correctly.", - MarketErrorCode.MissingAllocation, - this.demand, - ); - this.logger.debug(`New proposal has been received`, { id: proposal.id }); - this.proposalsCount.initial++; - try { - const { result: isProposalValid, reason } = await this.isProposalValid(proposal); - if (isProposalValid) { - const chosenPlatform = this.allocation.paymentPlatform; - await proposal - .respond(chosenPlatform) - .catch((e) => this.logger.debug(`Unable to respond proposal`, { id: proposal.id, e })); - this.logger.debug(`Proposal has been responded`, { id: proposal.id }); - } else { - this.proposalsCount.rejected++; - this.logger.error(`Proposal has been rejected`, { id: proposal.id, reason }); - } - } catch (error) { - this.logger.error(`Unable to respond proposal`, { id: proposal.id, error }); - } - } - - private async isProposalValid(proposal: Proposal): Promise<{ result: boolean; reason?: string }> { - if (!this.allocation) { - throw new GolemMarketError( - "Allocation is missing. The service has not been started correctly.", - MarketErrorCode.MissingAllocation, - this.demand, - ); - } - - const timeout = proposal.properties["golem.com.payment.debit-notes.accept-timeout?"]; - if (timeout && timeout < this.options.debitNotesAcceptanceTimeoutSec) { - return { result: false, reason: "Debit note acceptance timeout too short" }; - } - - if (!proposal.hasPaymentPlatform(this.allocation.paymentPlatform)) { - return { result: false, reason: "No common payment platform" }; - } - - if (!this.options.proposalFilter(proposal)) { - return { result: false, reason: "Proposal rejected by Proposal Filter" }; - } - - return { result: true }; - } - - private async processDraftProposal(proposal: Proposal) { - await this.agreementPoolService.addProposal(proposal); - this.proposalsCount.confirmed++; - this.logger.debug(`Proposal has been confirmed and added to agreement pool`, { - providerName: proposal.provider.name, - issuerId: proposal.issuerId, - id: proposal.id, - }); - } - - private async startProcessingProposalsBatch() { - while (this.isRunning) { - this.logger.debug("Waiting for proposals"); - await this.proposalsBatch.waitForProposals(); - - const proposals = await this.proposalsBatch.getProposals(); - this.logger.debug("Received batch of proposals", { count: proposals.length }); - - proposals.forEach((proposal) => this.processInitialProposal(proposal)); - } - } -} diff --git a/src/market/strategy.test.ts b/src/market/strategy.test.ts new file mode 100644 index 000000000..e3139ed2c --- /dev/null +++ b/src/market/strategy.test.ts @@ -0,0 +1,136 @@ +import { instance, mock, reset, when } from "@johanblumenberg/ts-mockito"; +import { OfferProposal } from "./proposal/offer-proposal"; +import { + acceptAll, + allowProvidersById, + allowProvidersByNameRegex, + disallowProvidersById, + disallowProvidersByNameRegex, +} from "./strategy"; + +const mockProposal = mock(OfferProposal); + +describe("SDK provided proposal filters", () => { + beforeEach(() => { + reset(mockProposal); + }); + + describe("acceptAll", () => { + test("Accepts proposals from any provider", () => { + when(mockProposal.provider) + .thenReturn({ + id: "provider-1", + name: "provider-name-1", + walletAddress: "operator-1", + }) + .thenReturn({ + id: "provider-2", + name: "provider-name-2", + walletAddress: "operator-2", + }); + + const p1 = instance(mockProposal); + const p2 = instance(mockProposal); + + const accepted = [p1, p2].filter(acceptAll()); + + expect(accepted.length).toEqual(2); + }); + }); + + describe("disallowProvidersById", () => { + test("Accepts only the providers with the name not listed on the blacklist", () => { + const p1 = mock(OfferProposal); + const p2 = mock(OfferProposal); + + when(p1.provider).thenReturn({ + id: "provider-1", + name: "provider-name-1", + walletAddress: "operator-1", + }); + + when(p2.provider).thenReturn({ + id: "provider-2", + name: "provider-name-2", + walletAddress: "operator-2", + }); + + const accepted = [instance(p1), instance(p2)].filter(disallowProvidersById(["provider-1"])); + + expect(accepted.length).toEqual(1); + expect(accepted[0].provider.id).toEqual("provider-2"); + }); + }); + + describe("disallowProvidersByNameRegex", () => { + test("Accepts only the providers which name doesn't match the specified regex", () => { + const p1 = mock(OfferProposal); + const p2 = mock(OfferProposal); + + when(p1.provider).thenReturn({ + id: "provider-1", + name: "provider-name-1", + walletAddress: "operator-1", + }); + + when(p2.provider).thenReturn({ + id: "provider-2", + name: "golem2004", + walletAddress: "operator-2", + }); + + const accepted = [instance(p1), instance(p2)].filter(disallowProvidersByNameRegex(/golem2004/)); + + expect(accepted.length).toEqual(1); + expect(accepted[0].provider.id).toEqual("provider-1"); + }); + }); + + describe("allowProvidersById", () => { + test("Accepts only the providers who's ID's are on the list", () => { + const p1 = mock(OfferProposal); + const p2 = mock(OfferProposal); + + when(p1.provider).thenReturn({ + id: "provider-1", + name: "provider-name-1", + walletAddress: "operator-1", + }); + + when(p2.provider).thenReturn({ + id: "provider-2", + name: "provider-name-2", + walletAddress: "operator-2", + }); + + const accepted = [instance(p1), instance(p2)].filter(allowProvidersById(["provider-1"])); + + expect(accepted.length).toEqual(1); + expect(accepted[0].provider.id).toEqual("provider-1"); + }); + + describe("allowProvidersByNameRegex", () => { + test("Accepts only the providers who's names match the provided regex", () => { + const p1 = mock(OfferProposal); + const p2 = mock(OfferProposal); + + when(p1.provider).thenReturn({ + id: "provider-1", + name: "provider-name-1", + walletAddress: "operator-1", + }); + + when(p2.provider).thenReturn({ + id: "provider-2", + name: "provider-name-2", + walletAddress: "operator-2", + }); + + const accepted = [instance(p1), instance(p2)].filter(allowProvidersByNameRegex(/-1$/)); + + expect(accepted.length).toEqual(1); + expect(accepted[0].provider.id).toEqual("provider-1"); + }); + }); + }); +}); diff --git a/src/market/strategy.ts b/src/market/strategy.ts index 980ebe324..ccf6673b5 100644 --- a/src/market/strategy.ts +++ b/src/market/strategy.ts @@ -1,30 +1,30 @@ -import { Proposal } from "./proposal"; +import { OfferProposal } from "./proposal/offer-proposal"; /** Default Proposal filter that accept all proposal coming from the market */ export const acceptAll = () => () => true; /** Proposal filter blocking every offer coming from a provider whose id is in the array */ -export const disallowProvidersById = (providerIds: string[]) => (proposal: Proposal) => +export const disallowProvidersById = (providerIds: string[]) => (proposal: OfferProposal) => !providerIds.includes(proposal.provider.id); /** Proposal filter blocking every offer coming from a provider whose name is in the array */ -export const disallowProvidersByName = (providerNames: string[]) => (proposal: Proposal) => +export const disallowProvidersByName = (providerNames: string[]) => (proposal: OfferProposal) => !providerNames.includes(proposal.provider.name); /** Proposal filter blocking every offer coming from a provider whose name match to the regexp */ -export const disallowProvidersByNameRegex = (regexp: RegExp) => (proposal: Proposal) => +export const disallowProvidersByNameRegex = (regexp: RegExp) => (proposal: OfferProposal) => !proposal.provider.name.match(regexp); /** Proposal filter that only allows offers from a provider whose id is in the array */ -export const allowProvidersById = (providerIds: string[]) => (proposal: Proposal) => +export const allowProvidersById = (providerIds: string[]) => (proposal: OfferProposal) => providerIds.includes(proposal.provider.id); /** Proposal filter that only allows offers from a provider whose name is in the array */ -export const allowProvidersByName = (providerNames: string[]) => (proposal: Proposal) => +export const allowProvidersByName = (providerNames: string[]) => (proposal: OfferProposal) => providerNames.includes(proposal.provider.name); /** Proposal filter that only allows offers from a provider whose name match to the regexp */ -export const allowProvidersByNameRegex = (regexp: RegExp) => (proposal: Proposal) => +export const allowProvidersByNameRegex = (regexp: RegExp) => (proposal: OfferProposal) => !!proposal.provider.name.match(regexp); export type PriceLimits = { @@ -40,7 +40,7 @@ export type PriceLimits = { * @param priceLimits.cpuPerSec The maximum price for CPU usage in GLM/s * @param priceLimits.envPerSec The maximum price for the duration of the activity in GLM/s */ -export const limitPriceFilter = (priceLimits: PriceLimits) => (proposal: Proposal) => { +export const limitPriceFilter = (priceLimits: PriceLimits) => (proposal: OfferProposal) => { return ( proposal.pricing.cpuSec <= priceLimits.cpuPerSec && proposal.pricing.envSec <= priceLimits.envPerSec && diff --git a/src/network/api.ts b/src/network/api.ts new file mode 100644 index 000000000..6ae88d841 --- /dev/null +++ b/src/network/api.ts @@ -0,0 +1,52 @@ +import { Network } from "./network"; +import { NetworkNode } from "./node"; +import { NetworkOptions } from "./network.module"; + +export interface NetworkEvents { + networkCreated: (event: { network: Network }) => void; + errorCreatingNetwork: (event: { error: Error }) => void; + + networkRemoved: (event: { network: Network }) => void; + errorRemovingNetwork: (event: { network: Network; error: Error }) => void; + + nodeCreated: (event: { network: Network; node: NetworkNode }) => void; + errorCreatingNode: (event: { network: Network; error: Error }) => void; + + nodeRemoved: (event: { network: Network; node: NetworkNode }) => void; + errorRemovingNode: (event: { network: Network; node: NetworkNode; error: Error }) => void; +} + +export interface INetworkApi { + /** + * Creates a new network with the specified options. + * @param options NetworkOptions + */ + createNetwork(options: NetworkOptions): Promise; + + /** + * Removes an existing network. + * @param network - The network to be removed. + */ + removeNetwork(network: Network): Promise; + + /** + * Creates a new node within a specified network. + * @param network - The network to which the node will be added. + * @param nodeId - The ID of the node to be created. + * @param nodeIp - Optional IP address for the node. If not provided, the first available IP address will be assigned. + */ + + createNetworkNode(network: Network, nodeId: string, nodeIp?: string): Promise; + + /** + * Removes an existing node from a specified network. + * @param network - The network from which the node will be removed. + * @param node - The node to be removed. + */ + removeNetworkNode(network: Network, node: NetworkNode): Promise; + + /** + * Returns the identifier of the requesor + */ + getIdentity(): Promise; +} diff --git a/src/network/config.ts b/src/network/config.ts deleted file mode 100644 index b8151ec61..000000000 --- a/src/network/config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { NetworkOptions } from "./network"; -import { Logger, defaultLogger } from "../utils"; - -const DEFAULTS = { - networkIp: "192.168.0.0/24", -}; - -/** - * @internal - */ -export class NetworkConfig { - public readonly mask?: string; - public readonly ip: string; - public readonly ownerId: string; - public readonly ownerIp?: string; - public readonly gateway?: string; - public readonly logger: Logger; - - constructor(options: NetworkOptions) { - this.ip = options?.networkIp || DEFAULTS.networkIp; - this.mask = options?.networkMask; - this.ownerId = options.networkOwnerId; - this.ownerIp = options?.networkOwnerIp; - this.gateway = options?.networkGateway; - this.logger = options?.logger || defaultLogger("network"); - } -} diff --git a/src/network/error.ts b/src/network/error.ts index eda92eb2a..288bc8f73 100644 --- a/src/network/error.ts +++ b/src/network/error.ts @@ -1,25 +1,32 @@ -import { GolemModuleError } from "../error/golem-error"; +import { GolemModuleError } from "../shared/error/golem-error"; import { NetworkInfo } from "./network"; export enum NetworkErrorCode { - ServiceNotInitialized, - NetworkSetupMissing, - NetworkCreationFailed, - NoAddressesAvailable, - AddressOutOfRange, - AddressAlreadyAssigned, - NodeAddingFailed, - NodeRemovalFailed, - NetworkRemovalFailed, + ServiceNotInitialized = "ServiceNotInitialized", + NetworkSetupMissing = "NetworkSetupMissing", + NetworkCreationFailed = "NetworkCreationFailed", + NoAddressesAvailable = "NoAddressesAvailable", + AddressOutOfRange = "AddressOutOfRange", + AddressAlreadyAssigned = "AddressAlreadyAssigned", + NodeAddingFailed = "NodeAddingFailed", + NodeRemovalFailed = "NodeRemovalFailed", + NetworkRemovalFailed = "NetworkRemovalFailed", + GettingIdentityFailed = "GettingIdentityFailed", + NetworkRemoved = "NetworkRemoved", } export class GolemNetworkError extends GolemModuleError { + #network?: NetworkInfo; constructor( message: string, public code: NetworkErrorCode, - public network?: NetworkInfo, + network?: NetworkInfo, public previous?: Error, ) { super(message, code, previous); + this.#network = network; + } + public getNetwork(): NetworkInfo | undefined { + return this.#network; } } diff --git a/src/network/index.ts b/src/network/index.ts index 5669665ec..1c0844695 100644 --- a/src/network/index.ts +++ b/src/network/index.ts @@ -1,4 +1,5 @@ -export { Network } from "./network"; -export { NetworkNode } from "./node"; -export { NetworkService, NetworkServiceOptions } from "./service"; -export { GolemNetworkError, NetworkErrorCode } from "./error"; +export * from "./network"; +export * from "./node"; +export * from "./network.module"; +export * from "./error"; +export * from "./api"; diff --git a/src/network/network.module.test.ts b/src/network/network.module.test.ts new file mode 100644 index 000000000..aba27904a --- /dev/null +++ b/src/network/network.module.test.ts @@ -0,0 +1,201 @@ +import { + GolemNetworkError, + Logger, + Network, + NetworkErrorCode, + NetworkInfo, + NetworkModuleImpl, + NetworkNode, +} from "../index"; +import { mock, anything, capture, imock, instance, reset, verify, when } from "@johanblumenberg/ts-mockito"; +import { INetworkApi } from "./api"; +import { IPv4 } from "ip-num"; + +const mockNetworkApi = imock(); +const mockNetwork = mock(Network); + +let networkModule: NetworkModuleImpl; + +describe("Network", () => { + beforeEach(() => { + reset(mockNetworkApi); + reset(mockNetwork); + networkModule = new NetworkModuleImpl({ + networkApi: instance(mockNetworkApi), + logger: instance(imock()), + }); + when(mockNetworkApi.createNetwork(anything())).thenResolve(instance(mockNetwork)); + when(mockNetwork.getNetworkInfo()).thenReturn({ + id: "test-id-1", + ip: "192.168.0.0", + mask: "255.255.255.0", + } as NetworkInfo); + when(mockNetwork.isIpInNetwork(anything())).thenReturn(true); + when(mockNetwork.isNodeIpUnique(anything())).thenReturn(true); + when(mockNetwork.isNodeIdUnique(anything())).thenReturn(true); + when(mockNetwork.getFirstAvailableIpAddress()).thenReturn(IPv4.fromString("192.168.0.1")); + }); + + describe("Creating", () => { + it("should create network with default ip and mask", async () => { + await networkModule.createNetwork(); + expect(capture(mockNetworkApi.createNetwork).last()).toEqual([ + { + ip: "192.168.0.0", + mask: "255.255.255.0", + }, + ]); + }); + + it("should create network with 16 bit mask", async () => { + await networkModule.createNetwork({ ip: "192.168.7.0/16" }); + expect(capture(mockNetworkApi.createNetwork).last()).toEqual([ + { + ip: "192.168.0.0", + mask: "255.255.0.0", + }, + ]); + }); + + it("should create network with 24 bit mask", async () => { + await networkModule.createNetwork({ ip: "192.168.7.0/24" }); + expect(capture(mockNetworkApi.createNetwork).last()).toEqual([ + { + ip: "192.168.7.0", + mask: "255.255.255.0", + }, + ]); + }); + + it("should create network with 8 bit mask", async () => { + await networkModule.createNetwork({ ip: "192.168.7.0/8" }); + expect(capture(mockNetworkApi.createNetwork).last()).toEqual([ + { + ip: "192.0.0.0", + mask: "255.0.0.0", + }, + ]); + }); + + it("should not create network with invalid ip", async () => { + const shouldFail = networkModule.createNetwork({ ip: "123.1.2" }); + await expect(shouldFail).rejects.toMatchError( + new GolemNetworkError( + "Unable to create network. An IP4 number cannot have less or greater than 4 octets", + NetworkErrorCode.NetworkCreationFailed, + undefined, + new Error("An IP4 number cannot have less or greater than 4 octets"), + ), + ); + }); + + it("should create network with custom gateway", async () => { + await networkModule.createNetwork({ + ip: "192.168.0.1/27", + gateway: "192.168.0.2", + }); + expect(capture(mockNetworkApi.createNetwork).last()).toEqual([ + { + ip: "192.168.0.0", + mask: "255.255.255.224", + gateway: "192.168.0.2", + }, + ]); + }); + }); + describe("Nodes", () => { + it("should create a new node", async () => { + const network = instance(mockNetwork); + await networkModule.createNetworkNode(network, "7", "192.168.0.7"); + expect(capture(mockNetworkApi.createNetworkNode).last()).toEqual([network, "7", "192.168.0.7"]); + }); + + it("should create a few nodes", async () => { + const network = instance(mockNetwork); + await networkModule.createNetworkNode(network, "2", "192.168.0.3"); + await networkModule.createNetworkNode(network, "3", "192.168.0.7"); + expect(capture(mockNetworkApi.createNetworkNode).first()).toEqual([network, "2", "192.168.0.3"]); + expect(capture(mockNetworkApi.createNetworkNode).last()).toEqual([network, "3", "192.168.0.7"]); + }); + + it("should not create a node with an existing ID", async () => { + const network = instance(mockNetwork); + when(mockNetwork.isNodeIdUnique("2")).thenReturn(false); + await expect(networkModule.createNetworkNode(network, "2", "192.168.0.7")).rejects.toMatchError( + new GolemNetworkError( + `Network ID '2' has already been assigned in this network.`, + NetworkErrorCode.AddressAlreadyAssigned, + ), + ); + }); + + it("should not create a node with an existing IP", async () => { + when(mockNetwork.isNodeIpUnique(anything())).thenReturn(false); + const network = instance(mockNetwork); + await expect(networkModule.createNetworkNode(network, "3", "192.168.0.3")).rejects.toMatchError( + new GolemNetworkError( + "IP '192.168.0.3' has already been assigned in this network.", + NetworkErrorCode.AddressAlreadyAssigned, + ), + ); + }); + + it("should not add a node with address outside the network range", async () => { + const network = instance(mockNetwork); + when(mockNetwork.isIpInNetwork(anything())).thenReturn(false); + await expect(networkModule.createNetworkNode(network, "3", "192.168.2.2")).rejects.toMatchError( + new GolemNetworkError( + "The given IP ('192.168.2.2') address must belong to the network ('192.168.0.0').", + NetworkErrorCode.AddressOutOfRange, + ), + ); + }); + + it("should not add too many nodes", async () => { + const network = instance(mockNetwork); + const mockError = new GolemNetworkError( + "No more addresses available in 192.168.0.0/30", + NetworkErrorCode.NoAddressesAvailable, + ); + when(mockNetwork.getFirstAvailableIpAddress()).thenThrow(mockError); + await expect(networkModule.createNetworkNode(network, "next-id")).rejects.toMatchError(mockError); + }); + + it("should not remove node from the network if it does not belong to the network", async () => { + const network = instance(mockNetwork); + const mockNode = mock(NetworkNode); + const node = instance(mockNode); + when(mockNode.id).thenReturn("88"); + when(mockNetwork.hasNode(node)).thenReturn(false); + await expect(networkModule.removeNetworkNode(network, node)).rejects.toMatchError( + new GolemNetworkError(`The network node 88 does not belong to the network`, NetworkErrorCode.NodeRemovalFailed), + ); + }); + + it("should ignore the removal of the node if the network has been removed", async () => { + const network = instance(mockNetwork); + const mockNode = mock(NetworkNode); + const node = instance(mockNode); + when(mockNode.id).thenReturn("88"); + when(mockNetwork.hasNode(node)).thenReturn(true); + when(mockNetwork.isRemoved()).thenReturn(true); + await networkModule.removeNetworkNode(network, node); + verify(mockNetworkApi.removeNetworkNode(anything(), anything())).never(); + }); + }); + + describe("Removing", () => { + it("should remove network", async () => { + const network = instance(mockNetwork); + await networkModule.removeNetwork(network); + verify(mockNetworkApi.removeNetwork(network)).once(); + }); + + it("should not remove network that doesn't exist", async () => { + const network = instance(mockNetwork); + const mockError = new Error("404"); + when(mockNetworkApi.removeNetwork(network)).thenReject(mockError); + await expect(networkModule.removeNetwork(network)).rejects.toMatchError(mockError); + }); + }); +}); diff --git a/src/network/network.module.ts b/src/network/network.module.ts new file mode 100644 index 000000000..2faa4a1c2 --- /dev/null +++ b/src/network/network.module.ts @@ -0,0 +1,220 @@ +import { EventEmitter } from "eventemitter3"; +import { Network } from "./network"; +import { GolemNetworkError, NetworkErrorCode } from "./error"; +import { defaultLogger, Logger } from "../shared/utils"; +import { INetworkApi, NetworkEvents } from "./api"; +import { NetworkNode } from "./node"; +import { IPv4, IPv4CidrRange, IPv4Mask } from "ip-num"; +import AsyncLock from "async-lock"; +import { getMessageFromApiError } from "../shared/utils/apiErrorMessage"; + +export interface NetworkOptions { + /** + * The IP address of the network. May contain netmask, e.g. "192.168.0.0/24". + * This field can include the netmask directly in CIDR notation. + * @default "192.168.0.0" + */ + ip?: string; + + /** + * The desired IP address of the requestor node within the newly-created network. + * This field is optional and if not provided, the first available IP address will be assigned. + */ + ownerIp?: string; + + /** + * Optional network mask given in dotted decimal notation. + * If the ip address was provided in Cidr notation this mask will override the mask from the Cidr notation + */ + mask?: string; + + /** + * Optional gateway address for the network. + * This field can be used to specify a gateway IP address for the network. + */ + gateway?: string; +} + +export interface NetworkModule { + events: EventEmitter; + + /** + * Creates a new network with the specified options. + * @param options NetworkOptions + */ + createNetwork(options?: NetworkOptions): Promise; + + /** + * Removes an existing network. + * @param network - The network to be removed. + */ + removeNetwork(network: Network): Promise; + + /** + * Creates a new node within a specified network. + * @param network - The network to which the node will be added. + * @param nodeId - The ID of the node to be created. + * @param nodeIp - Optional IP address for the node. If not provided, the first available IP address will be assigned. + */ + createNetworkNode(network: Network, nodeId: string, nodeIp?: string): Promise; + + /** + * Removes an existing node from a specified network. + * @param network - The network from which the node will be removed. + * @param node - The node to be removed. + */ + removeNetworkNode(network: Network, node: NetworkNode): Promise; +} + +export class NetworkModuleImpl implements NetworkModule { + events: EventEmitter = new EventEmitter(); + + private readonly networkApi: INetworkApi; + + private readonly logger = defaultLogger("network"); + + private lock: AsyncLock = new AsyncLock(); + + constructor(deps: { logger?: Logger; networkApi: INetworkApi }) { + this.networkApi = deps.networkApi; + if (deps.logger) { + this.logger = deps.logger; + } + } + + async createNetwork(options?: NetworkOptions): Promise { + this.logger.debug(`Creating network`, options); + try { + const ipDecimalDottedString = options?.ip?.split("/")?.[0] || "192.168.0.0"; + const maskBinaryNotation = parseInt(options?.ip?.split("/")?.[1] || "24"); + const maskPrefix = options?.mask ? IPv4Mask.fromDecimalDottedString(options.mask).prefix : maskBinaryNotation; + const ipRange = IPv4CidrRange.fromCidr(`${IPv4.fromString(ipDecimalDottedString)}/${maskPrefix}`); + const ip = ipRange.getFirst(); + const mask = ipRange.getPrefix().toMask(); + const gateway = options?.gateway ? new IPv4(options.gateway) : undefined; + const network = await this.networkApi.createNetwork({ + ip: ip.toString(), + mask: mask?.toString(), + gateway: gateway?.toString(), + }); + // add Requestor as network node + const requestorId = await this.networkApi.getIdentity(); + await this.createNetworkNode(network, requestorId, options?.ownerIp); + this.logger.info(`Created network`, network.getNetworkInfo()); + this.events.emit("networkCreated", { network }); + return network; + } catch (err) { + const message = getMessageFromApiError(err); + const error = + err instanceof GolemNetworkError + ? err + : new GolemNetworkError( + `Unable to create network. ${message}`, + NetworkErrorCode.NetworkCreationFailed, + undefined, + err, + ); + this.events.emit("errorCreatingNetwork", { error }); + throw error; + } + } + async removeNetwork(network: Network): Promise { + this.logger.debug(`Removing network`, network.getNetworkInfo()); + await this.lock.acquire(`net-${network.id}`, async () => { + try { + await this.networkApi.removeNetwork(network); + network.markAsRemoved(); + this.logger.info(`Removed network`, network.getNetworkInfo()); + this.events.emit("networkRemoved", { network }); + } catch (error) { + this.events.emit("errorRemovingNetwork", { network, error }); + throw error; + } + }); + } + + async createNetworkNode(network: Network, nodeId: string, nodeIp?: string): Promise { + this.logger.debug(`Creating network node`, { nodeId, nodeIp }); + return await this.lock.acquire(`net-${network.id}`, async () => { + try { + if (!network.isNodeIdUnique(nodeId)) { + throw new GolemNetworkError( + `Network ID '${nodeId}' has already been assigned in this network.`, + NetworkErrorCode.AddressAlreadyAssigned, + network.getNetworkInfo(), + ); + } + if (network.isRemoved()) { + throw new GolemNetworkError( + `Unable to create network node ${nodeId}. Network has already been removed`, + NetworkErrorCode.NetworkRemoved, + network.getNetworkInfo(), + ); + } + const ipv4 = this.getFreeIpInNetwork(network, nodeIp); + const node = await this.networkApi.createNetworkNode(network, nodeId, ipv4.toString()); + network.addNode(node); + this.logger.info(`Added network node`, { id: nodeId, ip: ipv4.toString() }); + this.events.emit("nodeCreated", { network, node }); + return node; + } catch (error) { + this.events.emit("errorCreatingNode", { network, error }); + throw error; + } + }); + } + + async removeNetworkNode(network: Network, node: NetworkNode): Promise { + this.logger.debug(`Removing network node`, { nodeId: node.id, nodeIp: node.ip }); + return await this.lock.acquire(`net-${network.id}`, async () => { + try { + if (!network.hasNode(node)) { + throw new GolemNetworkError( + `The network node ${node.id} does not belong to the network`, + NetworkErrorCode.NodeRemovalFailed, + network.getNetworkInfo(), + ); + } + if (network.isRemoved()) { + this.logger.debug(`Unable to remove network node ${node.id}. Network has already been removed`, { + network, + node, + }); + return; + } + await this.networkApi.removeNetworkNode(network, node); + network.removeNode(node); + this.logger.info(`Removed network node`, { + network: network.getNetworkInfo().ip, + nodeIp: node.ip, + }); + this.events.emit("nodeRemoved", { network, node }); + } catch (error) { + this.events.emit("errorRemovingNode", { network, node, error }); + throw error; + } + }); + } + + private getFreeIpInNetwork(network: Network, targetIp?: string): IPv4 { + if (!targetIp) { + return network.getFirstAvailableIpAddress(); + } + const ipv4 = IPv4.fromString(targetIp); + if (!network.isIpInNetwork(ipv4)) { + throw new GolemNetworkError( + `The given IP ('${targetIp}') address must belong to the network ('${network.getNetworkInfo().ip}').`, + NetworkErrorCode.AddressOutOfRange, + network.getNetworkInfo(), + ); + } + if (!network.isNodeIpUnique(ipv4)) { + throw new GolemNetworkError( + `IP '${targetIp.toString()}' has already been assigned in this network.`, + NetworkErrorCode.AddressAlreadyAssigned, + network.getNetworkInfo(), + ); + } + return ipv4; + } +} diff --git a/src/network/network.test.ts b/src/network/network.test.ts new file mode 100644 index 000000000..28f3fcae2 --- /dev/null +++ b/src/network/network.test.ts @@ -0,0 +1,123 @@ +import { Network } from "./network"; +import { instance, mock, when } from "@johanblumenberg/ts-mockito"; +import { NetworkNode } from "./node"; +import { GolemNetworkError, NetworkErrorCode } from "./error"; +import { IPv4 } from "ip-num"; + +const mockNetworkNode = mock(NetworkNode); +when(mockNetworkNode.id).thenReturn("network-node-id"); +when(mockNetworkNode.ip).thenReturn("192.168.0.2"); + +describe("Network", () => { + describe("Creating", () => { + test("should create a network from the ip given in Cidr notation", () => { + const network = new Network("network-id", "192.168.0.0/24"); + const networkInfo = network.getNetworkInfo(); + expect(networkInfo.id).toEqual("network-id"); + expect(networkInfo.ip).toEqual("192.168.0.0"); + expect(networkInfo.mask).toEqual("255.255.255.0"); + }); + test("should create a network from the ip and mask given in decimal dotted noatation", () => { + const network = new Network("network-id", "192.168.0.0", "255.255.255.0"); + const networkInfo = network.getNetworkInfo(); + expect(networkInfo.id).toEqual("network-id"); + expect(networkInfo.ip).toEqual("192.168.0.0"); + expect(networkInfo.mask).toEqual("255.255.255.0"); + }); + test("should not create a network with invalid ip", () => { + expect(() => new Network("network-id", "192.168.0")).toThrow( + new Error("Cidr notation should be in the form [ip number]/[range]"), + ); + }); + test("should not create a network with invalid mask", () => { + expect(() => new Network("network-id", "192.168.0.0", "255.0")).toThrow( + new Error("An IP4 number cannot have less or greater than 4 octets"), + ); + }); + test("should not create a network with invalid gatewey", () => { + expect(() => new Network("network-id", "192.168.0.0", "255.255.255.0", "234")).toThrow( + new Error("An IP4 number cannot have less or greater than 4 octets"), + ); + }); + }); + describe("Adding nodes", () => { + test("should add a node to the network", () => { + const network = new Network("network-id", "192.168.0.0/24"); + network.addNode(instance(mockNetworkNode)); + const networkInfo = network.getNetworkInfo(); + expect(networkInfo.nodes).toEqual({ "192.168.0.2": "network-node-id" }); + }); + test("should not add a node with the existing id", () => { + const network = new Network("network-id", "192.168.0.0/24"); + network.addNode(instance(mockNetworkNode)); + expect(() => network.addNode(instance(mockNetworkNode))).toThrow( + new GolemNetworkError( + `Node network-node-id has already been added to this network`, + NetworkErrorCode.AddressAlreadyAssigned, + ), + ); + }); + test("should not add a node to removed network", () => { + const network = new Network("network-id", "192.168.0.0/24"); + network.markAsRemoved(); + expect(() => network.addNode(instance(mockNetworkNode))).toThrow( + new GolemNetworkError(`Unable to add node network-node-id to removed network`, NetworkErrorCode.NetworkRemoved), + ); + }); + test("should get first avialble ip adrress", () => { + const network = new Network("network-id", "192.168.0.0/24"); + expect(network.getFirstAvailableIpAddress().toString()).toEqual("192.168.0.1"); + }); + }); + describe("Remove nodes", () => { + test("should remove a node from the network", () => { + const network = new Network("network-id", "192.168.0.0/24"); + const networkNode = instance(mockNetworkNode); + network.addNode(networkNode); + network.removeNode(networkNode); + expect(network.getNetworkInfo().nodes).toEqual({}); + }); + test("should not remove a node if it does not belong to the network", () => { + const network = new Network("network-id", "192.168.0.0/24"); + const networkNode = instance(mockNetworkNode); + network.addNode(networkNode); + when(mockNetworkNode.id).thenReturn("test-id-2"); + expect(() => network.removeNode(instance(mockNetworkNode))).toThrow( + new GolemNetworkError(`There is no node test-id-2 in the network`, NetworkErrorCode.NodeRemovalFailed), + ); + }); + test("should not remove a node if network has been removed", () => { + const network = new Network("network-id", "192.168.0.0/24"); + const networkNode = instance(mockNetworkNode); + when(mockNetworkNode.id).thenReturn("test-id-1"); + network.addNode(networkNode); + network.markAsRemoved(); + expect(() => network.removeNode(networkNode)).toThrow( + new GolemNetworkError(`Unable to remove node test-id-1 from removed network`, NetworkErrorCode.NetworkRemoved), + ); + }); + }); + describe("Validating", () => { + test("should check if node id is unique", () => { + const network = new Network("network-id", "192.168.0.0/24"); + when(mockNetworkNode.id).thenReturn("test-id"); + network.addNode(instance(mockNetworkNode)); + expect(network.isNodeIdUnique("test-id")).toBe(false); + expect(network.isNodeIdUnique("test-id-2")).toBe(true); + }); + test("should check if node ip is unique", () => { + const network = new Network("network-id", "192.168.0.0/24"); + when(mockNetworkNode.ip).thenReturn("192.168.0.2"); + network.addNode(instance(mockNetworkNode)); + expect(network.isNodeIpUnique(IPv4.fromDecimalDottedString("192.168.0.2"))).toBe(false); + expect(network.isNodeIpUnique(IPv4.fromDecimalDottedString("192.168.0.3"))).toBe(true); + }); + test("should check if node ip belongs to the network", () => { + const network = new Network("network-id", "192.168.0.0/24"); + expect(network.isIpInNetwork(IPv4.fromDecimalDottedString("192.168.0.2"))).toBe(true); + expect(network.isIpInNetwork(IPv4.fromDecimalDottedString("195.168.0.3"))).toBe(false); + expect(network.isIpInNetwork(IPv4.fromDecimalDottedString("192.169.0.3"))).toBe(false); + expect(network.isIpInNetwork(IPv4.fromDecimalDottedString("192.168.1.3"))).toBe(false); + }); + }); +}); diff --git a/src/network/network.ts b/src/network/network.ts index f28c0a970..01ba6eb99 100644 --- a/src/network/network.ts +++ b/src/network/network.ts @@ -1,214 +1,115 @@ import { AbstractIPNum, IPv4, IPv4CidrRange, IPv4Mask, IPv4Prefix } from "ip-num"; -import { Logger, YagnaApi, YagnaOptions } from "../utils"; -import { NetworkConfig } from "./config"; import { NetworkNode } from "./node"; import { GolemNetworkError, NetworkErrorCode } from "./error"; -/** - * @hidden - */ -export interface NetworkOptions { - /** the node ID of the owner of this VPN (the requestor) */ - networkOwnerId: string; - /** {@link YagnaOptions} */ - yagnaOptions?: YagnaOptions; - /** the IP address of the network. May contain netmask, e.g. "192.168.0.0/24" */ - networkIp?: string; - /** the desired IP address of the requestor node within the newly-created network */ - networkOwnerIp?: string; - /** optional netmask (only if not provided within the `ip` argument) */ - networkMask?: string; - /** optional gateway address for the network */ - networkGateway?: string; - /** optional custom logger module */ - logger?: Logger; -} - export interface NetworkInfo { id: string; ip: string; mask: string; + gateway?: string; nodes: { [ip: string]: string }; } -/** - * Network module - an object represents VPN created between the requestor and the provider nodes within Golem Network. - * @hidden - */ +export enum NetworkState { + Active = "Active", + Removed = "Removed", +} + export class Network { private readonly ip: IPv4; private readonly ipRange: IPv4CidrRange; private ipIterator: Iterator; private mask: IPv4Mask; - private ownerId: string; - private ownerIp: IPv4; private gateway?: IPv4; private nodes = new Map(); - private logger: Logger; + private state: NetworkState = NetworkState.Active; - /** - * Create a new VPN. - * - * @param yagnaApi - {@link YagnaApi} - * @param options - {@link NetworkOptions} - */ - static async create(yagnaApi: YagnaApi, options: NetworkOptions): Promise { - const config = new NetworkConfig(options); - try { - const { - data: { id, ip, mask }, - } = await yagnaApi.net.createNetwork({ - id: config.ownerId, - ip: config.ip, - mask: config.mask, - gateway: config.gateway, - }); - const network = new Network(id!, yagnaApi, config); - await network.addNode(network.ownerId, network.ownerIp.toString()).catch(async (e) => { - await yagnaApi.net.removeNetwork(id as string); - throw e; - }); - config.logger.info(`Network created`, { id, ip, mask }); - return network; - } catch (error) { - if (error instanceof GolemNetworkError) { - throw error; - } - throw new GolemNetworkError( - `Unable to create network. ${error?.response?.data?.message || error}`, - NetworkErrorCode.NetworkCreationFailed, - undefined, - error, - ); - } - } - - /** - * @param id - * @param yagnaApi - * @param config - * @private - * @hidden - */ - private constructor( + constructor( public readonly id: string, - private readonly yagnaApi: YagnaApi, - public readonly config: NetworkConfig, + ip: string, + mask?: string, + gateway?: string, ) { - this.ipRange = IPv4CidrRange.fromCidr(config.mask ? `${config.ip}/${config.mask}` : config.ip); + this.ipRange = IPv4CidrRange.fromCidr( + mask ? `${ip.split("/")[0]}/${IPv4Mask.fromDecimalDottedString(mask).prefix}` : ip, + ); this.ipIterator = this.ipRange[Symbol.iterator](); - this.ip = this.nextAddress(); + this.ip = this.getFirstAvailableIpAddress(); this.mask = this.ipRange.getPrefix().toMask(); - this.ownerId = config.ownerId; - this.ownerIp = config.ownerIp ? new IPv4(config.ownerIp) : this.nextAddress(); - this.gateway = config.gateway ? new IPv4(config.gateway) : undefined; - this.logger = config.logger; + this.gateway = gateway ? new IPv4(gateway) : undefined; } /** - * Get Network Information - * @return NetworkInfo + * Returns information about the network. */ - getNetworkInfo(): NetworkInfo { + public getNetworkInfo(): NetworkInfo { return { id: this.id, ip: this.ip.toString(), mask: this.mask.toString(), - nodes: Object.fromEntries(Array.from(this.nodes).map(([id, node]) => [node.ip.toString(), id])), + gateway: this.gateway?.toString?.(), + nodes: Object.fromEntries(Array.from(this.nodes).map(([id, node]) => [node.ip, id])), }; } /** - * Add a new node to the network. - * - * @param nodeId Node ID within the Golem network of this VPN node - * @param ip IP address to assign to this node + * Adds a node to the network. + * @param node - The network node to be added. */ - async addNode(nodeId: string, ip?: string): Promise { - try { - this.ensureIdUnique(nodeId); - let ipv4: IPv4; - if (ip) { - ipv4 = IPv4.fromString(ip); - this.ensureIpInNetwork(ipv4); - this.ensureIpUnique(ipv4); - } else { - while (true) { - ipv4 = this.nextAddress(); - if (this.isIpUnique(ipv4)) break; - } - } - const node = new NetworkNode(nodeId, ipv4, this.getNetworkInfo.bind(this), this.getUrl()); - this.nodes.set(nodeId, node); - await this.yagnaApi.net.addNode(this.id, { id: nodeId, ip: ipv4.toString() }); - this.logger.debug(`Node has added to the network.`, { id: nodeId, ip: ipv4.toString() }); - return node; - } catch (error) { - if (error instanceof GolemNetworkError) { - throw error; - } + public addNode(node: NetworkNode) { + if (this.isRemoved()) { throw new GolemNetworkError( - `Unable to add node to network. ${error?.data?.message || error.toString()}`, - NetworkErrorCode.NodeAddingFailed, + `Unable to add node ${node.id} to removed network`, + NetworkErrorCode.NetworkRemoved, this.getNetworkInfo(), - error, ); } - } - - /** - * Remove the node from the network - * @param nodeId - */ - async removeNode(nodeId: string): Promise { - const node = this.nodes.get(nodeId); - if (!node) { - throw new GolemNetworkError( - `Unable to remove node ${nodeId}. There is no such node in the network`, - NetworkErrorCode.NodeRemovalFailed, - this.getNetworkInfo(), - ); - } - try { - await this.yagnaApi.net.removeNode(this.id, nodeId); - this.nodes.delete(nodeId); - this.logger.debug(`Node has removed from the network.`, { id: nodeId, ip: node.ip.toString() }); - } catch (error) { + if (this.hasNode(node)) { throw new GolemNetworkError( - `Unable to remove node ${nodeId}. ${error}`, - NetworkErrorCode.NetworkRemovalFailed, - this.getNetworkInfo(), - error, + `Node ${node.id} has already been added to this network`, + NetworkErrorCode.AddressAlreadyAssigned, ); } + this.nodes.set(node.id, node); } /** - * Checks whether the node belongs to the network - * @param nodeId + * Checks whether the node belongs to the network. + * @param node - The network node to check. */ - hasNode(nodeId: string): boolean { - return this.nodes.has(nodeId); + public hasNode(node: NetworkNode): boolean { + return this.nodes.has(node.id); } /** - * Remove this network, terminating any connections it provides + * Removes a node from the network. + * @param node - The network node to be removed. */ - async remove(): Promise { - try { - await this.yagnaApi.net.removeNetwork(this.id); - this.logger.info(`Network has removed:`, { id: this.id, ip: this.ip.toString() }); - } catch (error) { + public removeNode(node: NetworkNode) { + if (this.isRemoved()) { throw new GolemNetworkError( - `Unable to remove network. ${error?.data?.message || error.toString()}`, - NetworkErrorCode.NetworkRemovalFailed, + `Unable to remove node ${node.id} from removed network`, + NetworkErrorCode.NetworkRemoved, this.getNetworkInfo(), - error, ); } + if (!this.hasNode(node)) { + throw new GolemNetworkError(`There is no node ${node.id} in the network`, NetworkErrorCode.NodeRemovalFailed); + } + this.nodes.delete(node.id); } - private nextAddress(): IPv4 { + public markAsRemoved() { + if (this.state === NetworkState.Removed) { + throw new GolemNetworkError("Network already removed", NetworkErrorCode.NetworkRemoved, this.getNetworkInfo()); + } + this.state = NetworkState.Removed; + } + + /** + * Returns the first available IP address in the network. + */ + public getFirstAvailableIpAddress(): IPv4 { const ip = this.ipIterator.next().value; if (!ip) throw new GolemNetworkError( @@ -219,42 +120,33 @@ export class Network { return ip; } - private ensureIpInNetwork(ip: IPv4): boolean { - if (!this.ipRange.contains(new IPv4CidrRange(ip, new IPv4Prefix(BigInt(this.mask.prefix))))) - throw new GolemNetworkError( - `The given IP ('${ip.toString()}') address must belong to the network ('${this.ipRange.toCidrString()}').`, - NetworkErrorCode.AddressOutOfRange, - this.getNetworkInfo(), - ); - return true; - } - - private ensureIpUnique(ip: IPv4) { - if (!this.isIpUnique(ip)) - throw new GolemNetworkError( - `IP '${ip.toString()}' has already been assigned in this network.`, - NetworkErrorCode.AddressAlreadyAssigned, - this.getNetworkInfo(), - ); + /** + * Checks if a given IP address is within the network range. + * @param ip - The IPv4 address to check. + */ + public isIpInNetwork(ip: IPv4): boolean { + return this.ipRange.contains(new IPv4CidrRange(ip, new IPv4Prefix(BigInt(this.mask.prefix)))); } - private ensureIdUnique(id: string) { - if (this.nodes.has(id)) - throw new GolemNetworkError( - `Network ID '${id}' has already been assigned in this network.`, - NetworkErrorCode.AddressAlreadyAssigned, - this.getNetworkInfo(), - ); + /** + * Checks if a given node ID is unique within the network. + * @param id - The node ID to check. + */ + public isNodeIdUnique(id: string): boolean { + return !this.nodes.has(id); } - private isIpUnique(ip: IPv4): boolean { + /** + * Checks if a given IP address is unique within the network. + */ + public isNodeIpUnique(ip: IPv4): boolean { for (const node of this.nodes.values()) { - if (node.ip.isEquals(ip)) return false; + if (new IPv4(node.ip).isEquals(ip)) return false; } return true; } - private getUrl() { - return this.yagnaApi.net["basePath"]; + public isRemoved() { + return this.state === NetworkState.Removed; } } diff --git a/src/network/node.ts b/src/network/node.ts index 61ccec853..ceb6a91ae 100644 --- a/src/network/node.ts +++ b/src/network/node.ts @@ -1,4 +1,3 @@ -import { IPv4 } from "ip-num"; import { NetworkInfo } from "./network"; /** @@ -7,9 +6,9 @@ import { NetworkInfo } from "./network"; export class NetworkNode { constructor( public readonly id: string, - public readonly ip: IPv4, - private getNetworkInfo: () => NetworkInfo, - private apiUrl: string, + public readonly ip: string, + public getNetworkInfo: () => NetworkInfo, + public yagnaBaseUri: string, ) {} /** @@ -22,19 +21,14 @@ export class NetworkNode { net: [ { ...this.getNetworkInfo(), - nodeIp: this.ip.toString(), + nodeIp: this.ip, }, ], }; } - /** - * Get the websocket URI corresponding with a specific TCP port on this Node. - * @param port TCP port of the service within the runtime - * @return the url - */ - getWebsocketUri(port: number) { - const url = new URL(this.apiUrl); + getWebsocketUri(port: number): string { + const url = new URL(this.yagnaBaseUri); url.protocol = "ws"; return `${url.href}/net/${this.getNetworkInfo().id}/tcp/${this.ip}/${port}`; } diff --git a/src/network/service.ts b/src/network/service.ts deleted file mode 100644 index 8cf529712..000000000 --- a/src/network/service.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { defaultLogger, Logger, YagnaApi } from "../utils"; -import { Network, NetworkOptions } from "./network"; -import { NetworkNode } from "./node"; -import { GolemNetworkError, NetworkErrorCode } from "./error"; - -export type NetworkServiceOptions = Omit; - -/** - * Network Service - * @description Service used in {@link TaskExecutor} - * @internal - */ -export class NetworkService { - private network?: Network; - private logger: Logger; - - constructor( - private readonly yagnaApi: YagnaApi, - private readonly options?: NetworkServiceOptions, - ) { - this.logger = options?.logger || defaultLogger("network"); - } - - async run(networkOwnerId?: string) { - if (!networkOwnerId) { - const data = await this.yagnaApi.identity.getIdentity(); - networkOwnerId = data.identity; - } - this.network = await Network.create(this.yagnaApi, { ...this.options, networkOwnerId }); - this.logger.info("Network Service has started"); - } - - public async addNode(nodeId: string, ip?: string): Promise { - if (!this.network) - throw new GolemNetworkError( - "The service is not started and the network does not exist", - NetworkErrorCode.NetworkSetupMissing, - ); - return this.network.addNode(nodeId, ip); - } - - public async removeNode(nodeId: string): Promise { - if (!this.network) - throw new GolemNetworkError( - "The service is not started and the network does not exist", - NetworkErrorCode.ServiceNotInitialized, - ); - return this.network.removeNode(nodeId); - } - - public hasNode(nodeId: string) { - if (!this.network) - throw new GolemNetworkError( - "The service is not started and the network does not exist", - NetworkErrorCode.ServiceNotInitialized, - ); - return this.network.hasNode(nodeId); - } - - async end() { - await this.network?.remove(); - this.logger.info("Network Service has been stopped"); - } -} diff --git a/src/network/tcpProxy.ts b/src/network/tcpProxy.ts index 60d63f629..ae2bd70d9 100644 --- a/src/network/tcpProxy.ts +++ b/src/network/tcpProxy.ts @@ -1,9 +1,9 @@ import net from "net"; import { WebSocket } from "ws"; import { EventEmitter } from "eventemitter3"; -import { defaultLogger, Logger } from "../utils"; +import { defaultLogger, Logger } from "../shared/utils"; -interface TcpProxyEvents { +export interface TcpProxyEvents { /** Raised when the proxy encounters any sort of error */ error: (err: unknown) => void; } @@ -11,7 +11,7 @@ interface TcpProxyEvents { /** * Configuration required by the TcpProxy to work properly */ -interface TcpProxyOptions { +export interface TcpProxyOptions { /** * The logger instance to use for logging */ diff --git a/src/package/config.ts b/src/package/config.ts deleted file mode 100644 index 4103095c1..000000000 --- a/src/package/config.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Logger } from "../utils"; -import { PackageOptions } from "./package"; -import { GolemConfigError } from "../error/golem-error"; - -/** - * @internal - */ -export const DEFAULTS = Object.freeze({ - payment: { driver: "erc20", network: "holesky" }, - engine: "vm", - minMemGib: 0.5, - minStorageGib: 2, - minCpuThreads: 1, - minCpuCores: 1, - capabilities: [], -}); - -/** - * @internal - */ -export enum PackageFormat { - Unknown = "", - GVMKitSquash = "gvmkit-squash", -} - -/** - * @internal - */ - -// ? Isn't it just a merge of object literals and no need to have a class here -export class PackageConfig { - readonly packageFormat: string; - readonly imageHash?: string; - readonly imageTag?: string; - readonly engine: string; - readonly minMemGib: number; - readonly minStorageGib: number; - readonly minCpuThreads: number; - readonly minCpuCores: number; - readonly capabilities: string[]; - readonly manifest?: string; - readonly manifestSig?: string; - readonly manifestSigAlgorithm?: string; - readonly manifestCert?: string; - readonly logger?: Logger; - - constructor(options: PackageOptions) { - if (!options.imageHash && !options.manifest && !options.imageTag) { - throw new GolemConfigError("You must define a package or manifest option"); - } - - this.packageFormat = PackageFormat.GVMKitSquash; - this.imageHash = options.imageHash; - this.imageTag = options.imageTag; - this.engine = options.engine || DEFAULTS.engine; - this.minMemGib = options.minMemGib || DEFAULTS.minMemGib; - this.minStorageGib = options.minStorageGib || DEFAULTS.minStorageGib; - this.minCpuThreads = options.minCpuThreads || DEFAULTS.minCpuThreads; - this.minCpuCores = options.minCpuCores || DEFAULTS.minCpuCores; - this.capabilities = options.capabilities || DEFAULTS.capabilities; - this.manifest = options.manifest; - this.manifestSig = options.manifestSig; - this.manifestSigAlgorithm = options.manifestSigAlgorithm; - this.manifestCert = options.manifestCert; - this.logger = options.logger; - } -} diff --git a/src/package/index.ts b/src/package/index.ts deleted file mode 100644 index e6ade331d..000000000 --- a/src/package/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Package, PackageOptions, AllPackageOptions } from "./package"; diff --git a/src/package/package.ts b/src/package/package.ts deleted file mode 100644 index 903ffd1aa..000000000 --- a/src/package/package.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { ComparisonOperator, DecorationsBuilder, MarketDecoration } from "../market/builder"; -import { EnvUtils, Logger, defaultLogger } from "../utils"; -import { PackageConfig } from "./config"; -import { RequireAtLeastOne } from "../utils/types"; -import { GolemError, GolemPlatformError } from "../error/golem-error"; - -export type AllPackageOptions = { - /** Type of engine required: vm, emscripten, sgx, sgx-js, sgx-wasm, sgx-wasi */ - engine?: string; - /** Minimum required memory to execute application GB */ - minMemGib?: number; - /** Minimum required disk storage to execute tasks in GB */ - minStorageGib?: number; - /** Minimum required CPU threads */ - minCpuThreads?: number; - /** Minimum required CPU cores */ - minCpuCores?: number; - /** Required providers capabilities to run application */ - capabilities?: string[]; - /** finds package by its contents hash */ - imageHash?: string; - /** finds package by registry tag */ - imageTag?: string; - manifest?: string; - /** Signature of base64 encoded Computation Payload Manifest **/ - manifestSig?: string; - /** Algorithm of manifest signature, e.g. "sha256" **/ - manifestSigAlgorithm?: string; - /** Certificate - base64 encoded public certificate (DER or PEM) matching key used to generate signature **/ - manifestCert?: string; - logger?: Logger; -}; - -export type PackageOptions = RequireAtLeastOne; - -export interface PackageDetails { - minMemGib: number; - minStorageGib: number; - minCpuThreads: number; - minCpuCores: number; - engine: string; - capabilities: string[]; - imageHash?: string; -} - -/** - * Package module - an object for descriptions of the payload required by the requestor. - */ -export class Package { - private logger: Logger; - - private constructor(private options: PackageConfig) { - this.logger = options.logger || defaultLogger("work"); - } - - static create(options: PackageOptions): Package { - // ? : Dependency Injection could be useful - const config = new PackageConfig(options); - return new Package(config); - } - - static getImageIdentifier( - str: string, - ): RequireAtLeastOne<{ imageHash: string; imageTag: string }, "imageHash" | "imageTag"> { - const tagRegex = /^(.*?)\/(.*?):(.*)$/; - if (tagRegex.test(str)) { - return { - imageTag: str, - }; - } - - return { - imageHash: str, - }; - } - - async getDemandDecoration(): Promise { - const builder = new DecorationsBuilder(); - builder - .addProperty("golem.srv.comp.vm.package_format", this.options.packageFormat) - .addConstraint("golem.inf.mem.gib", this.options.minMemGib.toString(), ComparisonOperator.GtEq) - .addConstraint("golem.inf.storage.gib", this.options.minStorageGib.toString(), ComparisonOperator.GtEq) - .addConstraint("golem.runtime.name", this.options.engine) - .addConstraint("golem.inf.cpu.cores", this.options.minCpuCores.toString(), ComparisonOperator.GtEq) - .addConstraint("golem.inf.cpu.threads", this.options.minCpuThreads.toString(), ComparisonOperator.GtEq); - if (this.options.imageHash || this.options.imageTag) { - const taskPackage = await this.resolveTaskPackageUrl(); - builder.addProperty("golem.srv.comp.task_package", taskPackage); - } - if (this.options.capabilities.length) - this.options.capabilities.forEach((cap) => builder.addConstraint("golem.runtime.capabilities", cap)); - this.addManifestDecorations(builder); - return builder.getDecorations(); - } - - private async resolveTaskPackageUrl(): Promise { - const repoUrl = EnvUtils.getRepoUrl(); - - //TODO : in future this should be passed probably through config - const isHttps = false; - - const isDev = EnvUtils.isDevMode(); - - let hash = this.options.imageHash; - const tag = this.options.imageTag; - - const url = `${repoUrl}/v1/image/info?${isDev ? "dev=true" : "count=true"}&${tag ? `tag=${tag}` : `hash=${hash}`}`; - - try { - const response = await fetch(url); - if (!response.ok) { - this.logger.error(`Unable to get image`, { url: tag || hash, from: repoUrl }); - throw new GolemPlatformError(`Unable to get image ${await response.text()}`); - } - - const data = await response.json(); - - const imageUrl = isHttps ? data.https : data.http; - hash = data.sha3; - - return `hash:sha3:${hash}:${imageUrl}`; - } catch (error) { - if (error instanceof GolemError) throw error; - this.logger.error(`Unable to get image`, { url: tag || hash, from: repoUrl }); - throw new GolemPlatformError(`Failed to fetch image: ${error}`); - } - } - - private addManifestDecorations(builder: DecorationsBuilder): void { - if (!this.options.manifest) return; - builder.addProperty("golem.srv.comp.payload", this.options.manifest); - if (this.options.manifestSig) builder.addProperty("golem.srv.comp.payload.sig", this.options.manifestSig); - if (this.options.manifestSigAlgorithm) - builder.addProperty("golem.srv.comp.payload.sig.algorithm", this.options.manifestSigAlgorithm); - if (this.options.manifestCert) builder.addProperty("golem.srv.comp.payload.cert", this.options.manifestCert); - } - - get details(): PackageDetails { - return { - minMemGib: this.options.minMemGib, - minStorageGib: this.options.minStorageGib, - minCpuThreads: this.options.minCpuThreads, - minCpuCores: this.options.minCpuCores, - engine: this.options.engine, - capabilities: this.options.capabilities, - imageHash: this.options.imageHash, - }; - } -} diff --git a/src/payment/BaseDocument.ts b/src/payment/BaseDocument.ts new file mode 100644 index 000000000..44f2c09fc --- /dev/null +++ b/src/payment/BaseDocument.ts @@ -0,0 +1,48 @@ +import { PaymentApi } from "ya-ts-client"; +import { ProviderInfo } from "../market/agreement"; + +export interface BaseModel { + issuerId: string; + recipientId: string; + payeeAddr: string; + payerAddr: string; + paymentPlatform: string; + agreementId: string; + paymentDueDate?: string; + status: PaymentApi.InvoiceDTO["status"]; +} + +/** + * Common properties and methods for payment related documents - Invoices and DebitNotes + */ +export abstract class BaseDocument { + public readonly recipientId: string; + public readonly payeeAddr: string; + public readonly requestorWalletAddress: string; + public readonly paymentPlatform: string; + public readonly agreementId: string; + public readonly paymentDueDate?: string; + + protected status: PaymentApi.InvoiceDTO["status"]; + + protected constructor( + public readonly id: string, + protected model: ModelType, + public readonly provider: ProviderInfo, + ) { + this.recipientId = model.recipientId; + this.payeeAddr = model.payeeAddr; + this.requestorWalletAddress = model.payerAddr; + this.paymentPlatform = model.paymentPlatform; + this.agreementId = model.agreementId; + this.paymentDueDate = model.paymentDueDate; + this.status = model.status; + } + + /** + * Tells what's the current status of the document + */ + public getStatus() { + return this.status; + } +} diff --git a/src/payment/InvoiceProcessor.ts b/src/payment/InvoiceProcessor.ts index 466a8e718..9e2baff8a 100644 --- a/src/payment/InvoiceProcessor.ts +++ b/src/payment/InvoiceProcessor.ts @@ -1,19 +1,18 @@ -import { Allocation, Invoice } from "ya-ts-client/dist/ya-payment"; -import { Yagna } from "../utils"; -import { YagnaApi, YagnaOptions } from "../utils/yagna/yagna"; -import { Decimal, Numeric } from "decimal.js-light"; +import { PaymentApi } from "ya-ts-client"; +import { YagnaApi } from "../shared/utils"; +import Decimal, { Numeric } from "decimal.js-light"; export type InvoiceAcceptResult = | { invoiceId: string; - allocation: Allocation; + allocation: PaymentApi.AllocationDTO; success: true; amount: string; dryRun: boolean; } | { invoiceId: string; - allocation: Allocation; + allocation: PaymentApi.AllocationDTO; success: false; amount: string; reason: unknown; @@ -27,29 +26,19 @@ export class InvoiceProcessor { /** * Use `InvoiceProcessor.create()` to create an instance of this class. */ - private constructor(private readonly api: YagnaApi) {} - - /** - * Creates an instance of `InvoiceProcessor` and connects to the Yagna API. - * @param options Options for the Yagna API. - */ - public static async create(options?: YagnaOptions): Promise { - const yagna = new Yagna(options); - await yagna.connect(); - const api = yagna.getApi(); - return new InvoiceProcessor(api); - } + public constructor(private readonly api: YagnaApi) {} /** * Collects invoices from the Yagna API until the limit is reached or there are no more invoices. - * @param after Only collect invoices that were created after this date. - * @param limit Maximum number of invoices to collect. - * @param statuses Only collect invoices with these statuses. - * @param providerIds Only collect invoices from these providers. - * @param minAmount Only collect invoices with an amount greater than or equal to this. - * @param maxAmount Only collect invoices with an amount less than or equal to this. - * @param providerWallets Only collect invoices from these provider wallets. - * @param paymentPlatforms Only collect invoices from these payment platforms. + * @param {Object} options - The parameters for collecting invoices. + * @param options.after Only collect invoices that were created after this date. + * @param options.limit Maximum number of invoices to collect. + * @param options.statuses Only collect invoices with these statuses. + * @param options.providerIds Only collect invoices from these providers. + * @param options.minAmount Only collect invoices with an amount greater than or equal to this. + * @param options.maxAmount Only collect invoices with an amount less than or equal to this. + * @param options.providerWallets Only collect invoices from these provider wallets. + * @param options.paymentPlatforms Only collect invoices from these payment platforms. * * @example * ```typescript @@ -88,7 +77,7 @@ export class InvoiceProcessor { // this is not very efficient, but it's the only way to get invoices sorted by timestamp // otherwise yagna returns the invoices in seemingly random order // FIXME: move to batched requests once yagna api supports it - const invoices = await this.api.payment.getInvoices(after?.toISOString()).then((response) => response.data); + const invoices = await this.api.payment.getInvoices(after?.toISOString()); const filteredInvoices = invoices.filter((invoice) => { if (statuses && !statuses.includes(invoice.status)) { return false; @@ -117,8 +106,8 @@ export class InvoiceProcessor { /** * Fetches a single invoice from the Yagna API. */ - async fetchSingleInvoice(invoiceId: string): Promise { - return this.api.payment.getInvoice(invoiceId).then((res) => res.data); + async fetchSingleInvoice(invoiceId: string): Promise { + return this.api.payment.getInvoice(invoiceId); } /** @@ -129,10 +118,10 @@ export class InvoiceProcessor { invoice, dryRun = false, }: { - invoice: Invoice; + invoice: PaymentApi.InvoiceDTO; dryRun?: boolean; }): Promise { - let allocation: Allocation = { + let allocation: PaymentApi.AllocationDTO = { totalAmount: invoice.amount, paymentPlatform: invoice.paymentPlatform, address: invoice.payerAddr, @@ -155,7 +144,7 @@ export class InvoiceProcessor { } try { - allocation = await this.api.payment.createAllocation(allocation).then((res) => res.data); + allocation = await this.api.payment.createAllocation(allocation); await this.api.payment.acceptInvoice(invoice.invoiceId, { allocationId: allocation.allocationId, @@ -194,7 +183,7 @@ export class InvoiceProcessor { invoices, dryRun = false, }: { - invoices: Invoice[]; + invoices: PaymentApi.InvoiceDTO[]; dryRun?: boolean; }): Promise { /** @@ -202,25 +191,19 @@ export class InvoiceProcessor { * So it's necessary to group invoices by payment platform and payer address * and create an allocation for each group. */ - const groupByPaymentPlatform = (invoiceDetails: Invoice[]) => { - return invoiceDetails.reduce( - (acc, curr) => { - acc[curr.paymentPlatform] = acc[curr.paymentPlatform] || []; - acc[curr.paymentPlatform].push(curr); - return acc; - }, - {} as Record, - ); + const groupByPaymentPlatform = (invoiceDetails: PaymentApi.InvoiceDTO[]) => { + return invoiceDetails.reduce>((acc, curr) => { + acc[curr.paymentPlatform] = acc[curr.paymentPlatform] || []; + acc[curr.paymentPlatform].push(curr); + return acc; + }, {}); }; - const groupByPayerAddress = (invoiceDetails: Invoice[]) => { - return invoiceDetails.reduce( - (acc, curr) => { - acc[curr.payerAddr] = acc[curr.payerAddr] || []; - acc[curr.payerAddr].push(curr); - return acc; - }, - {} as Record, - ); + const groupByPayerAddress = (invoiceDetails: PaymentApi.InvoiceDTO[]) => { + return invoiceDetails.reduce>((acc, curr) => { + acc[curr.payerAddr] = acc[curr.payerAddr] || []; + acc[curr.payerAddr].push(curr); + return acc; + }, {}); }; const results: InvoiceAcceptResult[] = []; @@ -230,7 +213,7 @@ export class InvoiceProcessor { const groupedByPayerAddress = groupByPayerAddress(invoices); for (const [payerAddress, invoices] of Object.entries(groupedByPayerAddress)) { const sum = invoices.reduce((acc, curr) => acc.plus(curr.amount), new Decimal(0)); - let allocation: Allocation = { + let allocation: PaymentApi.AllocationDTO = { totalAmount: sum.toFixed(18), paymentPlatform, address: payerAddress, @@ -242,7 +225,7 @@ export class InvoiceProcessor { allocationId: "", }; if (!dryRun) { - allocation = await this.api.payment.createAllocation(allocation).then((res) => res.data); + allocation = await this.api.payment.createAllocation(allocation); } for (const invoice of invoices) { if (dryRun) { diff --git a/src/payment/PayerDetails.ts b/src/payment/PayerDetails.ts new file mode 100644 index 000000000..af82ea7cf --- /dev/null +++ b/src/payment/PayerDetails.ts @@ -0,0 +1,13 @@ +export class PayerDetails { + constructor( + public readonly network: string, + public readonly driver: string, + public readonly address: string, + // eslint-disable-next-line @typescript-eslint/ban-types -- keep the autocomplete for "glm" and "tglm" but allow any string + public readonly token: "glm" | "tglm" | (string & {}), + ) {} + + getPaymentPlatform() { + return `${this.driver}-${this.network}-${this.token}`; + } +} diff --git a/src/payment/agreement_payment_process.spec.ts b/src/payment/agreement_payment_process.spec.ts index f4dbc87ba..9d6f70079 100644 --- a/src/payment/agreement_payment_process.spec.ts +++ b/src/payment/agreement_payment_process.spec.ts @@ -1,96 +1,105 @@ import { AgreementPaymentProcess } from "./agreement_payment_process"; -import { anything, instance, mock, objectContaining, reset, verify, when } from "@johanblumenberg/ts-mockito"; -import { Agreement } from "../agreement"; +import { anything, imock, instance, mock, reset, spy, verify, when } from "@johanblumenberg/ts-mockito"; +import { Agreement } from "../market/agreement"; import { Allocation } from "./allocation"; import { Invoice } from "./invoice"; -import { InvoiceStatus } from "ya-ts-client/dist/ya-payment"; -import { RejectionReason } from "./rejection"; import { DebitNote } from "./debit_note"; import { GolemPaymentError, PaymentErrorCode } from "./error"; -import { GolemUserError } from "../error/golem-error"; +import { GolemUserError } from "../shared/error/golem-error"; +import { Subject } from "rxjs"; +import { RejectionReason } from "./rejection"; +import { PaymentModule } from "./payment.module"; const agreementMock = mock(Agreement); const allocationMock = mock(Allocation); const invoiceMock = mock(Invoice); const debitNoteMock = mock(DebitNote); +const mockPaymentModule = imock(); + beforeEach(() => { reset(agreementMock); reset(allocationMock); reset(invoiceMock); reset(debitNoteMock); + reset(mockPaymentModule); + const testProviderInfo = { id: "test-provider-id", name: "test-provider-name", walletAddress: "0x1234", }; - when(agreementMock.getProviderInfo()).thenReturn(testProviderInfo); + + when(agreementMock.provider).thenReturn(testProviderInfo); when(invoiceMock.provider).thenReturn(testProviderInfo); + when(mockPaymentModule.observeInvoices()).thenReturn(new Subject()); + when(mockPaymentModule.observeDebitNotes()).thenReturn(new Subject()); }); describe("AgreementPaymentProcess", () => { describe("Accepting Invoices", () => { describe("Basic use cases", () => { it("accepts a invoice in RECEIVED state", async () => { - when(allocationMock.id).thenReturn("1000"); - when(invoiceMock.amount).thenReturn(0.123); - when(invoiceMock.amountPrecise).thenReturn("0.123"); - when(invoiceMock.getStatus()).thenResolve(InvoiceStatus.Received); + const allocation = instance(allocationMock); - const process = new AgreementPaymentProcess(instance(agreementMock), instance(allocationMock), { + when(invoiceMock.amount).thenReturn("0.123"); + when(invoiceMock.getStatus()).thenReturn("RECEIVED"); + + const process = new AgreementPaymentProcess(instance(agreementMock), allocation, instance(mockPaymentModule), { debitNoteFilter: () => true, invoiceFilter: () => true, }); - const success = await process.addInvoice(instance(invoiceMock)); + const invoice = instance(invoiceMock); + const success = await process.addInvoice(invoice); expect(success).toEqual(true); - verify(invoiceMock.accept("0.123", "1000")).called(); + verify(mockPaymentModule.acceptInvoice(invoice, allocation, "0.123")).called(); expect(process.isFinished()).toEqual(true); }); it("rejects invoice if it's ignored by the user defined invoice filter", async () => { - when(allocationMock.id).thenReturn("1000"); when(invoiceMock.id).thenReturn("invoice-id"); when(invoiceMock.agreementId).thenReturn("agreement-id"); - when(invoiceMock.amount).thenReturn(0.123); - when(invoiceMock.amountPrecise).thenReturn("0.123"); - when(invoiceMock.getStatus()).thenResolve(InvoiceStatus.Received); - - const process = new AgreementPaymentProcess(instance(agreementMock), instance(allocationMock), { - debitNoteFilter: () => true, - invoiceFilter: () => false, - }); + when(invoiceMock.amount).thenReturn("0.123"); + when(invoiceMock.getStatus()).thenReturn("RECEIVED"); + + const process = new AgreementPaymentProcess( + instance(agreementMock), + instance(allocationMock), + instance(mockPaymentModule), + { + debitNoteFilter: () => true, + invoiceFilter: () => false, + }, + ); - const success = await process.addInvoice(instance(invoiceMock)); + const invoice = instance(invoiceMock); + const success = await process.addInvoice(invoice); expect(success).toEqual(false); verify( - invoiceMock.reject( - objectContaining({ - rejectionReason: RejectionReason.RejectedByRequestorFilter, - message: "Invoice invoice-id for agreement agreement-id rejected by Invoice Filter", - totalAmountAccepted: "0", - }), + mockPaymentModule.rejectInvoice( + invoice, + "Invoice invoice-id for agreement agreement-id rejected by Invoice Filter", ), ).called(); expect(process.isFinished()).toEqual(true); }); it("throws an error when invoice in a state different than RECEIVED", async () => { - when(allocationMock.id).thenReturn("1000"); when(invoiceMock.id).thenReturn("invoice-id"); when(invoiceMock.agreementId).thenReturn("agreement-id"); - when(invoiceMock.amount).thenReturn(0.123); - when(invoiceMock.amountPrecise).thenReturn("0.123"); - when(invoiceMock.getStatus()).thenResolve(InvoiceStatus.Accepted); + when(invoiceMock.amount).thenReturn("0.123"); + when(invoiceMock.getStatus()).thenReturn("ACCEPTED"); const allocation = instance(allocationMock); - const process = new AgreementPaymentProcess(instance(agreementMock), allocation, { + const process = new AgreementPaymentProcess(instance(agreementMock), allocation, instance(mockPaymentModule), { debitNoteFilter: () => true, invoiceFilter: () => true, }); const invoice = instance(invoiceMock); + await expect(() => process.addInvoice(invoice)).rejects.toMatchError( new GolemPaymentError( "The invoice invoice-id for agreement agreement-id has status ACCEPTED, but we can accept only the ones with status RECEIVED", @@ -100,87 +109,39 @@ describe("AgreementPaymentProcess", () => { ), ); - verify(invoiceMock.accept("0.123", "1000")).never(); + verify(mockPaymentModule.acceptInvoice(invoice, allocation, "0.123")).never(); expect(process.isFinished()).toEqual(false); }); }); describe("Dealing with duplicates", () => { - it("accepts the duplicated invoice if accepting the previous one failed", async () => { - when(allocationMock.id).thenReturn("1000"); + it("doesn't accept the second invoice", async () => { const allocation = instance(allocationMock); - when(invoiceMock.amount).thenReturn(0.123); - when(invoiceMock.amountPrecise).thenReturn("0.123"); - when(invoiceMock.getStatus()).thenResolve(InvoiceStatus.Received); - when(invoiceMock.isSameAs(anything())).thenReturn(true); - - const process = new AgreementPaymentProcess(instance(agreementMock), allocation, { - debitNoteFilter: () => true, - invoiceFilter: () => true, - }); - - const invoice = instance(invoiceMock); - - // Simulate issue with accepting the first one - const issue = new Error("Failed to accept in yagna"); - when(invoiceMock.accept("0.123", "1000")) - .thenReject(issue) // On first call - .thenResolve(); // On second call - - await expect(() => process.addInvoice(invoice)).rejects.toThrow(issue); - - // Then simulate the duplicate coming again - const success = await process.addInvoice(invoice); - - expect(success).toEqual(true); - verify(invoiceMock.accept("0.123", "1000")).twice(); - expect(process.isFinished()).toEqual(true); - }); - - it("accepts the duplicate if the original invoice has not been already decided upon (still in RECEIVED state)", async () => { - when(allocationMock.id).thenReturn("1000"); - - when(invoiceMock.amount).thenReturn(0.123); - when(invoiceMock.amountPrecise).thenReturn("0.123"); - when(invoiceMock.getStatus()).thenResolve(InvoiceStatus.Received); - when(invoiceMock.isSameAs(anything())).thenReturn(true); - - const process = new AgreementPaymentProcess(instance(agreementMock), instance(allocationMock), { - debitNoteFilter: () => true, - invoiceFilter: () => true, - }); - - const invoice = instance(invoiceMock); - - when(invoiceMock.accept("0.123", "1000")).thenResolve(); - - const firstSuccess = await process.addInvoice(invoice); - const secondSuccess = await process.addInvoice(invoice); - - expect(firstSuccess).toEqual(true); - expect(secondSuccess).toEqual(true); - verify(invoiceMock.accept("0.123", "1000")).twice(); - expect(process.isFinished()).toEqual(true); - }); + when(invoiceMock.id).thenReturn("invoice-id"); + when(invoiceMock.amount).thenReturn("0.123"); + when(invoiceMock.getStatus()).thenReturn("RECEIVED"); - it("doesn't accept the same invoice twice if the previous one was already processed", async () => { - when(invoiceMock.getStatus()).thenResolve(InvoiceStatus.Received).thenResolve(InvoiceStatus.Accepted); - when(invoiceMock.isSameAs(anything())).thenReturn(true); + when(agreementMock.id).thenReturn("agreement-id"); - const process = new AgreementPaymentProcess(instance(agreementMock), instance(allocationMock), { + const process = new AgreementPaymentProcess(instance(agreementMock), allocation, instance(mockPaymentModule), { debitNoteFilter: () => true, invoiceFilter: () => true, }); - const invoice = instance(invoiceMock); - - const firstSuccess = await process.addInvoice(invoice); - const secondSuccess = await process.addInvoice(invoice); + const invoice1 = instance(invoiceMock); + const invoice2 = instance(invoiceMock); + const firstSuccess = await process.addInvoice(invoice1); expect(firstSuccess).toEqual(true); - expect(secondSuccess).toEqual(false); - expect(process.isFinished()).toEqual(true); + await expect(() => process.addInvoice(invoice2)).rejects.toMatchError( + new GolemPaymentError( + "Agreement agreement-id is already covered with an invoice: invoice-id", + PaymentErrorCode.AgreementAlreadyPaid, + allocation, + invoice1.provider, + ), + ); }); }); @@ -189,12 +150,12 @@ describe("AgreementPaymentProcess", () => { when(invoiceMock.id).thenReturn("invoice-id"); when(invoiceMock.agreementId).thenReturn("agreement-id"); when(agreementMock.id).thenReturn("agreement-id"); - when(invoiceMock.getStatus()).thenResolve(InvoiceStatus.Received); + when(invoiceMock.getStatus()).thenReturn("RECEIVED"); when(invoiceMock.isSameAs(anything())).thenReturn(false); const allocation = instance(allocationMock); const agreement = instance(agreementMock); - const process = new AgreementPaymentProcess(agreement, allocation, { + const process = new AgreementPaymentProcess(agreement, allocation, instance(mockPaymentModule), { debitNoteFilter: () => true, invoiceFilter: () => true, }); @@ -209,7 +170,7 @@ describe("AgreementPaymentProcess", () => { "Agreement agreement-id is already covered with an invoice: invoice-id", PaymentErrorCode.AgreementAlreadyPaid, allocation, - agreement.getProviderInfo(), + agreement.provider, ), ); expect(process.isFinished()).toEqual(true); @@ -218,12 +179,12 @@ describe("AgreementPaymentProcess", () => { when(invoiceMock.id).thenReturn("invoice-id"); when(invoiceMock.agreementId).thenReturn("agreement-id"); when(agreementMock.id).thenReturn("agreement-id"); - when(invoiceMock.getStatus()).thenResolve(InvoiceStatus.Received); + when(invoiceMock.getStatus()).thenReturn("RECEIVED"); when(invoiceMock.isSameAs(anything())).thenReturn(false); const allocation = instance(allocationMock); const agreement = instance(agreementMock); - const process = new AgreementPaymentProcess(agreement, allocation, { + const process = new AgreementPaymentProcess(agreement, allocation, instance(mockPaymentModule), { debitNoteFilter: () => true, invoiceFilter: () => { throw new Error("invoiceFilter error"); @@ -242,11 +203,11 @@ describe("AgreementPaymentProcess", () => { describe("Accepting DebitNotes", () => { describe("Basic use cases", () => { it("accepts a single debit note", async () => { - when(allocationMock.id).thenReturn("1000"); - when(debitNoteMock.totalAmountDue).thenReturn(0.123); - when(debitNoteMock.totalAmountDuePrecise).thenReturn("0.123"); + const allocation = instance(allocationMock); - const process = new AgreementPaymentProcess(instance(agreementMock), instance(allocationMock), { + when(debitNoteMock.totalAmountDue).thenReturn("0.123"); + + const process = new AgreementPaymentProcess(instance(agreementMock), allocation, instance(mockPaymentModule), { debitNoteFilter: () => true, invoiceFilter: () => true, }); @@ -256,21 +217,27 @@ describe("AgreementPaymentProcess", () => { const success = await process.addDebitNote(debitNote); expect(success).toEqual(true); - verify(debitNoteMock.accept("0.123", "1000")).called(); + verify(mockPaymentModule.acceptDebitNote(debitNote, allocation, "0.123")).called(); expect(process.isFinished()).toEqual(false); }); + // Reason: Debit note rejections are not implemented in yagna yet it("rejects debit note if it's ignored by the user defined debit note filter", async () => { - when(allocationMock.id).thenReturn("1000"); - when(debitNoteMock.totalAmountDue).thenReturn(0.123); - when(debitNoteMock.totalAmountDuePrecise).thenReturn("0.123"); + when(debitNoteMock.totalAmountDue).thenReturn("0.123"); + when(debitNoteMock.id).thenReturn("debit-note-id"); when(debitNoteMock.agreementId).thenReturn("agreement-id"); - const process = new AgreementPaymentProcess(instance(agreementMock), instance(allocationMock), { - debitNoteFilter: () => false, - invoiceFilter: () => true, - }); + const process = new AgreementPaymentProcess( + instance(agreementMock), + instance(allocationMock), + instance(mockPaymentModule), + { + debitNoteFilter: () => false, + invoiceFilter: () => true, + }, + ); + const processSpy = spy(process); const debitNote = instance(debitNoteMock); @@ -278,30 +245,30 @@ describe("AgreementPaymentProcess", () => { expect(success).toEqual(false); verify( - debitNoteMock.reject( - objectContaining({ - rejectionReason: RejectionReason.RejectedByRequestorFilter, - message: "DebitNote debit-note-id for agreement agreement-id rejected by DebitNote Filter", - totalAmountAccepted: "0", - }), + processSpy["rejectDebitNote"]( + debitNote, + RejectionReason.RejectedByRequestorFilter, + "DebitNote debit-note-id for agreement agreement-id rejected by DebitNote Filter", ), ).called(); expect(process.isFinished()).toEqual(false); }); + // Reason: Debit note rejections are not implemented in yagna yet it("rejects debit note if there is already an invoice for that process", async () => { - when(allocationMock.id).thenReturn("1000"); - when(invoiceMock.amount).thenReturn(0.123); - when(invoiceMock.amountPrecise).thenReturn("0.123"); - when(invoiceMock.getStatus()).thenResolve(InvoiceStatus.Received); - when(debitNoteMock.totalAmountDue).thenReturn(0.456); + const allocation = instance(allocationMock); + + when(invoiceMock.amount).thenReturn("0.123"); + when(invoiceMock.getStatus()).thenReturn("RECEIVED"); + when(debitNoteMock.totalAmountDue).thenReturn("0.456"); when(debitNoteMock.id).thenReturn("debit-note-id"); when(debitNoteMock.agreementId).thenReturn("agreement-id"); - const process = new AgreementPaymentProcess(instance(agreementMock), instance(allocationMock), { + const process = new AgreementPaymentProcess(instance(agreementMock), allocation, instance(mockPaymentModule), { debitNoteFilter: () => true, invoiceFilter: () => true, }); + const processSpy = spy(process); const invoice = instance(invoiceMock); const debitNote = instance(debitNoteMock); @@ -310,17 +277,14 @@ describe("AgreementPaymentProcess", () => { const debitNoteSuccess = await process.addDebitNote(debitNote); expect(invoiceSuccess).toEqual(true); - verify(invoiceMock.accept("0.123", "1000")).called(); + verify(mockPaymentModule.acceptInvoice(invoice, allocation, "0.123")).called(); expect(debitNoteSuccess).toEqual(false); verify( - debitNoteMock.reject( - objectContaining({ - rejectionReason: RejectionReason.AgreementFinalized, - message: - "DebitNote debit-note-id rejected because the agreement agreement-id is already covered with a final invoice that should be paid instead of the debit note", - totalAmountAccepted: "0", - }), + processSpy["rejectDebitNote"]( + debitNote, + RejectionReason.AgreementFinalized, + "DebitNote debit-note-id rejected because the agreement agreement-id is already covered with a final invoice that should be paid instead of the debit note", ), ).called(); expect(process.isFinished()).toEqual(true); @@ -328,42 +292,18 @@ describe("AgreementPaymentProcess", () => { }); describe("Dealing with duplicates", () => { - it("accepts the duplicated debit note if accepting the previous failed", async () => { - when(allocationMock.id).thenReturn("1000"); - when(debitNoteMock.totalAmountDue).thenReturn(0.123); - when(debitNoteMock.totalAmountDuePrecise).thenReturn("0.123"); - when(debitNoteMock.getStatus()).thenResolve(InvoiceStatus.Received); - - const process = new AgreementPaymentProcess(instance(agreementMock), instance(allocationMock), { - debitNoteFilter: () => true, - invoiceFilter: () => true, - }); - - const debitNote = instance(debitNoteMock); - - // Simulate issue with accepting the first one - const issue = new Error("Failed to accept in yagna"); - when(debitNoteMock.accept("0.123", "1000")) - .thenReject(issue) // On first call - .thenResolve(); // On second call - - await expect(() => process.addDebitNote(debitNote)).rejects.toThrow(issue); - - // Then simulate the duplicate coming again - const success = await process.addDebitNote(debitNote); - - expect(success).toEqual(true); - verify(debitNoteMock.accept("0.123", "1000")).twice(); - expect(process.isFinished()).toEqual(false); - }); - it("doesn't accept the same debit note twice if the previous one was already processed", async () => { - when(debitNoteMock.getStatus()).thenResolve(InvoiceStatus.Accepted); - - const process = new AgreementPaymentProcess(instance(agreementMock), instance(allocationMock), { - debitNoteFilter: () => true, - invoiceFilter: () => true, - }); + when(debitNoteMock.getStatus()).thenReturn("ACCEPTED"); + + const process = new AgreementPaymentProcess( + instance(agreementMock), + instance(allocationMock), + instance(mockPaymentModule), + { + debitNoteFilter: () => true, + invoiceFilter: () => true, + }, + ); const debitNote = instance(debitNoteMock); @@ -372,22 +312,26 @@ describe("AgreementPaymentProcess", () => { const secondSuccess = await process.addDebitNote(debitNote); expect(secondSuccess).toEqual(false); - verify(debitNoteMock.reject(anything())).never(); + verify(mockPaymentModule.rejectDebitNote(debitNote, anything())).never(); expect(process.isFinished()).toEqual(false); }); }); describe("Security", () => { it("throws an UserError in case of error in the debitNote filter", async () => { - when(allocationMock.id).thenReturn("1000"); - when(debitNoteMock.totalAmountDue).thenReturn(0.123); - when(debitNoteMock.getStatus()).thenResolve(InvoiceStatus.Received); - - const process = new AgreementPaymentProcess(instance(agreementMock), instance(allocationMock), { - debitNoteFilter: () => { - throw new Error("debitNoteFilter error"); + when(debitNoteMock.totalAmountDue).thenReturn("0.123"); + when(debitNoteMock.getStatus()).thenReturn("RECEIVED"); + + const process = new AgreementPaymentProcess( + instance(agreementMock), + instance(allocationMock), + instance(mockPaymentModule), + { + debitNoteFilter: () => { + throw new Error("debitNoteFilter error"); + }, + invoiceFilter: () => true, }, - invoiceFilter: () => true, - }); + ); const debitNote = instance(debitNoteMock); diff --git a/src/payment/agreement_payment_process.ts b/src/payment/agreement_payment_process.ts index 8395d0499..f91ae739c 100644 --- a/src/payment/agreement_payment_process.ts +++ b/src/payment/agreement_payment_process.ts @@ -1,17 +1,44 @@ -import { Agreement } from "../agreement"; +import { Agreement } from "../market"; import { Invoice } from "./invoice"; import { DebitNote } from "./debit_note"; import { RejectionReason } from "./rejection"; import { Allocation } from "./allocation"; -import { Logger, defaultLogger } from "../utils"; -import { DebitNoteFilter, InvoiceFilter } from "./service"; +import { defaultLogger, Logger } from "../shared/utils"; import AsyncLock from "async-lock"; -import { InvoiceStatus } from "ya-ts-client/dist/ya-payment"; import { GolemPaymentError, PaymentErrorCode } from "./error"; -import { GolemUserError } from "../error/golem-error"; +import { GolemUserError } from "../shared/error/golem-error"; +import { getMessageFromApiError } from "../shared/utils/apiErrorMessage"; +import { Demand } from "../market"; +import { filter } from "rxjs"; +import { PaymentModule } from "./payment.module"; + +export type DebitNoteFilter = ( + debitNote: DebitNote, + context: { + agreement: Agreement; + allocation: Allocation; + demand: Demand; + }, +) => Promise | boolean; + +export type InvoiceFilter = ( + invoice: Invoice, + context: { + agreement: Agreement; + allocation: Allocation; + demand: Demand; + }, +) => Promise | boolean; + +export interface PaymentProcessOptions { + invoiceFilter: InvoiceFilter; + debitNoteFilter: DebitNoteFilter; +} /** - * Process manager that controls the logic behind processing events related to an agreement which result with payments + * Process manager that controls the logic behind processing payments for an agreement (debit notes and invoices). + * The process is started automatically and ends when the final invoice is received. + * You can stop the process earlier by calling the `stop` method. You cannot restart the process after stopping it. */ export class AgreementPaymentProcess { private invoice: Invoice | null = null; @@ -23,19 +50,39 @@ export class AgreementPaymentProcess { * Example of a rule: you shouldn't accept a debit note if an invoice is already in place */ private lock: AsyncLock = new AsyncLock(); + private options: PaymentProcessOptions; public readonly logger: Logger; + private readonly cleanupSubscriptions: () => void; + constructor( public readonly agreement: Agreement, public readonly allocation: Allocation, - public readonly filters: { - invoiceFilter: InvoiceFilter; - debitNoteFilter: DebitNoteFilter; - }, + public readonly paymentModule: PaymentModule, + options?: Partial, logger?: Logger, ) { this.logger = logger || defaultLogger("payment"); + this.options = { + invoiceFilter: options?.invoiceFilter || (() => true), + debitNoteFilter: options?.debitNoteFilter || (() => true), + }; + + const invoiceSubscription = this.paymentModule + .observeInvoices() + .pipe(filter((invoice) => invoice.agreementId === this.agreement.id)) + .subscribe((invoice) => this.addInvoice(invoice)); + + const debitNoteSubscription = this.paymentModule + .observeDebitNotes() + .pipe(filter((debitNote) => debitNote.agreementId === this.agreement.id)) + .subscribe((debitNote) => this.addDebitNote(debitNote)); + + this.cleanupSubscriptions = () => { + invoiceSubscription.unsubscribe(); + debitNoteSubscription.unsubscribe(); + }; } /** @@ -91,7 +138,11 @@ export class AgreementPaymentProcess { let acceptedByFilter = false; try { - acceptedByFilter = await this.filters.debitNoteFilter(debitNote.dto); + acceptedByFilter = await this.options.debitNoteFilter(debitNote, { + agreement: this.agreement, + allocation: this.allocation, + demand: this.agreement.demand, + }); } catch (error) { throw new GolemUserError("An error occurred in the debit note filter", error); } @@ -106,72 +157,87 @@ export class AgreementPaymentProcess { return false; } - await debitNote.accept(debitNote.totalAmountDuePrecise, this.allocation.id); - this.logger.debug(`DebitNote accepted`, { - debitNoteId: debitNote.id, - agreementId: debitNote.agreementId, - }); - - return true; + try { + await this.paymentModule.acceptDebitNote(debitNote, this.allocation, debitNote.totalAmountDue); + this.logger.debug(`DebitNote accepted`, { + debitNoteId: debitNote.id, + agreementId: debitNote.agreementId, + }); + return true; + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Unable to accept debit note ${debitNote.id}. ${message}`, + PaymentErrorCode.DebitNoteAcceptanceFailed, + undefined, + debitNote.provider, + error, + ); + } } private async hasProcessedDebitNote(debitNote: DebitNote) { const status = await debitNote.getStatus(); - return status !== InvoiceStatus.Received; + return status !== "RECEIVED"; } private async rejectDebitNote(debitNote: DebitNote, rejectionReason: RejectionReason, rejectMessage: string) { - const reason = { - rejectionReason: rejectionReason, - totalAmountAccepted: "0", - message: rejectMessage, - }; - - await debitNote.reject(reason); + try { + await this.paymentModule.rejectDebitNote(debitNote, rejectMessage); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Unable to reject debit note ${debitNote.id}. ${message}`, + PaymentErrorCode.DebitNoteRejectionFailed, + undefined, + debitNote.provider, + error, + ); + } + } - this.logger.warn(`DebitNote rejected`, { reason: reason.message }); + private finalize(invoice: Invoice) { + this.invoice = invoice; + this.cleanupSubscriptions(); } private async applyInvoice(invoice: Invoice) { + this.logger.debug("Applying invoice for agreement", { + invoiceId: invoice.id, + agreementId: invoice.agreementId, + provider: invoice.provider, + }); + if (this.invoice) { - if (invoice.isSameAs(this.invoice)) { - const previousStatus = await this.invoice.getStatus(); - - if (previousStatus !== InvoiceStatus.Received) { - this.logger.warn(`Received duplicate of an already processed invoice , the new one will be ignored`, { - invoiceId: invoice.id, - agreementId: invoice.agreementId, - }); - return false; - } - } else { - // Protects from possible fraud: someone sends a second, different invoice for the same agreement - throw new GolemPaymentError( - `Agreement ${this.agreement.id} is already covered with an invoice: ${this.invoice.id}`, - PaymentErrorCode.AgreementAlreadyPaid, - this.allocation, - this.invoice.provider, - ); - } + // Protects from possible fraud: someone sends a second, different invoice for the same agreement + throw new GolemPaymentError( + `Agreement ${this.agreement.id} is already covered with an invoice: ${this.invoice.id}`, + PaymentErrorCode.AgreementAlreadyPaid, + this.allocation, + this.invoice.provider, + ); } - const status = await invoice.getStatus(); - if (status !== InvoiceStatus.Received) { + if (invoice.getStatus() !== "RECEIVED") { throw new GolemPaymentError( - `The invoice ${invoice.id} for agreement ${invoice.agreementId} has status ${status}, ` + - `but we can accept only the ones with status ${InvoiceStatus.Received}`, + `The invoice ${invoice.id} for agreement ${invoice.agreementId} has status ${invoice.getStatus()}, ` + + `but we can accept only the ones with status RECEIVED`, PaymentErrorCode.InvoiceAlreadyReceived, this.allocation, invoice.provider, ); } - this.invoice = invoice; + this.finalize(invoice); let acceptedByFilter = false; try { - acceptedByFilter = await this.filters.invoiceFilter(invoice.dto); + acceptedByFilter = await this.options.invoiceFilter(invoice, { + agreement: this.agreement, + allocation: this.allocation, + demand: this.agreement.demand, + }); } catch (error) { throw new GolemUserError("An error occurred in the invoice filter", error); } @@ -184,12 +250,18 @@ export class AgreementPaymentProcess { return false; } - await invoice.accept(invoice.amountPrecise, this.allocation.id); - this.logger.info(`Invoice has been accepted`, { - invoiceId: invoice.id, - agreementId: invoice.agreementId, - providerName: this.agreement.getProviderInfo().name, - }); + try { + await this.paymentModule.acceptInvoice(invoice, this.allocation, invoice.amount); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Unable to accept invoice ${invoice.id} ${message}`, + PaymentErrorCode.InvoiceAcceptanceFailed, + undefined, + invoice.provider, + error, + ); + } return true; } @@ -199,18 +271,30 @@ export class AgreementPaymentProcess { rejectionReason: RejectionReason.RejectedByRequestorFilter, message: string, ) { - const reason = { - rejectionReason: rejectionReason, - totalAmountAccepted: "0", - message: message, - }; - - await invoice.reject(reason); - - this.logger.warn(`Invoice rejected`, { reason: reason.message }); + try { + await this.paymentModule.rejectInvoice(invoice, message); + this.logger.warn(`Invoice rejected`, { reason: message }); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Unable to reject invoice ${invoice.id} ${message}`, + PaymentErrorCode.InvoiceRejectionFailed, + undefined, + invoice.provider, + error, + ); + } } private hasReceivedInvoice() { return this.invoice !== null; } + + public isStarted() { + return this.cleanupSubscriptions !== null; + } + + public stop(): void { + this.cleanupSubscriptions(); + } } diff --git a/src/payment/allocation.test.ts b/src/payment/allocation.test.ts new file mode 100644 index 000000000..3b73f6ca4 --- /dev/null +++ b/src/payment/allocation.test.ts @@ -0,0 +1,51 @@ +import { Allocation, GolemConfigError, YagnaApi } from "../index"; +import { anything, imock, instance, mock, reset, when } from "@johanblumenberg/ts-mockito"; +import { PaymentApi } from "ya-ts-client"; + +const mockYagna = mock(YagnaApi); +const mockPayment = mock(PaymentApi.RequestorService); +const mockAllocation = imock(); + +describe("Allocation", () => { + beforeEach(() => { + reset(mockYagna); + reset(mockPayment); + reset(mockAllocation); + + when(mockYagna.payment).thenReturn(instance(mockPayment)); + + when(mockPayment.createAllocation(anything())).thenResolve(instance(mockAllocation)); + }); + + describe("Creating", () => { + it("should create allocation", async () => { + const allocation = new Allocation({ + address: "0xSomeAddress", + paymentPlatform: "erc20-holesky-tglm", + allocationId: "allocation-id", + makeDeposit: false, + remainingAmount: "1.0", + spentAmount: "0.0", + timestamp: "2024-01-01T00:00:00.000Z", + totalAmount: "1.0", + }); + expect(allocation).toBeInstanceOf(Allocation); + }); + + it("should not create allocation with empty account parameters", () => { + expect( + () => + new Allocation({ + address: "", + paymentPlatform: "", + allocationId: "allocation-id", + makeDeposit: false, + remainingAmount: "1.0", + spentAmount: "0.0", + timestamp: "2024-01-01T00:00:00.000Z", + totalAmount: "1.0", + }), + ).toThrowError(new GolemConfigError("Account address and payment platform are required")); + }); + }); +}); diff --git a/src/payment/allocation.ts b/src/payment/allocation.ts index 281a81ab1..3dc185096 100644 --- a/src/payment/allocation.ts +++ b/src/payment/allocation.ts @@ -1,14 +1,7 @@ -import { Allocation as Model, MarketDecoration } from "ya-ts-client/dist/ya-payment"; -import { AllocationConfig, BasePaymentOptions } from "./config"; -import { Allocation as AllocationModel } from "ya-ts-client/dist/ya-payment/src/models/allocation"; -import { Events } from "../events"; -import { YagnaApi } from "../utils"; -import { GolemPaymentError, PaymentErrorCode } from "./error"; -import { GolemConfigError, GolemInternalError } from "../error/golem-error"; +import { PaymentApi } from "ya-ts-client"; +import { BasePaymentOptions } from "./config"; +import { GolemConfigError } from "../shared/error/golem-error"; -/** - * @hidden - */ export interface AllocationOptions extends BasePaymentOptions { account: { address: string; @@ -18,155 +11,46 @@ export interface AllocationOptions extends BasePaymentOptions { } /** - * Allocation module - an object represents a designated sum of money reserved for the purpose of making some particular payments. Allocations are currently purely virtual objects. An Allocation is connected to a payment account (wallet) specified by address and payment platform field. - * @hidden + * Represents a designated sum of money reserved for the purpose of making some particular payments. Allocations are currently purely virtual objects. An Allocation is connected to a payment account (wallet) specified by address and payment platform field. */ export class Allocation { /** Allocation ID */ public readonly id: string; + /** Timestamp of creation */ public readonly timestamp: string; + /** Timeout */ public readonly timeout?: string; + /** Address of requestor */ public readonly address: string; + /** Payment platform */ public readonly paymentPlatform: string; + /** Total allocation Amount */ public readonly totalAmount: string; - private spentAmount: string; - private remainingAmount: string; - /** - * Create allocation - * - * @param yagnaApi - {@link YagnaApi} - * @param options - {@link AllocationOptions} - */ - static async create(yagnaApi: YagnaApi, options: AllocationOptions): Promise { - try { - const config = new AllocationConfig(options); - const now = new Date(); - const model: AllocationModel = { - totalAmount: config.budget.toString(), - paymentPlatform: config.account.platform, - address: config.account.address, - timestamp: now.toISOString(), - timeout: new Date(+now + config.expirationSec * 1000).toISOString(), - makeDeposit: false, - remainingAmount: "", - spentAmount: "", - allocationId: "", - }; - const { data: newModel } = await yagnaApi.payment.createAllocation(model); - config.eventTarget?.dispatchEvent( - new Events.AllocationCreated({ - id: newModel.allocationId, - amount: parseFloat(newModel.totalAmount), - platform: newModel.paymentPlatform, - }), - ); - config.logger.debug( - `Allocation ${newModel.allocationId} has been created for address ${config.account.address} using payment platform ${config.account.platform}`, - ); - return new Allocation(yagnaApi, config, newModel); - } catch (error) { - throw new GolemPaymentError( - `Could not create new allocation. ${error.response?.data?.message || error.response?.data || error}`, - PaymentErrorCode.AllocationCreationFailed, - undefined, - undefined, - error, - ); - } - } + /** The amount that has been already spent */ + public readonly spentAmount: string; + + /** The amount left for spending */ + public readonly remainingAmount: string; - /** - * @param yagnaApi - {@link YagnaApi} - * @param options - {@link AllocationConfig} - * @param model - {@link Model} - * @hidden - */ - constructor( - private readonly yagnaApi: YagnaApi, - private readonly options: AllocationConfig, - model: Model, - ) { + constructor(private readonly model: PaymentApi.AllocationDTO) { this.id = model.allocationId; this.timeout = model.timeout; this.timestamp = model.timestamp; this.totalAmount = model.totalAmount; this.spentAmount = model.spentAmount; this.remainingAmount = model.remainingAmount; + if (!model.address || !model.paymentPlatform) { throw new GolemConfigError("Account address and payment platform are required"); } + this.address = model.address; this.paymentPlatform = model.paymentPlatform; } - - /** - * Returns remaining amount for allocation - * - * @return amount remaining - */ - async getRemainingAmount(): Promise { - await this.refresh(); - return this.remainingAmount; - } - - /** - * Returns already spent amount for allocation - * - * @return spent amount - */ - async getSpentAmount(): Promise { - await this.refresh(); - return this.spentAmount; - } - - /** - * Release allocation - */ - async release() { - try { - await this.yagnaApi.payment.releaseAllocation(this.id); - this.options.logger.debug(`Allocation ${this.id} has been released.`); - } catch (error) { - throw new GolemPaymentError( - `Could not release allocation. ${error.response?.data?.message || error}`, - PaymentErrorCode.AllocationReleaseFailed, - this, - undefined, - error, - ); - } - } - - /** - * Returns Market ya-ts-client decoration - * - * @return {@link MarketDecoration} - */ - async getDemandDecoration(): Promise { - try { - const { data: decoration } = await this.yagnaApi.payment.getDemandDecorations([this.id]); - return decoration; - } catch (error) { - throw new GolemInternalError( - `Unable to get demand decorations. ${error.response?.data?.message || error}`, - error, - ); - } - } - - private async refresh() { - try { - const { data } = await this.yagnaApi.payment.getAllocation(this.id); - this.remainingAmount = data.remainingAmount; - this.spentAmount = data.spentAmount; - } catch (error) { - throw new GolemInternalError(`Could not get allocation data. ${error.response?.data || error}`, error); - } - } } diff --git a/src/payment/api.ts b/src/payment/api.ts new file mode 100644 index 000000000..837d8a856 --- /dev/null +++ b/src/payment/api.ts @@ -0,0 +1,88 @@ +import { Subject } from "rxjs"; +import { Invoice } from "./invoice"; +import { DebitNote } from "./debit_note"; +import { Allocation } from "./allocation"; + +export type PaymentEvents = { + allocationCreated: (event: { allocation: Allocation }) => void; + errorCreatingAllocation: (event: { error: Error }) => void; + + allocationReleased: (event: { allocation: Allocation }) => void; + errorReleasingAllocation: (event: { allocation: Allocation; error: Error }) => void; + + allocationAmended: (event: { allocation: Allocation }) => void; + errorAmendingAllocation: (event: { allocation: Allocation; error: Error }) => void; + + invoiceReceived: (event: { invoice: Invoice }) => void; + debitNoteReceived: (event: { debitNote: DebitNote }) => void; + + invoiceAccepted: (event: { invoice: Invoice }) => void; + invoiceRejected: (event: { invoice: Invoice }) => void; + errorAcceptingInvoice: (event: { invoice: Invoice; error: Error }) => void; + errorRejectingInvoice: (event: { invoice: Invoice; error: Error }) => void; + + debitNoteAccepted: (event: { debitNote: DebitNote }) => void; + debitNoteRejected: (event: { debitNote: DebitNote }) => void; + errorAcceptingDebitNote: (event: { debitNote: DebitNote; error: Error }) => void; + errorRejectingDebitNote: (event: { debitNote: DebitNote; error: Error }) => void; +}; + +export interface IPaymentApi { + receivedInvoices$: Subject; + receivedDebitNotes$: Subject; + + /** Starts the reader logic */ + connect(): Promise; + + getInvoice(id: string): Promise; + + acceptInvoice(invoice: Invoice, allocation: Allocation, amount: string): Promise; + + rejectInvoice(invoice: Invoice, reason: string): Promise; + + getDebitNote(id: string): Promise; + + acceptDebitNote(debitNote: DebitNote, allocation: Allocation, amount: string): Promise; + + rejectDebitNote(debitNote: DebitNote, reason: string): Promise; + + getAllocation(id: string): Promise; + + createAllocation(params: CreateAllocationParams): Promise; + + releaseAllocation(allocation: Allocation): Promise; +} + +export type CreateAllocationParams = { + /** + * How much to allocate + */ + budget: number; + /** + * How long the allocation should be valid + */ + expirationSec: number; + /** + * Optionally override the payment platform to use for this allocation + */ + paymentPlatform?: string; + /** + * Optionally provide a deposit to be used for the allocation, instead of using funds from the yagna wallet. + * Deposit is a way to pay for the computation using someone else's funds. The other party has to + * call the `createDeposit` method on the `LockPayment` smart contract and provide the deposit ID. + * + * @deprecated NOT IMPLEMENTED BY YAGNA + * This is a feature that's not yet released in Yagna. + * The deprecation note will be removed once the feature will be supported by the network. + */ + deposit?: { + /** + * Address of the smart contract that holds the deposit. + */ + contract: string; + /** + * ID of the deposit, obtained by calling the `createDeposit` method on the smart contract. + */ + id: string; + }; +}; diff --git a/src/payment/config.ts b/src/payment/config.ts index d2f428d56..66e60ef8f 100644 --- a/src/payment/config.ts +++ b/src/payment/config.ts @@ -1,24 +1,4 @@ -import { AllocationOptions } from "./allocation"; -import { EnvUtils, Logger, defaultLogger, YagnaOptions } from "../utils"; -import { DebitNoteFilter, InvoiceFilter, PaymentOptions } from "./service"; -import { InvoiceOptions } from "./invoice"; -import { acceptAllDebitNotesFilter, acceptAllInvoicesFilter } from "./strategy"; -import { GolemConfigError } from "../error/golem-error"; - -const DEFAULTS = Object.freeze({ - payment: { network: "holesky", driver: "erc20" }, - budget: 1.0, - paymentTimeout: 1000 * 60, // 1 min - allocationExpirationSec: 60 * 60, // 60 min - invoiceReceiveTimeout: 1000 * 60 * 5, // 5 min - maxInvoiceEvents: 500, - maxDebitNotesEvents: 500, - invoiceFetchingInterval: 5_000, - debitNotesFetchingInterval: 5_000, - unsubscribeTimeoutMs: 10_000, - debitNoteFilter: acceptAllDebitNotesFilter(), - invoiceFilter: acceptAllInvoicesFilter(), -}); +import { Logger, YagnaOptions } from "../shared/utils"; export interface BasePaymentOptions { yagnaOptions?: YagnaOptions; @@ -28,80 +8,4 @@ export interface BasePaymentOptions { paymentRequestTimeout?: number; unsubscribeTimeoutMs?: number; logger?: Logger; - eventTarget?: EventTarget; -} -/** - * @internal - */ -abstract class BaseConfig { - public readonly paymentTimeout: number; - public readonly eventTarget?: EventTarget; - public readonly payment: { driver: string; network: string }; - public readonly options?: BasePaymentOptions; - public readonly logger: Logger; - - constructor(options?: BasePaymentOptions) { - this.options = options; - this.paymentTimeout = options?.paymentTimeout || DEFAULTS.paymentTimeout; - this.payment = { - driver: options?.payment?.driver || DEFAULTS.payment.driver, - network: options?.payment?.network || EnvUtils.getPaymentNetwork() || DEFAULTS.payment.network, - }; - this.logger = options?.logger || defaultLogger("payment"); - this.eventTarget = options?.eventTarget; - } -} -/** - * @internal - */ -export class PaymentConfig extends BaseConfig { - public readonly invoiceFetchingInterval: number; - public readonly debitNotesFetchingInterval: number; - public readonly maxInvoiceEvents: number; - public readonly maxDebitNotesEvents: number; - public readonly unsubscribeTimeoutMs: number; - public readonly debitNoteFilter: DebitNoteFilter; - public readonly invoiceFilter: InvoiceFilter; - - constructor(options?: PaymentOptions) { - super(options); - this.invoiceFetchingInterval = options?.invoiceFetchingInterval ?? DEFAULTS.invoiceFetchingInterval; - this.debitNotesFetchingInterval = options?.debitNotesFetchingInterval ?? DEFAULTS.debitNotesFetchingInterval; - this.maxInvoiceEvents = options?.maxInvoiceEvents ?? DEFAULTS.maxInvoiceEvents; - this.maxDebitNotesEvents = options?.maxDebitNotesEvents ?? DEFAULTS.maxDebitNotesEvents; - this.unsubscribeTimeoutMs = options?.unsubscribeTimeoutMs ?? DEFAULTS.unsubscribeTimeoutMs; - this.debitNoteFilter = options?.debitNotesFilter ?? DEFAULTS.debitNoteFilter; - this.invoiceFilter = options?.invoiceFilter ?? DEFAULTS.invoiceFilter; - } -} -/** - * @internal - */ -export class AllocationConfig extends BaseConfig { - public readonly budget: number; - public readonly payment: { driver: string; network: string }; - public readonly expirationSec: number; - public readonly account: { address: string; platform: string }; - - constructor(options?: AllocationOptions) { - super(options); - if (!options || !options?.account) { - throw new GolemConfigError("Account option is required"); - } - this.account = options.account; - this.budget = options?.budget || DEFAULTS.budget; - this.payment = { - driver: options?.payment?.driver || DEFAULTS.payment.driver, - network: options?.payment?.network || DEFAULTS.payment.network, - }; - this.expirationSec = options?.expirationSec || DEFAULTS.allocationExpirationSec; - } -} -/** - * @internal - */ -export class InvoiceConfig extends BaseConfig { - constructor(options?: InvoiceOptions) { - super(options); - } } diff --git a/src/payment/debit_note.spec.ts b/src/payment/debit_note.spec.ts index 42248aeb2..806f4de99 100644 --- a/src/payment/debit_note.spec.ts +++ b/src/payment/debit_note.spec.ts @@ -1,115 +1,75 @@ import { DebitNote } from "./debit_note"; -import { anything, imock, instance, mock, verify, when } from "@johanblumenberg/ts-mockito"; -import { YagnaApi } from "../utils"; -import { DebitNote as DebitNoteModel } from "ya-ts-client/dist/ya-payment/src/models"; -import { Agreement as AgreementModel } from "ya-ts-client/dist/ya-market/src/models"; -import { RequestorApi as PaymentRequestorApi } from "ya-ts-client/dist/ya-payment/api"; -import { RequestorApi as MarketRequestorApi } from "ya-ts-client/dist/ya-market/api"; -import { GolemPaymentError, PaymentErrorCode } from "./error"; -import { Decimal } from "decimal.js-light"; +import { imock, instance, mock, reset, when } from "@johanblumenberg/ts-mockito"; +import { YagnaApi } from "../shared/utils"; +import { MarketApi, PaymentApi } from "ya-ts-client"; +import Decimal from "decimal.js-light"; +import { ProviderInfo } from "../market/agreement"; -const yagnaApiMock = imock(); -const paymentApiMock = mock(PaymentRequestorApi); -const marketApiMock = mock(MarketRequestorApi); -when(yagnaApiMock.payment).thenReturn(instance(paymentApiMock)); -when(yagnaApiMock.market).thenReturn(instance(marketApiMock)); +const mockYagna = mock(YagnaApi); +const mockPayment = mock(PaymentApi.RequestorService); +const mockMarket = mock(MarketApi.RequestorService); +const mockDebitNote = imock(); +const mockAgreement = imock(); + +const dto: PaymentApi.DebitNoteDTO = { + activityId: "activity-id", + agreementId: "agreement-id", + debitNoteId: "debit-note-id", + issuerId: "provider-node-id", + payeeAddr: "0xRequestorWallet", + payerAddr: "0xProviderWallet", + paymentPlatform: "erc20-polygon-glm", + recipientId: "requestor-node-id", + status: "RECEIVED", + timestamp: "2024-01-01T00.00.000Z", + totalAmountDue: "1", +}; + +const TEST_PROVIDER_INFO: ProviderInfo = { + name: "provider-name", + id: "provider-id", + walletAddress: "0xProviderWallet", +}; -function creteAxiosResponseMock(data: T, status = 200) { - return { - data, - status, - statusText: "ok", - headers: [], - config: {}, - }; -} describe("Debit Notes", () => { + beforeEach(() => { + reset(mockYagna); + reset(mockPayment); + reset(mockMarket); + reset(mockDebitNote); + + when(mockYagna.payment).thenReturn(instance(mockPayment)); + when(mockYagna.market).thenReturn(instance(mockMarket)); + + when(mockDebitNote.debitNoteId).thenReturn("testId"); + when(mockDebitNote.payeeAddr).thenReturn("0x12345"); + when(mockDebitNote.issuerId).thenReturn("0x123"); + when(mockDebitNote.agreementId).thenReturn("agreementId"); + when(mockDebitNote.totalAmountDue).thenReturn("1"); + + when(mockAgreement.agreementId).thenReturn("agreementId"); + when(mockAgreement.offer).thenReturn({ + offerId: "offerId", + providerId: "providerId", + timestamp: new Date().toISOString(), + properties: { ["golem.node.id.name"]: "testProvider" }, + constraints: "", + }); + + when(mockPayment.getDebitNote("testId")).thenResolve(instance(mockDebitNote)); + + when(mockMarket.getAgreement("agreementId")).thenResolve(instance(mockAgreement)); + }); + describe("creating", () => { it("should crete debit note", async () => { - when(paymentApiMock.getDebitNote(anything())).thenResolve( - creteAxiosResponseMock({ - debitNoteId: "testId", - payeeAddr: "0x12345", - issuerId: "0x123", - } as DebitNoteModel), - ); - when(marketApiMock.getAgreement(anything())).thenResolve( - creteAxiosResponseMock({ - agreementId: "testId", - offer: { properties: { ["golem.node.id.name"]: "testProvider" } }, - } as AgreementModel), - ); - const debitNote = await DebitNote.create("testId", instance(yagnaApiMock)); - expect(debitNote).toBeDefined(); + const debitNote = new DebitNote(dto, TEST_PROVIDER_INFO); + expect(debitNote.id).toEqual(dto.debitNoteId); }); + it("should crete debit note with a big number amount", async () => { - when(paymentApiMock.getDebitNote(anything())).thenResolve( - creteAxiosResponseMock({ - debitNoteId: "testId", - payeeAddr: "0x12345", - issuerId: "0x123", - totalAmountDue: "0.009551938349900001", - } as DebitNoteModel), - ); - when(marketApiMock.getAgreement(anything())).thenResolve( - creteAxiosResponseMock({ - agreementId: "testId", - offer: { properties: { ["golem.node.id.name"]: "testProvider" } }, - } as AgreementModel), - ); - const debitNote = await DebitNote.create("testId", instance(yagnaApiMock)); - expect(new Decimal("0.009551938349900001").eq(new Decimal(debitNote.totalAmountDuePrecise))).toEqual(true); - }); - }); - describe("accepting", () => { - it("should accept debit note", async () => { - when(paymentApiMock.getDebitNote(anything())).thenResolve( - creteAxiosResponseMock({ - debitNoteId: "testId", - payeeAddr: "0x12345", - issuerId: "0x123", - } as DebitNoteModel), - ); - when(marketApiMock.getAgreement(anything())).thenResolve( - creteAxiosResponseMock({ - agreementId: "testId", - offer: { properties: { ["golem.node.id.name"]: "testProvider" } }, - } as AgreementModel), - ); - const debitNote = await DebitNote.create("testId", instance(yagnaApiMock)); - await debitNote.accept("1", "testId"); - verify(paymentApiMock.acceptDebitNote("testId", anything())).called(); - }); - it("should throw GolemPaymentError if debit note cannot be accepted", async () => { - when(paymentApiMock.getDebitNote(anything())).thenResolve( - creteAxiosResponseMock({ - debitNoteId: "testId", - payeeAddr: "0x12345", - issuerId: "0x123", - } as DebitNoteModel), - ); - when(marketApiMock.getAgreement(anything())).thenResolve( - creteAxiosResponseMock({ - agreementId: "testId", - offer: { properties: { ["golem.node.id.name"]: "testProvider" } }, - } as AgreementModel), - ); - const errorYagnaApiMock = new Error("test error"); - when(paymentApiMock.acceptDebitNote("testId", anything())).thenReject(errorYagnaApiMock); - const debitNote = await DebitNote.create("testId", instance(yagnaApiMock)); - await expect(debitNote.accept("1", "testId")).rejects.toMatchError( - new GolemPaymentError( - `Unable to accept debit note testId. ${errorYagnaApiMock}`, - PaymentErrorCode.DebitNoteAcceptanceFailed, - undefined, - { - id: "0x123", - name: "testProvider", - walletAddress: "0x12345", - }, - errorYagnaApiMock, - ), - ); + const debitNote = new DebitNote({ ...dto, totalAmountDue: "0.009551938349900001" }, TEST_PROVIDER_INFO); + expect(new Decimal("0.009551938349900001").eq(new Decimal(debitNote.totalAmountDue))).toEqual(true); }); }); }); diff --git a/src/payment/debit_note.ts b/src/payment/debit_note.ts index 1d7472c83..63f3f304d 100644 --- a/src/payment/debit_note.ts +++ b/src/payment/debit_note.ts @@ -1,164 +1,41 @@ -import { BasePaymentOptions, InvoiceConfig } from "./config"; -import { DebitNote as Model } from "ya-ts-client/dist/ya-payment/src/models"; -import { BaseNote } from "./invoice"; -import { Events } from "../events"; -import { Rejection } from "./rejection"; -import { YagnaApi } from "../utils"; -import { GolemPaymentError, PaymentErrorCode } from "./error"; -import { ProviderInfo } from "../agreement"; -import { ProposalProperties } from "../market/proposal"; +import { PaymentApi } from "ya-ts-client"; +import { ProviderInfo } from "../market/agreement"; +import { BaseDocument } from "./BaseDocument"; +import Decimal from "decimal.js-light"; -export type InvoiceOptions = BasePaymentOptions; - -export interface DebitNoteDTO { - id: string; - timestamp: string; - activityId: string; - agreementId: string; - /** @deprecated this field may store invalid values for big numbers. Use `totalAmountDuePrecise` instead **/ - totalAmountDue: number; - totalAmountDuePrecise: string; - usageCounterVector?: object; +export interface IDebitNoteRepository { + getById(id: string): Promise; } /** * A Debit Note is an artifact issued by the Provider to the Requestor, in the context of a specific Activity. It is a notification of Total Amount Due incurred by the Activity until the moment the Debit Note is issued. This is expected to be used as trigger for payment in upfront-payment or pay-as-you-go scenarios. NOTE: Only Debit Notes with non-null paymentDueDate are expected to trigger payments. NOTE: Debit Notes flag the current Total Amount Due, which is accumulated from the start of Activity. Debit Notes are expected to trigger payments, therefore payment amount for the newly received Debit Note is expected to be determined by difference of Total Payments for the Agreement vs Total Amount Due. - * @hidden */ -export class DebitNote extends BaseNote { +export class DebitNote extends BaseDocument { public readonly id: string; public readonly previousDebitNoteId?: string; public readonly timestamp: string; public readonly activityId: string; - /** - * @deprecated this field may store invalid values for big numbers. Use totalAmountDuePrecise instead - */ - public readonly totalAmountDue: number; - public readonly totalAmountDuePrecise: string; + public readonly totalAmountDue: string; public readonly usageCounterVector?: object; - /** - * Create Debit Note Model - * - * @param debitNoteId - debit note id - * @param yagnaApi - {@link YagnaApi} - * @param options - {@link InvoiceOptions} - */ - static async create(debitNoteId: string, yagnaApi: YagnaApi, options?: InvoiceOptions): Promise { - const config = new InvoiceConfig(options); - const { data: model } = await yagnaApi.payment.getDebitNote(debitNoteId); - const { data: agreement } = await yagnaApi.market.getAgreement(model.agreementId); - const providerInfo = { - id: model.issuerId, - walletAddress: model.payeeAddr, - name: (agreement.offer.properties as ProposalProperties)["golem.node.id.name"], - }; - return new DebitNote(model, providerInfo, yagnaApi, config); - } - /** * * @param model * @param providerInfo - * @param yagnaApi - * @param options - * @protected - * @hidden */ - protected constructor( - protected model: Model, + public constructor( + protected model: PaymentApi.DebitNoteDTO, providerInfo: ProviderInfo, - protected yagnaApi: YagnaApi, - protected options: InvoiceConfig, ) { - super(model, providerInfo, options); + super(model.debitNoteId, model, providerInfo); this.id = model.debitNoteId; this.timestamp = model.timestamp; this.activityId = model.activityId; - this.totalAmountDue = Number(model.totalAmountDue); - this.totalAmountDuePrecise = model.totalAmountDue; + this.totalAmountDue = model.totalAmountDue; this.usageCounterVector = model.usageCounterVector; } - get dto(): DebitNoteDTO { - return { - id: this.id, - timestamp: this.timestamp, - activityId: this.activityId, - agreementId: this.agreementId, - totalAmountDue: this.totalAmountDue, - totalAmountDuePrecise: this.totalAmountDuePrecise, - usageCounterVector: this.usageCounterVector, - }; - } - - /** - * Accept Debit Note - * - * @param totalAmountAccepted - * @param allocationId - */ - async accept(totalAmountAccepted: string, allocationId: string) { - try { - await this.yagnaApi.payment.acceptDebitNote(this.id, { - totalAmountAccepted, - allocationId, - }); - } catch (error) { - const reason = error?.response?.data?.message || error; - this.options.eventTarget?.dispatchEvent( - new Events.PaymentFailed({ id: this.id, agreementId: this.agreementId, reason }), - ); - throw new GolemPaymentError( - `Unable to accept debit note ${this.id}. ${reason}`, - PaymentErrorCode.DebitNoteAcceptanceFailed, - undefined, - this.provider, - error, - ); - } - this.options.eventTarget?.dispatchEvent( - new Events.DebitNoteAccepted({ - id: this.id, - agreementId: this.agreementId, - amount: Number(totalAmountAccepted), - amountPrecise: totalAmountAccepted, - provider: this.provider, - }), - ); - } - - public async getStatus() { - await this.refreshStatus(); - return this.status; - } - - /** - * Reject Debit Note - * - * @param rejection - {@link Rejection} - */ - async reject(rejection: Rejection) { - try { - // TODO: not implemented by yagna - 501 returned - // await this.yagnaApi.payment.rejectDebitNote(this.id, rejection); - } catch (error) { - throw new GolemPaymentError( - `Unable to reject debit note ${this.id}. ${error?.response?.data?.message || error}`, - PaymentErrorCode.DebitNoteRejectionFailed, - undefined, - this.provider, - error, - ); - } finally { - this.options.eventTarget?.dispatchEvent( - new Events.PaymentFailed({ id: this.id, agreementId: this.agreementId, reason: rejection.message }), - ); - } - } - - protected async refreshStatus() { - const { data: model } = await this.yagnaApi.payment.getDebitNote(this.id); - this.model = model; + public getPreciseAmount(): Decimal { + return new Decimal(this.totalAmountDue); } } diff --git a/src/payment/error.ts b/src/payment/error.ts index 3c413739f..17ec1d224 100644 --- a/src/payment/error.ts +++ b/src/payment/error.ts @@ -1,28 +1,40 @@ -import { GolemModuleError } from "../error/golem-error"; +import { GolemModuleError } from "../shared/error/golem-error"; import { Allocation } from "./allocation"; -import { ProviderInfo } from "../agreement"; +import { ProviderInfo } from "../market/agreement"; export enum PaymentErrorCode { - AllocationCreationFailed, - MissingAllocation, - PaymentProcessNotInitialized, - AllocationReleaseFailed, - InvoiceAcceptanceFailed, - DebitNoteAcceptanceFailed, - InvoiceRejectionFailed, - DebitNoteRejectionFailed, - PaymentStatusQueryFailed, - AgreementAlreadyPaid, - InvoiceAlreadyReceived, + AllocationCreationFailed = "AllocationCreationFailed", + MissingAllocation = "MissingAllocation", + PaymentProcessNotInitialized = "PaymentProcessNotInitialized", + AllocationReleaseFailed = "AllocationReleaseFailed", + InvoiceAcceptanceFailed = "InvoiceAcceptanceFailed", + DebitNoteAcceptanceFailed = "DebitNoteAcceptanceFailed", + InvoiceRejectionFailed = "InvoiceRejectionFailed", + DebitNoteRejectionFailed = "DebitNoteRejectionFailed", + CouldNotGetDebitNote = "CouldNotGetDebitNote", + CouldNotGetInvoice = "CouldNotGetInvoice", + PaymentStatusQueryFailed = "PaymentStatusQueryFailed", + AgreementAlreadyPaid = "AgreementAlreadyPaid", + InvoiceAlreadyReceived = "InvoiceAlreadyReceived", } export class GolemPaymentError extends GolemModuleError { + #allocation?: Allocation; + #provider?: ProviderInfo; constructor( message: string, public code: PaymentErrorCode, - public allocation?: Allocation, - public provider?: ProviderInfo, + allocation?: Allocation, + provider?: ProviderInfo, public previous?: Error, ) { super(message, code, previous); + this.#allocation = allocation; + this.#provider = provider; + } + public getAllocation(): Allocation | undefined { + return this.#allocation; + } + public getProvider(): ProviderInfo | undefined { + return this.#provider; } } diff --git a/src/payment/index.ts b/src/payment/index.ts index b5eeba7b7..16e8fd4f3 100644 --- a/src/payment/index.ts +++ b/src/payment/index.ts @@ -1,10 +1,10 @@ -export { PaymentService, PaymentOptions, PaymentServiceEvents } from "./service"; export { Invoice } from "./invoice"; export { DebitNote } from "./debit_note"; export { Allocation } from "./allocation"; -export { Payments, PAYMENT_EVENT_TYPE, InvoiceEvent, DebitNoteEvent } from "./payments"; export { Rejection, RejectionReason } from "./rejection"; export * as PaymentFilters from "./strategy"; export { GolemPaymentError, PaymentErrorCode } from "./error"; export { InvoiceProcessor, InvoiceAcceptResult } from "./InvoiceProcessor"; -export { PaymentConfig } from "./config"; +export * from "./payment.module"; +export * from "./api"; +export { InvoiceFilter, DebitNoteFilter } from "./agreement_payment_process"; diff --git a/src/payment/invoice.spec.ts b/src/payment/invoice.spec.ts index 26290f6ce..fc76fb856 100644 --- a/src/payment/invoice.spec.ts +++ b/src/payment/invoice.spec.ts @@ -1,41 +1,15 @@ import { Invoice } from "./invoice"; -import { anything, imock, instance, mock, when } from "@johanblumenberg/ts-mockito"; -import { YagnaApi } from "../utils"; -import { RequestorApi as PaymentRequestorApi } from "ya-ts-client/dist/ya-payment/src/api/requestor-api"; -import { RequestorApi as MarketRequestorApi } from "ya-ts-client/dist/ya-market/src/api/requestor-api"; -import { InvoiceStatus } from "ya-ts-client/dist/ya-payment/src/models"; -import { Agreement } from "ya-ts-client/dist/ya-market/src/models"; -import { GolemPaymentError, PaymentErrorCode } from "./error"; -import { Decimal } from "decimal.js-light"; +import { PaymentApi } from "ya-ts-client"; +import Decimal from "decimal.js-light"; -const mockYagnaApi = imock(); -const mockPaymentApi = mock(PaymentRequestorApi); -const mockMarketApi = mock(MarketRequestorApi); +const TEST_PROVIDER_INFO = { id: "provider-id", name: "provider-name", walletAddress: "0xTestWallet" }; +// Skipped as the tests will be migrated to respective service unit test after refactoring describe("Invoice", () => { - when(mockYagnaApi.market).thenReturn(instance(mockMarketApi)); - when(mockMarketApi.getAgreement("agreement-id")).thenResolve({ - config: {}, - headers: {}, - status: 200, - statusText: "OK", - data: { - agreementId: "agreement-id", - offer: { - properties: { - "golem.node.id.name": "provider-test", - }, - }, - } as Agreement, - }); describe("creating", () => { test("create invoice with a big number amount", async () => { - when(mockPaymentApi.getInvoice("invoiceId")).thenResolve({ - config: {}, - headers: {}, - status: 200, - statusText: "OK", - data: { + const invoice = new Invoice( + { invoiceId: "invoiceId", issuerId: "issuer-id", payeeAddr: "0xPAYEE", @@ -44,99 +18,43 @@ describe("Invoice", () => { paymentPlatform: "holesky", timestamp: "2023-01-01T00:00:00.000Z", agreementId: "agreement-id", - status: InvoiceStatus.Received, + status: "RECEIVED", amount: "0.009551938349900001", paymentDueDate: "2023-01-02T00:00:00.000Z", activityIds: ["activity-1"], }, - }); - when(mockYagnaApi.payment).thenReturn(instance(mockPaymentApi)); - const invoice = await Invoice.create("invoiceId", instance(mockYagnaApi)); - expect(new Decimal("0.009551938349900001").eq(new Decimal(invoice.amountPrecise))).toEqual(true); - }); - }); - describe("accepting", () => { - test("throw GolemPaymentError if invoice cannot be accepted", async () => { - when(mockPaymentApi.getInvoice("invoiceId")).thenResolve({ - config: {}, - headers: {}, - status: 200, - statusText: "OK", - data: { - invoiceId: "invoiceId", - issuerId: "issuer-id", - payeeAddr: "0xPAYEE", - payerAddr: "0xPAYER", - recipientId: "recipient-id", - paymentPlatform: "holesky", - timestamp: "2023-01-01T00:00:00.000Z", - agreementId: "agreement-id", - status: InvoiceStatus.Received, - amount: "10.00", - paymentDueDate: "2023-01-02T00:00:00.000Z", - activityIds: ["activity-1"], - }, - }); - const errorYagnaApiMock = new Error("test error"); - when(mockPaymentApi.acceptInvoice("invoiceId", anything())).thenReject(errorYagnaApiMock); - when(mockYagnaApi.payment).thenReturn(instance(mockPaymentApi)); - const invoice = await Invoice.create("invoiceId", instance(mockYagnaApi)); - await expect(invoice.accept("1", "testAllocationId")).rejects.toMatchError( - new GolemPaymentError( - `Unable to accept invoice invoiceId ${errorYagnaApiMock}`, - PaymentErrorCode.InvoiceAcceptanceFailed, - undefined, - { - id: "issuer-id", - name: "provider-test", - walletAddress: "0xPAYEE", - }, - errorYagnaApiMock, - ), + TEST_PROVIDER_INFO, ); + expect(new Decimal("0.009551938349900001").eq(new Decimal(invoice.amount))).toEqual(true); }); }); + describe("isSameAs", () => { test("returns true if the invoices share required properties", async () => { - when(mockYagnaApi.payment).thenReturn(instance(mockPaymentApi)); + const dto: PaymentApi.InvoiceDTO = { + invoiceId: "invoice-a", + issuerId: "issuer-id", + payeeAddr: "0xPAYEE", + payerAddr: "0xPAYER", + recipientId: "recipient-id", + paymentPlatform: "holesky", + timestamp: "2023-01-01T00:00:00.000Z", + agreementId: "agreement-id", + status: "RECEIVED", + amount: "10.00", + paymentDueDate: "2023-01-02T00:00:00.000Z", + activityIds: ["activity-1"], + }; - when(mockPaymentApi.getInvoice("invoice-a")).thenResolve({ - config: {}, - headers: {}, - status: 200, - statusText: "OK", - data: { - invoiceId: "invoice-a", - issuerId: "issuer-id", - payeeAddr: "0xPAYEE", - payerAddr: "0xPAYER", - recipientId: "recipient-id", - paymentPlatform: "holesky", - timestamp: "2023-01-01T00:00:00.000Z", - agreementId: "agreement-id", - status: InvoiceStatus.Received, - amount: "10.00", - paymentDueDate: "2023-01-02T00:00:00.000Z", - activityIds: ["activity-1"], - }, - }); - - const invoiceA = await Invoice.create("invoice-a", instance(mockYagnaApi)); - const invoiceB = await Invoice.create("invoice-a", instance(mockYagnaApi)); + const invoiceA = new Invoice(dto, TEST_PROVIDER_INFO); + const invoiceB = new Invoice(dto, TEST_PROVIDER_INFO); expect(invoiceA.isSameAs(invoiceB)).toEqual(true); }); test("returns false if the invoices don't share required properties", async () => { - when(mockYagnaApi.payment).thenReturn(instance(mockPaymentApi)); - when(mockYagnaApi.market).thenReturn(instance(mockMarketApi)); - - when(mockPaymentApi.getInvoice("invoice-a")).thenResolve({ - config: {}, - headers: {}, - status: 200, - statusText: "OK", - data: { + const invoiceA = new Invoice( + { invoiceId: "invoice-a", issuerId: "issuer-id", payeeAddr: "0xPAYEE", @@ -145,19 +63,16 @@ describe("Invoice", () => { paymentPlatform: "holesky", timestamp: "2023-01-01T00:00:00.000Z", agreementId: "agreement-id", - status: InvoiceStatus.Received, + status: "RECEIVED", amount: "10.00", paymentDueDate: "2023-01-02T00:00:00.000Z", activityIds: ["activity-1"], }, - }); + TEST_PROVIDER_INFO, + ); - when(mockPaymentApi.getInvoice("invoice-b")).thenResolve({ - config: {}, - headers: {}, - status: 200, - statusText: "OK", - data: { + const invoiceB = new Invoice( + { invoiceId: "invoice-b", issuerId: "issuer-id", payeeAddr: "0xPAYEE", @@ -166,15 +81,13 @@ describe("Invoice", () => { paymentPlatform: "holesky", timestamp: "2023-01-01T00:00:00.000Z", agreementId: "agreement-id", - status: InvoiceStatus.Received, + status: "RECEIVED", amount: "1000000000000000000000000000000.00", paymentDueDate: "2023-01-02T00:00:00.000Z", activityIds: ["activity-cheated"], }, - }); - - const invoiceA = await Invoice.create("invoice-a", instance(mockYagnaApi)); - const invoiceB = await Invoice.create("invoice-b", instance(mockYagnaApi)); + TEST_PROVIDER_INFO, + ); expect(invoiceA.isSameAs(invoiceB)).toEqual(false); }); diff --git a/src/payment/invoice.ts b/src/payment/invoice.ts index e693a7f6d..07c8dbb8f 100644 --- a/src/payment/invoice.ts +++ b/src/payment/invoice.ts @@ -1,235 +1,45 @@ -import { BasePaymentOptions, InvoiceConfig } from "./config"; -import { Invoice as Model, InvoiceStatus } from "ya-ts-client/dist/ya-payment/src/models"; -import { Events } from "../events"; -import { Rejection } from "./rejection"; -import { YagnaApi } from "../utils"; -import { GolemPaymentError, PaymentErrorCode } from "./error"; -import { ProviderInfo } from "../agreement"; -import { ProposalProperties } from "../market/proposal"; +import { BasePaymentOptions } from "./config"; +import { PaymentApi } from "ya-ts-client"; +import { ProviderInfo } from "../market/agreement"; +import { BaseDocument } from "./BaseDocument"; +import Decimal from "decimal.js-light"; export type InvoiceOptions = BasePaymentOptions; -export interface InvoiceDTO { - id: string; - timestamp: string; - activityIds?: string[]; - agreementId: string; - paymentDueDate?: string; - status: string; - requestorWalletAddress: string; - provider: ProviderInfo; - paymentPlatform: string; - /** @deprecated this field may store invalid values for big numbers. Use `amountPrecise` instead **/ - amount: number; - amountPrecise: string; -} - -/** - * @hidden - */ -export interface BaseModel { - issuerId: string; - recipientId: string; - payeeAddr: string; - payerAddr: string; - paymentPlatform: string; - agreementId: string; - paymentDueDate?: string; - status: InvoiceStatus; -} - -/** - * @hidden - */ -export abstract class BaseNote { - public abstract readonly id: string; - public readonly recipientId: string; - public readonly payeeAddr: string; - public readonly requestorWalletAddress: string; - public readonly paymentPlatform: string; - public readonly agreementId: string; - public readonly paymentDueDate?: string; - protected status: InvoiceStatus; - - protected constructor( - protected model: ModelType, - public readonly provider: ProviderInfo, - protected options: InvoiceConfig, - ) { - this.recipientId = model.recipientId; - this.payeeAddr = model.payeeAddr; - this.requestorWalletAddress = model.payerAddr; - this.paymentPlatform = model.paymentPlatform; - this.agreementId = model.agreementId; - this.paymentDueDate = model.paymentDueDate; - this.status = model.status; - } - protected async getStatus(): Promise { - try { - await this.refreshStatus(); - return this.model.status; - } catch (error) { - throw new GolemPaymentError( - `Unable to query payment status. ${error?.data?.message || error.toString()}`, - PaymentErrorCode.PaymentStatusQueryFailed, - undefined, - this.provider, - error, - ); - } - } - protected abstract accept(totalAmountAccepted: string, allocationId: string): Promise; - protected abstract reject(rejection: Rejection): Promise; - protected abstract refreshStatus(): Promise; +export interface IInvoiceRepository { + getById(id: string): Promise; } /** * An Invoice is an artifact issued by the Provider to the Requestor, in the context of a specific Agreement. It indicates the total Amount owed by the Requestor in this Agreement. No further Debit Notes shall be issued after the Invoice is issued. The issue of Invoice signals the Termination of the Agreement (if it hasn't been terminated already). No Activity execution is allowed after the Invoice is issued. - * @hidden */ -export class Invoice extends BaseNote { - /** Invoice ID */ - public readonly id: string; +export class Invoice extends BaseDocument { /** Activities IDs covered by this Invoice */ public readonly activityIds?: string[]; - /** - * @deprecated this field may store invalid values for big numbers. Use amountPrecise instead - * Amount in the invoice - */ - public readonly amount: number; - /** Amount in the invoice **/ - public readonly amountPrecise: string; + /** Amount in the invoice */ + public readonly amount: string; /** Invoice creation timestamp */ public readonly timestamp: string; /** Recipient ID */ public readonly recipientId: string; - /** - * Create invoice using invoice ID - * - * @param invoiceId - Invoice ID - * @param yagnaApi - {@link YagnaApi} - * @param options - {@link InvoiceOptions} - */ - static async create(invoiceId: string, yagnaApi: YagnaApi, options?: InvoiceOptions): Promise { - const config = new InvoiceConfig(options); - const { data: model } = await yagnaApi.payment.getInvoice(invoiceId); - const { data: agreement } = await yagnaApi.market.getAgreement(model.agreementId); - const providerInfo = { - id: model.issuerId, - walletAddress: model.payeeAddr, - name: (agreement.offer.properties as ProposalProperties)["golem.node.id.name"], - }; - return new Invoice(model, providerInfo, yagnaApi, config); - } - /** * @param model * @param providerInfo - * @param yagnaApi - * @param options - * @protected - * @hidden */ - protected constructor( - protected model: Model, + public constructor( + protected model: PaymentApi.InvoiceDTO, providerInfo: ProviderInfo, - protected yagnaApi: YagnaApi, - protected options: InvoiceConfig, ) { - super(model, providerInfo, options); - this.id = model.invoiceId; + super(model.invoiceId, model, providerInfo); this.activityIds = model.activityIds; - this.amount = Number(model.amount); - this.amountPrecise = model.amount; + this.amount = model.amount; this.timestamp = model.timestamp; this.recipientId = model.recipientId; } - get dto(): InvoiceDTO { - return { - id: this.id, - timestamp: this.timestamp, - activityIds: this.activityIds, - agreementId: this.agreementId, - paymentDueDate: this.paymentDueDate, - status: this.status, - requestorWalletAddress: this.requestorWalletAddress, - provider: this.provider, - paymentPlatform: this.paymentPlatform, - amount: this.amount, - amountPrecise: this.amountPrecise, - }; - } - - /** - * Get Invoice Status - * - * @return {@link InvoiceStatus} - */ - async getStatus(): Promise { - await this.refreshStatus(); - return this.status; - } - - /** - * Accept Invoice - * - * @param totalAmountAccepted - * @param allocationId - */ - async accept(totalAmountAccepted: string, allocationId: string) { - try { - await this.yagnaApi.payment.acceptInvoice(this.id, { - totalAmountAccepted: `${totalAmountAccepted}`, - allocationId, - }); - } catch (error) { - const reason = error?.response?.data?.message || error; - this.options.eventTarget?.dispatchEvent( - new Events.PaymentFailed({ id: this.id, agreementId: this.agreementId, reason }), - ); - throw new GolemPaymentError( - `Unable to accept invoice ${this.id} ${reason}`, - PaymentErrorCode.InvoiceAcceptanceFailed, - undefined, - this.provider, - error, - ); - } - this.options.eventTarget?.dispatchEvent( - new Events.PaymentAccepted({ - id: this.id, - agreementId: this.agreementId, - amount: this.amount, - amountPrecise: this.amountPrecise, - provider: this.provider, - }), - ); - } - - /** - * Reject Invoice - * - * @param rejection - {@link Rejection} - */ - async reject(rejection: Rejection) { - try { - // TODO: not implemented by yagna !!!! - // await this.yagnaApi.payment.rejectInvoice(this.id, rejection); - } catch (error) { - throw new GolemPaymentError( - `Unable to reject invoice ${this.id} ${error?.response?.data?.message || error}`, - PaymentErrorCode.InvoiceRejectionFailed, - undefined, - this.provider, - error, - ); - } finally { - this.options.eventTarget?.dispatchEvent( - new Events.PaymentFailed({ id: this.id, agreementId: this.agreementId, reason: rejection.message }), - ); - } + public getPreciseAmount(): Decimal { + return new Decimal(this.amount); } /** @@ -238,9 +48,4 @@ export class Invoice extends BaseNote { public isSameAs(invoice: Invoice) { return this.id === invoice.id && this.amount === invoice.amount && this.agreementId === invoice.agreementId; } - - protected async refreshStatus() { - const { data: model } = await this.yagnaApi.payment.getInvoice(this.id); - this.status = model.status; - } } diff --git a/src/payment/payment.module.ts b/src/payment/payment.module.ts new file mode 100644 index 000000000..190b2d568 --- /dev/null +++ b/src/payment/payment.module.ts @@ -0,0 +1,317 @@ +import { EventEmitter } from "eventemitter3"; +import { + Allocation, + CreateAllocationParams, + DebitNote, + Invoice, + InvoiceProcessor, + IPaymentApi, + PaymentEvents, +} from "./index"; +import { defaultLogger, YagnaApi } from "../shared/utils"; +import { Observable } from "rxjs"; +import { GolemServices } from "../golem-network"; +import { PayerDetails } from "./PayerDetails"; +import { AgreementPaymentProcess, PaymentProcessOptions } from "./agreement_payment_process"; +import { Agreement } from "../market"; +import * as EnvUtils from "../shared/utils/env"; +import { GolemInternalError } from "../shared/error/golem-error"; + +export interface PaymentModuleOptions { + /** + * Network used to facilitate the payment. + * (for example: "mainnet", "holesky") + * @default holesky + */ + network?: string; + /** + * Payment driver used to facilitate the payment. + * (for example: "erc20") + * @default erc20 + */ + // eslint-disable-next-line @typescript-eslint/ban-types -- keep the autocomplete for "erc20" but allow any string + driver?: "erc20" | (string & {}); + /** + * Token used to facilitate the payment. + * If unset, it will be inferred from the network. + * (for example: "glm", "tglm") + */ + // eslint-disable-next-line @typescript-eslint/ban-types -- keep the autocomplete for "glm" and "tglm" but allow any string + token?: "glm" | "tglm" | (string & {}); +} + +export interface PaymentModule { + events: EventEmitter; + + observeDebitNotes(): Observable; + + observeInvoices(): Observable; + + createAllocation(params: CreateAllocationParams): Promise; + + releaseAllocation(allocation: Allocation): Promise; + + amendAllocation(allocation: Allocation, params: CreateAllocationParams): Promise; + + getAllocation(id: string): Promise; + + acceptInvoice(invoice: Invoice, allocation: Allocation, amount: string): Promise; + + rejectInvoice(invoice: Invoice, reason: string): Promise; + + acceptDebitNote(debitNote: DebitNote, allocation: Allocation, amount: string): Promise; + + rejectDebitNote(debitNote: DebitNote, reason: string): Promise; + + createInvoiceProcessor(): InvoiceProcessor; + + createAgreementPaymentProcess( + agreement: Agreement, + allocation: Allocation, + options?: Partial, + ): AgreementPaymentProcess; + + /** + * Get the payment platform and wallet address of the payer. + */ + getPayerDetails(): Promise; +} + +const MAINNETS = Object.freeze(["mainnet", "polygon"]); + +export class PaymentModuleImpl implements PaymentModule { + events: EventEmitter = new EventEmitter(); + + private readonly yagnaApi: YagnaApi; + + private readonly paymentApi: IPaymentApi; + + private readonly logger = defaultLogger("payment"); + + private readonly options: Required = { + driver: "erc20", + network: EnvUtils.getPaymentNetwork(), + token: "tglm", + }; + + constructor(deps: GolemServices, options?: PaymentModuleOptions) { + const network = options?.network ?? this.options.network; + const driver = options?.driver ?? this.options.driver; + const token = options?.token ?? MAINNETS.includes(network) ? "glm" : "tglm"; + this.options = { network, driver, token }; + + this.logger = deps.logger; + this.yagnaApi = deps.yagna; + this.paymentApi = deps.paymentApi; + + this.startEmittingPaymentEvents(); + } + + private startEmittingPaymentEvents() { + this.paymentApi.receivedInvoices$.subscribe((invoice) => { + this.events.emit("invoiceReceived", { + invoice, + }); + }); + + this.paymentApi.receivedDebitNotes$.subscribe((debitNote) => { + this.events.emit("debitNoteReceived", { debitNote }); + }); + } + + private getPaymentPlatform(): string { + return `${this.options.driver}-${this.options.network}-${this.options.token}`; + } + + async getPayerDetails(): Promise { + const { identity: address } = await this.yagnaApi.identity.getIdentity(); + + return new PayerDetails(this.options.network, this.options.driver, address, this.options.token); + } + + observeDebitNotes(): Observable { + return this.paymentApi.receivedDebitNotes$; + } + + observeInvoices(): Observable { + return this.paymentApi.receivedInvoices$; + } + + async createAllocation(params: CreateAllocationParams): Promise { + this.logger.debug("Creating allocation", { params: params }); + + try { + const allocation = await this.paymentApi.createAllocation({ + paymentPlatform: this.getPaymentPlatform(), + ...params, + }); + this.events.emit("allocationCreated", { allocation }); + this.logger.info("Created allocation", { + allocationId: allocation.id, + budget: allocation.totalAmount, + platform: this.getPaymentPlatform(), + }); + this.logger.debug("Created allocation", allocation); + return allocation; + } catch (error) { + this.events.emit("errorCreatingAllocation", error); + throw error; + } + } + + async releaseAllocation(allocation: Allocation): Promise { + this.logger.debug("Releasing allocation", allocation); + try { + const lastKnownAllocationState = await this.getAllocation(allocation.id).catch(() => { + this.logger.warn("Failed to fetch allocation before releasing", { id: allocation.id }); + return allocation; + }); + await this.paymentApi.releaseAllocation(allocation); + this.events.emit("allocationReleased", { + allocation: lastKnownAllocationState, + }); + this.logger.info("Released allocation", { + allocationId: lastKnownAllocationState.id, + totalAmount: lastKnownAllocationState.totalAmount, + spentAmount: lastKnownAllocationState.spentAmount, + }); + } catch (error) { + this.events.emit("errorReleasingAllocation", { + allocation: await this.paymentApi.getAllocation(allocation.id).catch(() => { + this.logger.warn("Failed to fetch allocation after failed release attempt", { id: allocation.id }); + return allocation; + }), + error, + }); + throw error; + } + } + + getAllocation(id: string): Promise { + this.logger.debug("Fetching allocation by id", { id }); + return this.paymentApi.getAllocation(id); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + amendAllocation(allocation: Allocation, _newOpts: CreateAllocationParams): Promise { + const err = Error("Amending allocation is not supported yet"); + + this.events.emit("errorAmendingAllocation", { + allocation, + error: err, + }); + + throw err; + } + + async acceptInvoice(invoice: Invoice, allocation: Allocation, amount: string): Promise { + this.logger.debug("Accepting invoice", invoice); + try { + const acceptedInvoice = await this.paymentApi.acceptInvoice(invoice, allocation, amount); + this.events.emit("invoiceAccepted", { + invoice: acceptedInvoice, + }); + this.logger.info("Accepted invoice", { + id: invoice.id, + allocationId: allocation.id, + agreementId: invoice.agreementId, + provider: invoice.provider, + amount, + }); + return acceptedInvoice; + } catch (error) { + this.events.emit("errorAcceptingInvoice", { invoice, error }); + this.logger.error(`Failed to accept invoice. ${error}`, { + id: invoice.id, + allocationId: allocation.id, + agreementId: invoice.agreementId, + provider: invoice.provider, + amount, + }); + throw error; + } + } + + async rejectInvoice(invoice: Invoice, reason: string): Promise { + this.logger.debug("Rejecting invoice", { id: invoice.id, reason }); + try { + const rejectedInvoice = await this.paymentApi.rejectInvoice(invoice, reason); + this.events.emit("invoiceRejected", { + invoice: rejectedInvoice, + }); + this.logger.warn("Rejeced invoice", { id: invoice.id, reason }); + return rejectedInvoice; + } catch (error) { + this.events.emit("errorRejectingInvoice", { invoice, error }); + this.logger.error(`Failed to reject invoice. ${error}`, { id: invoice.id, reason }); + throw error; + } + } + + async acceptDebitNote(debitNote: DebitNote, allocation: Allocation, amount: string): Promise { + this.logger.debug("Accepting debit note", debitNote); + try { + const acceptedDebitNote = await this.paymentApi.acceptDebitNote(debitNote, allocation, amount); + this.events.emit("debitNoteAccepted", { + debitNote: acceptedDebitNote, + }); + this.logger.debug("Accepted debit note", { + id: debitNote.id, + allocationId: allocation.id, + activityId: debitNote.activityId, + provider: debitNote.provider, + amount, + }); + return acceptedDebitNote; + } catch (error) { + this.events.emit("errorAcceptingDebitNote", { debitNote, error }); + this.logger.error(`Failed to accept debitNote. ${error}`, { + id: debitNote.id, + allocationId: allocation.id, + activityId: debitNote.activityId, + provider: debitNote.provider, + amount, + }); + throw error; + } + } + + async rejectDebitNote(debitNote: DebitNote, reason: string): Promise { + this.logger.info("Rejecting debit note", { id: debitNote.id, reason }); + // TODO: this is not supported by PaymnetAdapter + const message = "Unable to send debitNote rejection to provider. This feature is not yet supported."; + this.logger.warn(message); + this.events.emit("errorRejectingDebitNote", { debitNote, error: new GolemInternalError(message) }); + return debitNote; + // this.logger.debug("Rejecting debit note", { id: debitNote.id, reason }); + // try { + // const rejectedDebitNote = await this.paymentApi.rejectDebitNote(debitNote, reason); + // this.events.emit("debitNoteRejected", rejectedDebitNote); + // return rejectedDebitNote; + // } catch (error) { + // this.events.emit("errorRejectingDebitNote", debitNote, error); + // throw error; + // } + } + + /** + * Creates an instance of utility class InvoiceProcessor that deals with invoice related use-cases + */ + createInvoiceProcessor(): InvoiceProcessor { + return new InvoiceProcessor(this.yagnaApi); + } + + createAgreementPaymentProcess( + agreement: Agreement, + allocation: Allocation, + options?: Partial, + ): AgreementPaymentProcess { + return new AgreementPaymentProcess( + agreement, + allocation, + this, + options, + this.logger.child("agreement-payment-process"), + ); + } +} diff --git a/src/payment/payments.ts b/src/payment/payments.ts deleted file mode 100644 index 8079fb73a..000000000 --- a/src/payment/payments.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { BasePaymentOptions, PaymentConfig } from "./config"; -import { Logger, sleep, YagnaApi } from "../utils"; -import { Invoice } from "./invoice"; -import { DebitNote } from "./debit_note"; -import { Events } from "../events"; -import { GolemTimeoutError } from "../error/golem-error"; - -export interface PaymentOptions extends BasePaymentOptions { - invoiceFetchingInterval?: number; - debitNotesFetchingInterval?: number; - maxInvoiceEvents?: number; - maxDebitNotesEvents?: number; -} - -export const PAYMENT_EVENT_TYPE = "PaymentReceived"; -const UNSUBSCRIBED_EVENT = "Unsubscribed"; - -export class Payments extends EventTarget { - private isRunning = true; - private options: PaymentConfig; - private logger: Logger; - private lastInvoiceFetchingTime: string = new Date().toISOString(); - private lastDebitNotesFetchingTime: string = new Date().toISOString(); - static async create(yagnaApi: YagnaApi, options?: PaymentOptions) { - return new Payments(yagnaApi, new PaymentConfig(options)); - } - - constructor( - private readonly yagnaApi: YagnaApi, - options?: PaymentOptions, - ) { - super(); - this.options = new PaymentConfig(options); - this.logger = this.options.logger; - this.subscribe().catch((error) => this.logger.error(`Unable to subscribe to payments`, { error })); - } - - /** - * Unsubscribe from collecting payment events. - * An error will be thrown when the unsubscribe timeout expires. - */ - async unsubscribe() { - return new Promise((resolve, reject) => { - const timeoutId = setTimeout( - () => - reject( - new GolemTimeoutError( - `The waiting time (${this.options.unsubscribeTimeoutMs} ms) for unsubscribe payment has been exceeded.`, - ), - ), - this.options.unsubscribeTimeoutMs, - ); - this.addEventListener(UNSUBSCRIBED_EVENT, () => { - this.logger.debug(`Payments unsubscribed`); - clearTimeout(timeoutId); - resolve(true); - }); - this.isRunning = false; - }); - } - - private async subscribe() { - this.subscribeForInvoices().catch((error) => this.logger.error(`Unable to collect invoices.`, { error })); - this.subscribeForDebitNotes().catch((error) => this.logger.error(`Unable to collect debit notes.`, { error })); - } - - private async subscribeForInvoices() { - while (this.isRunning) { - try { - const { data: invoiceEvents } = await this.yagnaApi.payment.getInvoiceEvents( - this.options.invoiceFetchingInterval / 1000, - this.lastInvoiceFetchingTime, - this.options.maxInvoiceEvents, - this.yagnaApi.appSessionId, - { timeout: 0 }, - ); - for (const event of invoiceEvents) { - if (!this.isRunning) break; - if (event.eventType !== "InvoiceReceivedEvent") continue; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore FIXME: ya-ts-client does not provide invoiceId in the event even though it is in the API response - const invoiceId = event["invoiceId"]; - const invoice = await Invoice.create(invoiceId, this.yagnaApi, { ...this.options }).catch((error) => - this.logger.error(`Unable to create invoice`, { id: invoiceId, error }), - ); - if (!invoice) continue; - this.dispatchEvent(new InvoiceEvent(PAYMENT_EVENT_TYPE, invoice)); - this.lastInvoiceFetchingTime = event.eventDate; - this.options.eventTarget?.dispatchEvent( - new Events.InvoiceReceived({ - id: invoice.id, - agreementId: invoice.agreementId, - amount: invoice.amount, - amountPrecise: invoice.amountPrecise, - provider: invoice.provider, - }), - ); - this.logger.debug(`New Invoice received`, { - id: invoice.id, - agreementId: invoice.agreementId, - amount: invoice.amount, - }); - } - } catch (error) { - const reason = error.response?.data?.message || error.message || error; - this.logger.error(`Unable to get invoices.`, { reason }); - await sleep(2); - } - } - this.dispatchEvent(new Event(UNSUBSCRIBED_EVENT)); - } - - private async subscribeForDebitNotes() { - while (this.isRunning) { - try { - const { data: debitNotesEvents } = await this.yagnaApi.payment - .getDebitNoteEvents( - this.options.debitNotesFetchingInterval / 1000, - this.lastDebitNotesFetchingTime, - this.options.maxDebitNotesEvents, - this.yagnaApi.appSessionId, - { timeout: 0 }, - ) - .catch(() => ({ data: [] })); - - for (const event of debitNotesEvents) { - if (!this.isRunning) return; - if (event.eventType !== "DebitNoteReceivedEvent") continue; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore FIXME: ya-ts-client does not provide debitNoteId in the event even though it is in the API response - const debitNoteId = event["debitNoteId"]; - const debitNote = await DebitNote.create(debitNoteId, this.yagnaApi, { ...this.options }).catch((error) => - this.logger.error(`Unable to create debit note`, { id: debitNoteId, error }), - ); - if (!debitNote) continue; - this.dispatchEvent(new DebitNoteEvent(PAYMENT_EVENT_TYPE, debitNote)); - this.lastDebitNotesFetchingTime = event.eventDate; - this.options.eventTarget?.dispatchEvent( - new Events.DebitNoteReceived({ - id: debitNote.id, - agreementId: debitNote.agreementId, - activityId: debitNote.activityId, - amount: debitNote.totalAmountDue, - amountPrecise: debitNote.totalAmountDuePrecise, - provider: debitNote.provider, - }), - ); - this.logger.debug("New Debit Note received", { - agreementId: debitNote.agreementId, - amount: debitNote.totalAmountDue, - }); - } - } catch (error) { - const reason = error.response?.data?.message || error; - this.logger.error(`Unable to get debit notes.`, { reason }); - await sleep(2); - } - } - } -} - -/** - * @hidden - */ -export class InvoiceEvent extends Event { - readonly invoice: Invoice; - - /** - * Create a new instance of DemandEvent - * @param type A string with the name of the event: - * @param data object with invoice data: - */ - constructor(type: string, data: EventInit & Invoice) { - super(type, data); - this.invoice = data; - } -} - -/** - * @hidden - */ -export class DebitNoteEvent extends Event { - readonly debitNote: DebitNote; - - /** - * Create a new instance of DemandEvent - * @param type A string with the name of the event: - * @param data object with debit note data: - */ - constructor(type: string, data: EventInit & DebitNote) { - super(type, data); - this.debitNote = data; - } -} diff --git a/src/payment/rejection.ts b/src/payment/rejection.ts index cde5d18ed..ac74a28ab 100644 --- a/src/payment/rejection.ts +++ b/src/payment/rejection.ts @@ -1,6 +1,3 @@ -/** - * @hidden - */ export enum RejectionReason { UnsolicitedService = "UNSOLICITED_SERVICE", BadService = "BAD_SERVICE", @@ -15,9 +12,6 @@ export enum RejectionReason { AgreementFinalized = "AGREEMENT_FINALIZED", } -/** - * @hidden - */ export interface Rejection { rejectionReason: RejectionReason; totalAmountAccepted: string; diff --git a/src/payment/service.ts b/src/payment/service.ts index f263a1068..fe2e9dadb 100644 --- a/src/payment/service.ts +++ b/src/payment/service.ts @@ -1,23 +1,5 @@ -import { Logger, sleep, YagnaApi } from "../utils"; -import { Allocation, AllocationOptions } from "./allocation"; -import { BasePaymentOptions, PaymentConfig } from "./config"; -import { Invoice, InvoiceDTO } from "./invoice"; -import { DebitNote, DebitNoteDTO } from "./debit_note"; -import { DebitNoteEvent, InvoiceEvent, PAYMENT_EVENT_TYPE, Payments } from "./payments"; -import { Agreement } from "../agreement"; -import { AgreementPaymentProcess } from "./agreement_payment_process"; -import { GolemPaymentError, PaymentErrorCode } from "./error"; -import { EventEmitter } from "eventemitter3"; - -export interface PaymentServiceEvents { - /** - * Triggered when the service encounters an issue in an "asynchronous sub-process" (like accepting payments) - * that should be notified to the caller - * - * @param err The error raised during an asynchronous process executed by the PaymentService - */ - error: (err: Error) => void; -} +import { BasePaymentOptions } from "./config"; +import { DebitNoteFilter, InvoiceFilter } from "./agreement_payment_process"; export interface PaymentOptions extends BasePaymentOptions { /** Interval for checking new invoices */ @@ -33,209 +15,3 @@ export interface PaymentOptions extends BasePaymentOptions { /** A custom filter that checks every invoices coming from providers */ invoiceFilter?: InvoiceFilter; } - -export type DebitNoteFilter = (debitNote: DebitNoteDTO) => Promise | boolean; -export type InvoiceFilter = (invoice: InvoiceDTO) => Promise | boolean; - -/** - * Payment Service - * @description Service used in {@link TaskExecutor} - * @internal - */ -export class PaymentService { - public readonly config: PaymentConfig; - private isRunning = false; - private logger: Logger; - private allocation?: Allocation; - private processes: Map = new Map(); - private payments?: Payments; - - public events = new EventEmitter(); - - constructor( - private readonly yagnaApi: YagnaApi, - options?: PaymentOptions, - ) { - this.config = new PaymentConfig(options); - this.logger = this.config.logger; - } - - async run() { - this.isRunning = true; - this.payments = await Payments.create(this.yagnaApi, this.config.options); - this.payments.addEventListener(PAYMENT_EVENT_TYPE, this.subscribePayments.bind(this)); - this.logger.info("Payment Service has started"); - } - - async end() { - if (this.processes.size) { - this.logger.info(`Waiting for all agreement processes to be completed.`, { - numberOfProcesses: this.processes.size, - }); - let timeout = false; - const timeoutId = setTimeout(() => (timeout = true), this.config.paymentTimeout); - let i = 0; - while (this.isRunning && !timeout) { - const numberOfUnpaidAgreements = this.getNumberOfUnpaidAgreements(); - this.isRunning = numberOfUnpaidAgreements !== 0; - await sleep(2); - i++; - if (i > 10) { - this.logger.info(`Waiting for ${this.processes.size} agreement processes to be completed to be paid...`); - i = 0; - } - } - clearTimeout(timeoutId); - } - this.isRunning = false; - await this.payments - ?.unsubscribe() - .catch((error) => this.logger.warn("Unable to unsubscribe from payments", { error })); - this.payments?.removeEventListener(PAYMENT_EVENT_TYPE, this.subscribePayments.bind(this)); - await this.allocation?.release().catch((error) => this.logger.warn("Unable to release allocation", { error })); - this.logger.info("Allocation has been released"); - this.logger.info("Payment service has been stopped"); - } - - /** - * Create a new allocation that will be used to settle payments for activities - * - * @param options Additional options to apply on top of the ones provided in the constructor - */ - async createAllocation(options?: Partial): Promise { - try { - const account = { - platform: this.getPaymentPlatform(), - address: await this.getPaymentAddress(), - }; - this.allocation = await Allocation.create(this.yagnaApi, { ...this.config.options, account, ...options }); - return this.allocation; - } catch (error) { - if (error instanceof GolemPaymentError) { - throw error; - } - throw new GolemPaymentError( - `Unable to create allocation for driver/network ${this.config.payment.driver}/${this.config.payment.network}. ${error}`, - PaymentErrorCode.AllocationCreationFailed, - undefined, - undefined, - error, - ); - } - } - - acceptPayments(agreement: Agreement) { - this.logger.debug(`Starting to accept payments`, { agreementId: agreement.id }); - - if (this.processes.has(agreement.id)) { - this.logger.warn("Payment process has already been started for this agreement", { agreementId: agreement.id }); - return; - } - - if (!this.allocation) { - throw new GolemPaymentError( - "You need to create an allocation before starting any payment processes", - PaymentErrorCode.MissingAllocation, - undefined, - agreement.getProviderInfo(), - ); - } - - this.processes.set( - agreement.id, - new AgreementPaymentProcess( - agreement, - this.allocation, - { - invoiceFilter: this.config.invoiceFilter, - debitNoteFilter: this.config.debitNoteFilter, - }, - this.logger, - ), - ); - } - - private getNumberOfUnpaidAgreements() { - const inProgress = [...this.processes.values()].filter((p) => !p.isFinished()); - - return inProgress.length; - } - - private async processInvoice(invoice: Invoice) { - this.logger.debug(`Attempting to process Invoice event`, { - invoiceId: invoice.id, - agreementId: invoice.agreementId, - }); - const process = this.processes.get(invoice.agreementId); - - // This serves two purposes: - // 1. We will only process invoices which have a payment process started - // 2. Indirectly, we reject invoices from agreements that we didn't create (TODO: guard this business rule elsewhere) - if (!process) { - throw new GolemPaymentError( - "No payment process was initiated for this agreement - did you forget to use 'acceptPayments' or that's not your invoice?", - PaymentErrorCode.PaymentProcessNotInitialized, - this.allocation, - invoice.provider, - ); - } - - await process.addInvoice(invoice); - } - - private async processDebitNote(debitNote: DebitNote) { - this.logger.debug(`Attempting to process DebitNote event`, { - debitNoteId: debitNote.id, - agreementId: debitNote.agreementId, - }); - const process = this.processes.get(debitNote.agreementId); - - // This serves two purposes: - // 1. We will only process debit-notes which have a payment process started - // 2. Indirectly, we reject debit-notes from agreements that we didn't create (TODO: guard this business rule elsewhere) - if (!process) { - throw new GolemPaymentError( - "No payment process was initiated for this agreement - did you forget to use 'acceptPayments' or that's not your debit note?", - PaymentErrorCode.PaymentProcessNotInitialized, - this.allocation, - debitNote.provider, - ); - } - - await process.addDebitNote(debitNote); - } - - private async subscribePayments(event: Event) { - if (event instanceof InvoiceEvent) { - try { - await this.processInvoice(event.invoice); - this.logger.debug(`Invoice event processed`, { agreementId: event.invoice.agreementId }); - } catch (err) { - this.logger.error(`Failed to process InvoiceEvent`, { agreementId: event.invoice.agreementId, err }); - this.events.emit("error", err); - } - } - - if (event instanceof DebitNoteEvent) { - try { - await this.processDebitNote(event.debitNote); - this.logger.debug(`DebitNote event processed`, { agreementId: event.debitNote.agreementId }); - } catch (err) { - this.logger.error(`Failed to process DebitNoteEvent`, { agreementId: event.debitNote.agreementId, err }); - this.events.emit("error", err); - } - } - } - - private getPaymentPlatform() { - const mainnets = ["polygon", "mainnet"]; - const token = mainnets.includes(this.config.payment.network) ? "glm" : "tglm"; - - return `${this.config.payment.driver}-${this.config.payment.network}-${token}`; - } - - private async getPaymentAddress(): Promise { - const data = await this.yagnaApi.identity.getIdentity(); - return data.identity; - } -} diff --git a/src/payment/strategy.test.ts b/src/payment/strategy.test.ts new file mode 100644 index 000000000..6b1ff86c4 --- /dev/null +++ b/src/payment/strategy.test.ts @@ -0,0 +1,69 @@ +import { instance, mock, when } from "@johanblumenberg/ts-mockito"; +import { + acceptAllDebitNotesFilter, + acceptAllInvoicesFilter, + acceptMaxAmountDebitNoteFilter, + acceptMaxAmountInvoiceFilter, +} from "./strategy"; +import { DebitNote } from "./debit_note"; +import { Invoice } from "./invoice"; + +describe("SDK provided Payment Filters", () => { + describe("acceptAllDebitNotesFilter", () => { + test("Accepts all debit notes", () => { + const mockDebitNoteDto = mock(DebitNote); + const debitNotes = [instance(mockDebitNoteDto), instance(mockDebitNoteDto)]; + const accepted = debitNotes.filter(acceptAllDebitNotesFilter()); + expect(accepted.length).toEqual(2); + }); + }); + + describe("acceptAllInvoicesFilter", () => { + test("Accepts all invoices", () => { + const mockInvoiceDto = mock(Invoice); + const invoices = [instance(mockInvoiceDto), instance(mockInvoiceDto)]; + const accepted = invoices.filter(acceptAllInvoicesFilter()); + expect(accepted.length).toEqual(2); + }); + }); + + describe("acceptMaxAmountDebitNoteFilter", () => { + test("Accepts debit notes that don't exceed a specified amount", async () => { + const mockDebitNoteDto0 = mock(DebitNote); + when(mockDebitNoteDto0.totalAmountDue).thenReturn("100"); + const mockDebitNoteDto1 = mock(DebitNote); + when(mockDebitNoteDto1.totalAmountDue).thenReturn("200"); + const debitNotes = [instance(mockDebitNoteDto0), instance(mockDebitNoteDto1)]; + + const filter = acceptMaxAmountDebitNoteFilter(150); + const accepted: DebitNote[] = []; + for (const debitNote of debitNotes) { + if (await filter(debitNote)) { + accepted.push(debitNote); + } + } + expect(accepted.length).toEqual(1); + expect(accepted[0].totalAmountDue).toEqual("100"); + }); + }); + + describe("acceptMaxAmountInvoiceFilter", () => { + test("Accepts invoices that don't exceed a specified amount", async () => { + const mockInvoiceDto0 = mock(Invoice); + when(mockInvoiceDto0.amount).thenReturn("100"); + const mockInvoiceDto1 = mock(Invoice); + when(mockInvoiceDto1.amount).thenReturn("200"); + const invoices = [instance(mockInvoiceDto0), instance(mockInvoiceDto1)]; + + const filter = acceptMaxAmountInvoiceFilter(150); + const accepted: Invoice[] = []; + for (const invoice of invoices) { + if (await filter(invoice)) { + accepted.push(invoice); + } + } + expect(accepted.length).toEqual(1); + expect(accepted[0].amount).toEqual("100"); + }); + }); +}); diff --git a/src/payment/strategy.ts b/src/payment/strategy.ts index e0c3d366c..1472f447e 100644 --- a/src/payment/strategy.ts +++ b/src/payment/strategy.ts @@ -1,16 +1,17 @@ -import { DebitNoteDTO } from "./debit_note"; -import { InvoiceDTO } from "./invoice"; -import { Decimal } from "decimal.js-light"; +import { DebitNote } from "./debit_note"; +import { Invoice } from "./invoice"; +import Decimal from "decimal.js-light"; /** Default DebitNotes filter that accept all debit notes without any validation */ export const acceptAllDebitNotesFilter = () => async () => true; + /** Default Invoices filter that accept all invoices without any validation */ export const acceptAllInvoicesFilter = () => async () => true; /** A custom filter that only accepts debit notes below a given value */ -export const acceptMaxAmountDebitNoteFilter = (maxAmount: number) => async (debitNote: DebitNoteDTO) => - new Decimal(debitNote.totalAmountDuePrecise).lte(maxAmount); +export const acceptMaxAmountDebitNoteFilter = (maxAmount: number) => async (debitNote: DebitNote) => + new Decimal(debitNote.totalAmountDue).lte(maxAmount); /** A custom filter that only accepts invoices below a given value */ -export const acceptMaxAmountInvoiceFilter = (maxAmount: number) => async (invoice: InvoiceDTO) => - new Decimal(invoice.amountPrecise).lte(maxAmount); +export const acceptMaxAmountInvoiceFilter = (maxAmount: number) => async (invoice: Invoice) => + new Decimal(invoice.amount).lte(maxAmount); diff --git a/src/resource-rental/index.ts b/src/resource-rental/index.ts new file mode 100644 index 000000000..22e852149 --- /dev/null +++ b/src/resource-rental/index.ts @@ -0,0 +1,3 @@ +export * from "./resource-rental"; +export * from "./resource-rental-pool"; +export * from "./rental.module"; diff --git a/src/resource-rental/rental.module.ts b/src/resource-rental/rental.module.ts new file mode 100644 index 000000000..2c5758e82 --- /dev/null +++ b/src/resource-rental/rental.module.ts @@ -0,0 +1,88 @@ +import { ActivityModule } from "../activity"; +import { Agreement, DraftOfferProposalPool, MarketModule } from "../market"; +import { NetworkModule } from "../network"; +import { Allocation, PaymentModule } from "../payment"; +import { StorageProvider } from "../shared/storage"; +import { Logger } from "../shared/utils"; +import { ResourceRental, ResourceRentalOptions } from "./resource-rental"; +import { ResourceRentalPool, ResourceRentalPoolOptions } from "./resource-rental-pool"; +import { EventEmitter } from "eventemitter3"; + +export interface ResourceRentalModuleEvents { + /** Emitted when ResourceRenatl is successfully created */ + resourceRentalCreated: (agreement: Agreement) => void; + + /** Emitted when ResourceRenatlPool is successfully created */ + resourceRentalPoolCreated: () => void; +} + +export interface RentalModule { + events: EventEmitter; + /** + * Factory that creates a new resource rental that's fully configured. + * This method will also create the payment process for the agreement. + * + */ + createResourceRental(agreement: Agreement, allocation: Allocation, options?: ResourceRentalOptions): ResourceRental; + /** + * Factory that creates new resource rental pool that's fully configured + */ + createResourceRentalPool( + draftPool: DraftOfferProposalPool, + allocation: Allocation, + options: ResourceRentalPoolOptions, + ): ResourceRentalPool; +} + +export class RentalModuleImpl implements RentalModule { + events = new EventEmitter(); + constructor( + private readonly deps: { + marketModule: MarketModule; + paymentModule: PaymentModule; + activityModule: ActivityModule; + networkModule: NetworkModule; + storageProvider: StorageProvider; + logger: Logger; + }, + ) {} + + createResourceRental(agreement: Agreement, allocation: Allocation, options?: ResourceRentalOptions): ResourceRental { + const paymentProcess = this.deps.paymentModule.createAgreementPaymentProcess( + agreement, + allocation, + options?.payment, + ); + const rental = new ResourceRental( + agreement, + this.deps.storageProvider, + paymentProcess, + this.deps.marketModule, + this.deps.activityModule, + this.deps.logger.child("resource-rental"), + options, + ); + this.events.emit("resourceRentalCreated", rental.agreement); + return rental; + } + + public createResourceRentalPool( + draftPool: DraftOfferProposalPool, + allocation: Allocation, + options?: ResourceRentalPoolOptions, + ): ResourceRentalPool { + const pool = new ResourceRentalPool({ + allocation, + rentalModule: this, + marketModule: this.deps.marketModule, + networkModule: this.deps.networkModule, + proposalPool: draftPool, + resourceRentalOptions: options?.resourceRentalOptions, + logger: this.deps.logger.child("resource-rental-pool"), + network: options?.network, + poolSize: options?.poolSize, + }); + this.events.emit("resourceRentalPoolCreated"); + return pool; + } +} diff --git a/src/resource-rental/resource-rental-pool.test.ts b/src/resource-rental/resource-rental-pool.test.ts new file mode 100644 index 000000000..f400324e3 --- /dev/null +++ b/src/resource-rental/resource-rental-pool.test.ts @@ -0,0 +1,437 @@ +import { Agreement } from "../market/agreement/agreement"; +import { _, imock, instance, mock, reset, spy, verify, when } from "@johanblumenberg/ts-mockito"; +import { ResourceRental } from "./resource-rental"; +import { Allocation } from "../payment"; +import type { MarketModule } from "../market"; +import { DraftOfferProposalPool } from "../market"; +import { ResourceRentalPool } from "./resource-rental-pool"; +import { type RequireAtLeastOne } from "../shared/utils/types"; +import { NetworkModule } from "../network"; +import { RentalModule } from "./rental.module"; +import { Logger, sleep } from "../shared/utils"; + +const allocation = mock(Allocation); +const proposalPool = mock(DraftOfferProposalPool); +const marketModule = imock(); +const networkModule = imock(); +const rentalModule = imock(); + +function getMockResourceRental() { + return { + hasActivity: () => false, + fetchAgreementState: () => Promise.resolve("Approved"), + agreement: { id: "1" } as Agreement, + } as ResourceRental; +} + +function getRentalPool(poolSize: RequireAtLeastOne<{ min: number; max: number }>) { + return new ResourceRentalPool({ + allocation: instance(allocation), + proposalPool: instance(proposalPool), + marketModule: instance(marketModule), + networkModule: instance(networkModule), + rentalModule: instance(rentalModule), + logger: instance(imock()), + network: undefined, + poolSize, + }); +} + +beforeEach(() => { + jest.useRealTimers(); + jest.clearAllMocks(); + reset(allocation); + reset(proposalPool); + reset(marketModule); + reset(networkModule); + reset(rentalModule); +}); + +describe("ResourceRentalPool", () => { + describe("ready()", () => { + it("prepares MIN_POOL_SIZE resource rentals", async () => { + when(marketModule.signAgreementFromPool(_, _)).thenResolve({} as Agreement); + when(rentalModule.createResourceRental(_, _, _)).thenCall(() => ({}) as ResourceRental); + + const pool = getRentalPool({ min: 5, max: 10 }); + + await pool.ready(); + + expect(pool.getAvailableSize()).toBe(5); + verify(marketModule.signAgreementFromPool(_, _, _)).times(5); + }); + it("retries on error", async () => { + when(rentalModule.createResourceRental(_, _, _)).thenCall(() => ({}) as ResourceRental); + + const fakeAgreement = {} as Agreement; + when(marketModule.signAgreementFromPool(_, _, _)) + .thenResolve(fakeAgreement) + .thenReject(new Error("Failed to propose agreement")) + .thenResolve(fakeAgreement) + .thenReject(new Error("Failed to propose agreement")) + .thenResolve(fakeAgreement); + + const pool = getRentalPool({ min: 3 }); + + await pool.ready(); + + expect(pool.getAvailableSize()).toBe(3); + verify(marketModule.signAgreementFromPool(_, _, _)).times(5); + }); + it("stops retrying after abort signal is triggered", async () => { + const pool = getRentalPool({ min: 3 }); + const poolSpy = spy(pool); + // first call will succeed, the rest will fail (fall back to the first implementation) + when(poolSpy["createNewResourceRental"]()) + .thenCall(() => new Promise((resolve) => setTimeout(() => resolve(getMockResourceRental()), 10))) + .thenCall( + () => new Promise((_, reject) => setTimeout(() => reject(new Error("Failed to propose agreement")), 10)), + ); + await expect(pool.ready(AbortSignal.timeout(100))).rejects.toThrow( + "Could not create enough resource rentals to reach the minimum pool size in time", + ); + // at least the first iteration was finished (with 1 rental created) and the second one was started + verify(poolSpy["createNewResourceRental"](_)).atLeast(5); + }); + it("stops retrying after specified timeout is reached", async () => { + const pool = getRentalPool({ min: 3 }); + const poolSpy = spy(pool); + when(poolSpy["createNewResourceRental"]()) + .thenResolve(getMockResourceRental()) + .thenReject(new Error("Failed to propose agreement")); + + await expect(pool.ready(10)).rejects.toThrow( + "Could not create enough resource rentals to reach the minimum pool size in time", + ); + expect(pool.getAvailableSize()).toBe(1); + verify(poolSpy["createNewResourceRental"](_)).atLeast(3); + }); + }); + describe("acquire()", () => { + it("takes a random resource rental from the pool if none have activities", async () => { + const pool = getRentalPool({ min: 3 }); + const rental1 = getMockResourceRental(); + const rental2 = getMockResourceRental(); + const rental3 = getMockResourceRental(); + pool["lowPriority"].add(rental1); + pool["lowPriority"].add(rental2); + pool["lowPriority"].add(rental3); + + const resourceRental = await pool.acquire(); + expect(pool.getBorrowedSize()).toBe(1); + expect(pool.getAvailableSize()).toBe(2); + expect([rental1, rental2, rental3]).toContain(resourceRental); + }); + it("prioritizes resource rentals from high priority pool", async () => { + const pool = getRentalPool({ min: 3 }); + const rental1 = getMockResourceRental(); + const rental2 = getMockResourceRental(); + const rental3 = getMockResourceRental(); + pool["lowPriority"].add(rental1); + pool["highPriority"].add(rental2); + pool["lowPriority"].add(rental3); + + const resourceRental = await pool.acquire(); + expect(pool.getBorrowedSize()).toBe(1); + expect(pool.getAvailableSize()).toBe(2); + expect(resourceRental).toBe(rental2); + }); + it("creates a new resource rental if none are available", async () => { + const pool = getRentalPool({ min: 3 }); + pool["createNewResourceRental"] = jest.fn(() => Promise.resolve(getMockResourceRental())); + + expect(pool.getSize()).toBe(0); + await pool.acquire(); + expect(pool.getSize()).toBe(1); + expect(pool.getBorrowedSize()).toBe(1); + expect(pool.getAvailableSize()).toBe(0); + }); + it("waits for a rental to become available when the pool is full", async () => { + const pool = getRentalPool({ min: 3, max: 3 }); + const rental1 = getMockResourceRental(); + const rental2 = getMockResourceRental(); + const rental3 = getMockResourceRental(); + pool["lowPriority"].add(rental1); + pool["lowPriority"].add(rental2); + pool["lowPriority"].add(rental3); + + const acquiredRental1 = await pool.acquire(); + await pool.acquire(); + await pool.acquire(); + + expect(pool.getAvailableSize()).toBe(0); + expect(pool.getBorrowedSize()).toBe(3); + const acquiredRentalPromise = pool.acquire(); + // go to the next tick + await Promise.resolve(); + expect(pool["acquireQueue"].length).toBe(1); + pool.release(acquiredRental1); + await acquiredRentalPromise; + expect(pool.getAvailableSize()).toBe(0); + expect(pool.getBorrowedSize()).toBe(3); + expect(pool["acquireQueue"].length).toBe(0); + }); + it("validates the resource rental before returning it", async () => { + const pool = getRentalPool({ min: 3 }); + const newlyCreatedRental = getMockResourceRental(); + jest.spyOn(pool, "destroy"); + pool["createNewResourceRental"] = jest.fn(() => Promise.resolve(newlyCreatedRental)); + + const rental1 = getMockResourceRental(); + rental1.fetchAgreementState = jest.fn().mockResolvedValue("Expired"); + const rental2 = getMockResourceRental(); + rental2.fetchAgreementState = jest.fn().mockResolvedValue("Expired"); + pool["lowPriority"].add(rental1); + pool["lowPriority"].add(rental2); + + expect(pool.getBorrowedSize()).toBe(0); + expect(pool.getAvailableSize()).toBe(2); + const resourceRental = await pool.acquire(); + expect(pool.getBorrowedSize()).toBe(1); + expect(pool.getAvailableSize()).toBe(0); + expect(resourceRental).toBe(newlyCreatedRental); + expect(pool["destroy"]).toHaveBeenCalledWith(rental1); + expect(pool["destroy"]).toHaveBeenCalledWith(rental2); + }); + it("should not create more processes than allowed", async () => { + jest.useFakeTimers(); + const pool = getRentalPool({ min: 3, max: 3 }); + pool["createNewResourceRental"] = jest.fn(async () => { + pool["rentalsBeingSigned"]++; + await new Promise((resolve) => setTimeout(resolve, 50)); + pool["rentalsBeingSigned"]--; + return getMockResourceRental(); + }); + expect(pool.getSize()).toBe(0); + pool.acquire(); // should be resolved after 50ms + pool.acquire(); // should be resolved after 50ms + pool.acquire(); // should be resolved after 50ms + pool.acquire(); // should be added to the queue + pool.acquire(); // should be added to the queue + pool.acquire(); // should be added to the queue + pool.acquire(); // should be added to the queue + await jest.advanceTimersByTimeAsync(50); + expect(pool.getSize()).toBe(3); + expect(pool.getBorrowedSize()).toBe(3); + expect(pool.getAvailableSize()).toBe(0); + expect(pool["acquireQueue"].length).toBe(4); + }); + }); + describe("release()", () => { + it("releases a resource rental back to the pool", async () => { + const pool = getRentalPool({ min: 3 }); + const rental1 = getMockResourceRental(); + const rental2 = getMockResourceRental(); + pool["lowPriority"].add(rental1); + pool["lowPriority"].add(rental2); + + const resourceRental = await pool.acquire(); + expect(pool.getBorrowedSize()).toBe(1); + expect(pool.getAvailableSize()).toBe(1); + await pool.release(resourceRental); + expect(pool.getBorrowedSize()).toBe(0); + expect(pool.getAvailableSize()).toBe(2); + expect(pool["lowPriority"].has(rental1)).toBe(true); + expect(pool["lowPriority"].has(rental2)).toBe(true); + }); + it("releases a resource rental back to the high priority pool if it has an activity", async () => { + const pool = getRentalPool({ min: 3 }); + const rental1 = getMockResourceRental(); + const rental2 = getMockResourceRental(); + const rental3 = getMockResourceRental(); + pool["lowPriority"].add(rental1); + pool["lowPriority"].add(rental2); + pool["lowPriority"].add(rental3); + + const resourceRental = await pool.acquire(); + expect(pool.getBorrowedSize()).toBe(1); + expect(pool.getAvailableSize()).toBe(2); + resourceRental.hasActivity = () => true; + await pool.release(resourceRental); + expect(pool.getBorrowedSize()).toBe(0); + expect(pool.getAvailableSize()).toBe(3); + expect(pool["highPriority"].size).toBe(1); + expect(pool["lowPriority"].size).toBe(2); + }); + it("destroys the resource rental if the pool is full", async () => { + const pool = getRentalPool({ max: 2 }); + jest.spyOn(pool, "destroy"); + const rental1 = getMockResourceRental(); + const rental2 = getMockResourceRental(); + const rental3 = getMockResourceRental(); + + pool["lowPriority"].add(rental1); + pool["lowPriority"].add(rental2); + + const acquiredRental1 = await pool.acquire(); + expect(pool.getBorrowedSize()).toBe(1); + expect(pool.getAvailableSize()).toBe(1); + + pool["lowPriority"].add(rental3); + + await pool.release(acquiredRental1); + expect(pool.getBorrowedSize()).toBe(0); + expect(pool.getAvailableSize()).toBe(2); + expect(pool["lowPriority"].has(rental2)).toBe(true); + expect(pool["lowPriority"].has(rental3)).toBe(true); + expect(pool["destroy"]).toHaveBeenCalledWith(rental1); + }); + it("destroys the resource rental if it is invalid", async () => { + const pool = getRentalPool({ max: 1 }); + jest.spyOn(pool, "destroy"); + const rental1 = getMockResourceRental(); + + pool["lowPriority"].add(rental1); + + const acquiredRental1 = await pool.acquire(); + expect(pool.getBorrowedSize()).toBe(1); + expect(pool.getAvailableSize()).toBe(0); + + acquiredRental1.fetchAgreementState = jest.fn().mockResolvedValue("Expired"); + + await pool.release(acquiredRental1); + expect(pool.getBorrowedSize()).toBe(0); + expect(pool.getAvailableSize()).toBe(0); + expect(pool["destroy"]).toHaveBeenCalledWith(rental1); + }); + }); + describe("destroy()", () => { + it("removes the resource rental from the pool", async () => { + const pool = getRentalPool({ max: 1 }); + const rental1 = getMockResourceRental(); + pool["lowPriority"].add(rental1); + + const resourceRental = await pool.acquire(); + expect(pool.getBorrowedSize()).toBe(1); + expect(pool.getAvailableSize()).toBe(0); + pool.destroy(resourceRental); + expect(pool.getBorrowedSize()).toBe(0); + expect(pool.getAvailableSize()).toBe(0); + }); + }); + describe("drainAndClear", () => { + it("destroys all resource rentals in the pool", async () => { + const pool = getRentalPool({ max: 3 }); + jest.spyOn(pool, "destroy"); + const rental1 = getMockResourceRental(); + const rental2 = getMockResourceRental(); + const rental3 = getMockResourceRental(); + pool["lowPriority"].add(rental1); + pool["lowPriority"].add(rental2); + pool["lowPriority"].add(rental3); + + await pool.acquire(); + await pool.acquire(); + expect(pool.getBorrowedSize()).toBe(2); + expect(pool.getAvailableSize()).toBe(1); + await pool.drainAndClear(); + expect(pool.getBorrowedSize()).toBe(0); + expect(pool.getAvailableSize()).toBe(0); + expect(pool["destroy"]).toHaveBeenCalledWith(rental1); + expect(pool["destroy"]).toHaveBeenCalledWith(rental2); + expect(pool["destroy"]).toHaveBeenCalledWith(rental3); + }); + it("prevents new rentals from being acquired during the drain", async () => { + const pool = getRentalPool({ max: 3 }); + const realDestroy = pool.destroy; + jest.spyOn(pool, "destroy").mockImplementation(async (...args) => { + await new Promise((resolve) => setTimeout(resolve, 100)); + return realDestroy.apply(pool, args); + }); + const rental1 = getMockResourceRental(); + const rental2 = getMockResourceRental(); + const rental3 = getMockResourceRental(); + pool["lowPriority"].add(rental1); + pool["lowPriority"].add(rental2); + pool["lowPriority"].add(rental3); + + await pool.acquire(); + await pool.acquire(); + expect(pool.getBorrowedSize()).toBe(2); + expect(pool.getAvailableSize()).toBe(1); + const drainPromise = pool.drainAndClear(); + expect(pool.acquire()).rejects.toThrow("The pool is in draining mode"); + await drainPromise; + expect(pool.getBorrowedSize()).toBe(0); + expect(pool.getAvailableSize()).toBe(0); + expect(pool["destroy"]).toHaveBeenCalledWith(rental1); + expect(pool["destroy"]).toHaveBeenCalledWith(rental2); + expect(pool["destroy"]).toHaveBeenCalledWith(rental3); + }); + it("reuses the same promise if called multiple times", async () => { + const pool = getRentalPool({ max: 3 }); + const poolSpy = spy(pool); + when(poolSpy["startDrain"]()).thenResolve(); + + expect(pool["drainPromise"]).toBeUndefined(); + const drainPromise1 = pool.drainAndClear(); + const drainPromise2 = pool.drainAndClear(); + const drainPromise3 = pool.drainAndClear(); + expect(pool["drainPromise"]).toBeDefined(); + await Promise.all([drainPromise1, drainPromise2, drainPromise3]); + verify(poolSpy["startDrain"]()).once(); + expect(pool["drainPromise"]).toBeUndefined(); + }); + it("stops rentals that are in the process of being signed", async () => { + const pool = getRentalPool({ max: 3 }); + const mockAgreement = mock(Agreement); + const agreement = instance(mockAgreement); + + // simulate signing process taking a long time + when(marketModule.signAgreementFromPool(_, _, _)).thenCall((_1, _2, signal: AbortSignal) => { + return new Promise((resolve, reject) => { + signal.throwIfAborted(); + signal.addEventListener("abort", () => reject(signal.reason)); + setTimeout(() => resolve(agreement), 1000); + }); + }); + + expect.assertions(3); + const acquirePromise = pool + .acquire() + .then(() => { + throw new Error("Acquire resolved even though it should have been rejected"); + }) + .catch((error) => { + expect(error).toBe("The pool is in draining mode"); + }); + await pool.drainAndClear(); + await acquirePromise; + expect(pool.getSize()).toBe(0); + expect(pool["rentalsBeingSigned"]).toBe(0); + }); + it("destroy all rentals by drainAndClear that release process was previously started asynchronously", async () => { + const maxPoolSize = 4; + const pool = getRentalPool({ min: 1, max: maxPoolSize }); + const poolSpy = spy(pool); + pool["createNewResourceRental"] = jest.fn(async () => { + pool["rentalsBeingSigned"]++; + await sleep(10, true); + pool["rentalsBeingSigned"]--; + return getMockResourceRental(); + }); + // simulate slow validation to slow down the release process + pool["validate"] = jest.fn(async () => { + await sleep(100, true); + return true; + }); + // run 12 tasks simultaneously after which the release process will start, but without waiting for it to finish + await Promise.allSettled( + Array(12) + .fill(0) + .map(() => + pool.acquire().then(async (rental) => { + await sleep(100, true); + pool.release(rental); + }), + ), + ); + await pool.drainAndClear(); + expect(pool.getAvailableSize()).toEqual(0); + expect(pool.getBorrowedSize()).toEqual(0); + expect(pool.getSize()).toEqual(0); + verify(poolSpy.release(_)).times(12); + verify(poolSpy.destroy(_)).times(maxPoolSize); + }); + }); +}); diff --git a/src/resource-rental/resource-rental-pool.ts b/src/resource-rental/resource-rental-pool.ts new file mode 100644 index 000000000..87ca6c4b9 --- /dev/null +++ b/src/resource-rental/resource-rental-pool.ts @@ -0,0 +1,439 @@ +import type { Agreement, DraftOfferProposalPool, MarketModule } from "../market"; +import { GolemMarketError, MarketErrorCode } from "../market"; +import type { Logger } from "../shared/utils"; +import { anyAbortSignal, createAbortSignalFromTimeout, runOnNextEventLoopIteration } from "../shared/utils"; +import { EventEmitter } from "eventemitter3"; +import type { RequireAtLeastOne } from "../shared/utils/types"; +import type { Allocation } from "../payment"; +import type { ResourceRental, ResourceRentalOptions } from "./resource-rental"; +import { Network, NetworkModule } from "../network"; +import { RentalModule } from "./rental.module"; +import { AgreementOptions } from "../market/agreement/agreement"; +import { GolemAbortError } from "../shared/error/golem-error"; +import AsyncLock from "async-lock"; + +export interface ResourceRentalPoolDependencies { + allocation: Allocation; + proposalPool: DraftOfferProposalPool; + marketModule: MarketModule; + networkModule: NetworkModule; + rentalModule: RentalModule; + logger: Logger; +} + +export type PoolSize = number | RequireAtLeastOne<{ min: number; max: number }>; + +export interface ResourceRentalPoolOptions { + poolSize?: PoolSize; + network?: Network; + resourceRentalOptions?: ResourceRentalOptions; + agreementOptions?: AgreementOptions; +} + +export interface ResourceRentalPoolEvents { + /** Triggered when the pool has the minimal number of rentals prepared for operations */ + ready: () => void; + + /** Triggered when the pool is emptied from all rentals */ + end: () => void; + + acquired: (event: { agreement: Agreement }) => void; + released: (event: { agreement: Agreement }) => void; + + created: (event: { agreement: Agreement }) => void; + errorDestroyingRental: (event: { agreement: Agreement; error: GolemMarketError }) => void; + + destroyed: (event: { agreement: Agreement }) => void; + errorCreatingRental: (event: { error: GolemMarketError }) => void; + + /** Triggered when the pool enters the "draining" state */ + draining: () => void; +} + +const MAX_POOL_SIZE = 100; + +/** + * Pool of resource rentals that can be borrowed, released or destroyed. + */ +export class ResourceRentalPool { + public readonly events = new EventEmitter(); + + /** + * Pool of resource rentals that do not have an activity + */ + private lowPriority = new Set(); + /** + * Pool of resource rentals that have an activity + */ + private highPriority = new Set(); + private borrowed = new Set(); + /** + * Queue of functions that are waiting for a lease process to be available + */ + private acquireQueue: Array<(rental: ResourceRental) => void> = []; + private logger: Logger; + + private drainPromise?: Promise; + private abortController: AbortController; + + private allocation: Allocation; + private network?: Network; + private proposalPool: DraftOfferProposalPool; + private marketModule: MarketModule; + private networkModule: NetworkModule; + private rentalModule: RentalModule; + private readonly minPoolSize: number; + private readonly maxPoolSize: number; + private readonly resourceRentalOptions?: ResourceRentalOptions; + private readonly agreementOptions?: AgreementOptions; + private asyncLock = new AsyncLock(); + /** + * Number of resource rentals that are currently being signed. + * This is used to prevent creating more resource rentals than the pool size allows. + */ + private rentalsBeingSigned = 0; + + constructor(options: ResourceRentalPoolOptions & ResourceRentalPoolDependencies) { + this.allocation = options.allocation; + this.proposalPool = options.proposalPool; + this.marketModule = options.marketModule; + this.rentalModule = options.rentalModule; + this.networkModule = options.networkModule; + this.network = options.network; + this.resourceRentalOptions = options.resourceRentalOptions; + this.agreementOptions = options.agreementOptions; + + this.logger = options.logger; + + this.minPoolSize = + (() => { + if (typeof options?.poolSize === "number") { + return options?.poolSize; + } + if (typeof options?.poolSize === "object") { + return options?.poolSize.min; + } + })() || 0; + + this.maxPoolSize = + (() => { + if (typeof options?.poolSize === "object") { + return options?.poolSize.max; + } + })() || MAX_POOL_SIZE; + + this.abortController = new AbortController(); + } + + private async createNewResourceRental(signalOrTimeout?: number | AbortSignal) { + this.logger.debug("Creating new resource rental to add to pool"); + const signal = anyAbortSignal(this.abortController.signal, createAbortSignalFromTimeout(signalOrTimeout)); + + try { + this.rentalsBeingSigned++; + const agreement = await this.marketModule.signAgreementFromPool(this.proposalPool, this.agreementOptions, signal); + const networkNode = this.network + ? await this.networkModule.createNetworkNode(this.network, agreement.provider.id) + : undefined; + const resourceRental = this.rentalModule.createResourceRental(agreement, this.allocation, { + networkNode, + ...this.resourceRentalOptions, + }); + this.events.emit("created", { agreement }); + return resourceRental; + } catch (error) { + this.events.emit("errorCreatingRental", { + error: new GolemMarketError( + "Creating resource rental failed", + MarketErrorCode.ResourceRentalCreationFailed, + error, + ), + }); + this.logger.error("Creating resource rental failed", error); + throw error; + } finally { + this.rentalsBeingSigned--; + } + } + + private async validate(resourceRental: ResourceRental) { + try { + const state = await resourceRental.fetchAgreementState(); + const result = state === "Approved"; + this.logger.debug("Validated resource rental in the pool", { result, state }); + return result; + } catch (err) { + this.logger.error("Something went wrong while validating resource rental, it will be destroyed", err); + return false; + } + } + + private canCreateMoreResourceRentals() { + return this.getSize() + this.rentalsBeingSigned < this.maxPoolSize; + } + + /** + * Take the first valid resource rental from the pool + * If there is no valid resource rental, return null + */ + private async takeValidResourceRental(): Promise { + let resourceRental: ResourceRental | null = null; + if (this.highPriority.size > 0) { + resourceRental = this.highPriority.values().next().value as ResourceRental; + this.highPriority.delete(resourceRental); + } else if (this.lowPriority.size > 0) { + resourceRental = this.lowPriority.values().next().value as ResourceRental; + this.lowPriority.delete(resourceRental); + } + if (!resourceRental) { + return null; + } + const isValid = await this.validate(resourceRental); + if (!isValid) { + await this.destroy(resourceRental); + return this.takeValidResourceRental(); + } + return resourceRental; + } + + private async enqueueAcquire(): Promise { + return new Promise((resolve) => { + this.acquireQueue.push((resourceRental) => { + this.borrowed.add(resourceRental); + this.events.emit("acquired", { + agreement: resourceRental.agreement, + }); + resolve(resourceRental); + }); + }); + } + + /** + * Borrow a resource rental from the pool. + * If there is no valid resource rental a new one will be created. + * @param signalOrTimeout - the timeout in milliseconds or an AbortSignal that will be used to cancel the rental request + */ + async acquire(signalOrTimeout?: number | AbortSignal): Promise { + if (this.isDraining) { + throw new GolemAbortError("The pool is in draining mode, you cannot acquire new resources"); + } + + let resourceRental = await this.takeValidResourceRental(); + + if (!resourceRental) { + if (!this.canCreateMoreResourceRentals()) { + return this.enqueueAcquire(); + } + resourceRental = await this.createNewResourceRental(signalOrTimeout); + } + + this.borrowed.add(resourceRental); + this.events.emit("acquired", { + agreement: resourceRental.agreement, + }); + + return resourceRental; + } + + /** + * If there are any acquires waiting in the queue, the resource rental will be passed to the first one. + * Otherwise, the resource rental will be added to the queue. + */ + private passResourceRentalToWaitingAcquireOrBackToPool(resourceRental: ResourceRental) { + if (this.acquireQueue.length > 0) { + const acquire = this.acquireQueue.shift()!; + acquire(resourceRental); + return; + } + if (resourceRental.hasActivity()) { + this.highPriority.add(resourceRental); + } else { + this.lowPriority.add(resourceRental); + } + } + + async release(resourceRental: ResourceRental): Promise { + return this.asyncLock.acquire("resource-rental-pool", async () => { + if (this.getAvailableSize() >= this.maxPoolSize) { + return this.destroy(resourceRental); + } + this.borrowed.delete(resourceRental); + const isValid = await this.validate(resourceRental); + if (!isValid) { + return this.destroy(resourceRental); + } + this.passResourceRentalToWaitingAcquireOrBackToPool(resourceRental); + this.events.emit("released", { + agreement: resourceRental.agreement, + }); + }); + } + + async destroy(resourceRental: ResourceRental): Promise { + try { + this.borrowed.delete(resourceRental); + this.logger.debug("Destroying resource rental from the pool", { agreementId: resourceRental.agreement.id }); + await Promise.all([resourceRental.stopAndFinalize(), this.removeNetworkNode(resourceRental)]); + this.events.emit("destroyed", { + agreement: resourceRental.agreement, + }); + } catch (error) { + this.events.emit("errorDestroyingRental", { + agreement: resourceRental.agreement, + error: new GolemMarketError( + "Destroying resource rental failed", + MarketErrorCode.ResourceRentalTerminationFailed, + error, + ), + }); + this.logger.error("Destroying resource rental failed", error); + } + } + + private get isDraining(): boolean { + return !!this.drainPromise; + } + + private async startDrain() { + try { + await this.asyncLock.acquire("resource-rental-pool", async () => { + this.abortController.abort("The pool is in draining mode"); + this.events.emit("draining"); + this.acquireQueue = []; + const allResourceRentals = Array.from(this.borrowed) + .concat(Array.from(this.lowPriority)) + .concat(Array.from(this.highPriority)); + await Promise.allSettled(allResourceRentals.map((resourceRental) => this.destroy(resourceRental))); + this.lowPriority.clear(); + this.highPriority.clear(); + this.borrowed.clear(); + this.abortController = new AbortController(); + }); + } catch (error) { + this.logger.error("Draining the pool failed", error); + throw error; + } finally { + this.events.emit("end"); + } + } + + /** + * Sets the pool into draining mode and then clears it + * + * When set to drain mode, no new acquires will be possible. At the same time, all agreements in the pool will be terminated with the Providers. + * + * @return Resolves when all agreements are terminated + */ + async drainAndClear() { + if (this.isDraining) { + return this.drainPromise; + } + this.drainPromise = this.startDrain().finally(() => { + this.drainPromise = undefined; + }); + return this.drainPromise; + } + + /** + * Total size (available + borrowed) + */ + getSize() { + return this.getAvailableSize() + this.getBorrowedSize(); + } + + /** + * Available size (how many resource rental are ready to be borrowed) + */ + getAvailableSize() { + return this.lowPriority.size + this.highPriority.size; + } + + /** + * Borrowed size (how many resource rental are currently out of the pool) + */ + getBorrowedSize() { + return this.borrowed.size; + } + + /** + * Wait till the pool is ready to use (min number of items in pool are usable). + * If an error occurs while creating new resource rentals, it will be retried until the pool is ready + * (potentially indefinitely). To stop this process if it fails to reach the desired state in a given time, + * you can pass either a timeout in milliseconds or an AbortSignal. + * + * @example + * ```typescript + * await pool.ready(10_000); // If the pool is not ready in 10 seconds, an error will be thrown + * ``` + * @example + * ```typescript + * await pool.ready(AbortSignal.timeout(10_000)); // If the pool is not ready in 10 seconds, an error will be thrown + * ``` + */ + async ready(timeoutMs?: number): Promise; + async ready(abortSignal?: AbortSignal): Promise; + async ready(timeoutOrAbortSignal?: number | AbortSignal): Promise { + if (this.minPoolSize <= this.getAvailableSize()) { + return; + } + const signal = anyAbortSignal(this.abortController.signal, createAbortSignalFromTimeout(timeoutOrAbortSignal)); + const tryCreatingMissingResourceRentals = async () => { + await Promise.allSettled( + new Array(this.minPoolSize - this.getAvailableSize()).fill(0).map(() => + this.createNewResourceRental(signal).then( + (resourceRental) => this.lowPriority.add(resourceRental), + (error) => this.logger.error("Creating resource rental failed", error), + ), + ), + ); + }; + + while (this.minPoolSize > this.getAvailableSize()) { + if (signal.aborted) { + break; + } + await runOnNextEventLoopIteration(tryCreatingMissingResourceRentals); + } + + if (this.minPoolSize > this.getAvailableSize()) { + throw new Error("Could not create enough resource rentals to reach the minimum pool size in time"); + } + this.events.emit("ready"); + } + + private async removeNetworkNode(resourceRental: ResourceRental) { + if (this.network && resourceRental.networkNode) { + this.logger.debug("Removing a node from the network", { + network: this.network.getNetworkInfo().ip, + nodeIp: resourceRental.networkNode.ip, + }); + await this.networkModule.removeNetworkNode(this.network, resourceRental.networkNode); + } + } + + /** + * Acquire a resource rental from the pool and release it after the callback is done + * @example + * ```typescript + * const result = await pool.withRental(async (rental) => { + * // Do something with the rented resources + * return result; + * // pool.release(rental) is called automatically + * // even if an error is thrown in the callback + * }); + * ``` + * @param callback - a function that takes a `rental` object as its argument. The rental is automatically released after the callback is executed, regardless of whether it completes successfully or throws an error. + * @param signalOrTimeout - the timeout in milliseconds or an AbortSignal that will be used to cancel the rental request + */ + public async withRental( + callback: (rental: ResourceRental) => Promise, + signalOrTimeout?: number | AbortSignal, + ): Promise { + const rental = await this.acquire(signalOrTimeout); + try { + return await callback(rental); + } finally { + await this.release(rental); + } + } +} diff --git a/src/resource-rental/resource-rental.test.ts b/src/resource-rental/resource-rental.test.ts new file mode 100644 index 000000000..88bbea936 --- /dev/null +++ b/src/resource-rental/resource-rental.test.ts @@ -0,0 +1,83 @@ +import { imock, instance, mock, reset, spy, when, verify, _ } from "@johanblumenberg/ts-mockito"; +import { Agreement, MarketModule } from "../market"; +import { StorageProvider } from "../shared/storage"; +import { AgreementPaymentProcess } from "../payment/agreement_payment_process"; +import { ResourceRental, ResourceRentalOptions } from "."; +import { ActivityModule, ExeUnit } from "../activity"; +import { Logger } from "../shared/utils"; + +const mockAgreement = mock(Agreement); +const mockStorageProvider = imock(); +const mockPaymentProcess = mock(AgreementPaymentProcess); +const mockMarketModule = imock(); +const mockActivityModule = imock(); +const mockLogger = imock(); +const mockResourceRentalOptions = imock(); +when(mockResourceRentalOptions.networkNode).thenReturn(undefined); + +let resourceRental: ResourceRental; + +beforeEach(() => { + reset(mockAgreement); + reset(mockStorageProvider); + reset(mockPaymentProcess); + reset(mockMarketModule); + reset(mockActivityModule); + reset(mockLogger); + reset(mockResourceRentalOptions); + when(mockActivityModule.createExeUnit(_, _)).thenResolve(instance(mock(ExeUnit))); + resourceRental = new ResourceRental( + instance(mockAgreement), + instance(mockStorageProvider), + instance(mockPaymentProcess), + instance(mockMarketModule), + instance(mockActivityModule), + instance(mockLogger), + instance(mockResourceRentalOptions), + ); +}); + +describe("ResourceRental", () => { + describe("stopAndFinalize", () => { + it("reuses the same promise if called multiple times", async () => { + const rentalSpy = spy(resourceRental); + when(rentalSpy["startStopAndFinalize"](_)).thenResolve(); + expect(resourceRental["finalizePromise"]).toBeUndefined(); + const promise1 = resourceRental.stopAndFinalize(); + const promise2 = resourceRental.stopAndFinalize(); + const promise3 = resourceRental.stopAndFinalize(); + expect(resourceRental["finalizePromise"]).toBeDefined(); + await Promise.all([promise1, promise2, promise3]); + verify(rentalSpy["startStopAndFinalize"](_)).once(); + expect(resourceRental["finalizePromise"]).toBeUndefined(); + }); + describe("ExeUnit", () => { + it("should create an exe unit on startup and use it later", async () => { + expect(resourceRental["currentExeUnit"]).toBeDefined(); + verify(mockActivityModule.createExeUnit(_, _)).once(); + await resourceRental.getExeUnit(); + verify(mockActivityModule.createExeUnit(_, _)).once(); + }); + + it("should reuse the same promise if called multiple times", async () => { + expect(resourceRental["currentExeUnit"]).toBeDefined(); + const promise1 = resourceRental.getExeUnit(); + const promise2 = resourceRental.getExeUnit(); + const promise3 = resourceRental.getExeUnit(); + await Promise.all([promise1, promise2, promise3]); + verify(mockActivityModule.createExeUnit(_, _)).once(); + }); + + it("should reuse the same promise if called multiple time after destroy exe-unit created on strtup", async () => { + expect(resourceRental["currentExeUnit"]).toBeDefined(); + await resourceRental.destroyExeUnit(); + const promise1 = resourceRental.getExeUnit(); + const promise2 = resourceRental.getExeUnit(); + const promise3 = resourceRental.getExeUnit(); + expect(resourceRental["exeUnitPromise"]).toBeDefined(); + await Promise.all([promise1, promise2, promise3]); + verify(mockActivityModule.createExeUnit(_, _)).twice(); + }); + }); + }); +}); diff --git a/src/resource-rental/resource-rental.ts b/src/resource-rental/resource-rental.ts new file mode 100644 index 000000000..8933cdac7 --- /dev/null +++ b/src/resource-rental/resource-rental.ts @@ -0,0 +1,196 @@ +import { Agreement, MarketModule } from "../market"; +import { AgreementPaymentProcess, PaymentProcessOptions } from "../payment/agreement_payment_process"; +import { createAbortSignalFromTimeout, Logger } from "../shared/utils"; +import { waitForCondition } from "../shared/utils/wait"; +import { Activity, ActivityModule, ExeUnit, ExeUnitOptions } from "../activity"; +import { StorageProvider } from "../shared/storage"; +import { EventEmitter } from "eventemitter3"; +import { NetworkNode } from "../network"; +import { ExecutionOptions } from "../activity/exe-script-executor"; +import { GolemAbortError, GolemTimeoutError, GolemUserError } from "../shared/error/golem-error"; + +export interface ResourceRentalEvents { + /** Emitted when the rental process is fully finalized */ + finalized: () => void; + + /** Emitted when ExeUnit is successfully created and initialised */ + exeUnitCreated: (activity: Activity) => void; + + /** Emitted when the ExeUnit is successfully destroyed */ + exeUnitDestroyed: (activity: Activity) => void; + + /** Emitted when there is an error while creating or destroying the ExeUnit */ + error: (error: Error) => void; +} + +export interface ResourceRentalOptions { + exeUnit?: Pick; + activity?: ExecutionOptions; + payment?: Partial; + networkNode?: NetworkNode; +} + +/** + * Combines an agreement, activity, exe unit and payment process into a single high-level abstraction. + */ +export class ResourceRental { + public readonly events = new EventEmitter(); + public readonly networkNode?: NetworkNode; + + private currentExeUnit: ExeUnit | null = null; + private abortController = new AbortController(); + private finalizePromise?: Promise; + private exeUnitPromise?: Promise; + + public constructor( + public readonly agreement: Agreement, + private readonly storageProvider: StorageProvider, + private readonly paymentProcess: AgreementPaymentProcess, + private readonly marketModule: MarketModule, + private readonly activityModule: ActivityModule, + private readonly logger: Logger, + private readonly resourceRentalOptions?: ResourceRentalOptions, + ) { + this.networkNode = this.resourceRentalOptions?.networkNode; + + this.createExeUnit(this.abortController.signal); + // TODO: Listen to agreement events to know when it goes down due to provider closing it! + } + + private async startStopAndFinalize(signalOrTimeout?: number | AbortSignal) { + try { + if (this.currentExeUnit) { + await this.currentExeUnit.teardown(); + } + this.abortController.abort("The resource rental is finalizing"); + if (this.currentExeUnit?.activity) { + await this.activityModule.destroyActivity(this.currentExeUnit.activity); + } + if ((await this.fetchAgreementState()) !== "Terminated") { + await this.marketModule.terminateAgreement(this.agreement); + } + if (this.paymentProcess.isFinished()) { + return; + } + + this.logger.info("Waiting for payment process of agreement to finish", { agreementId: this.agreement.id }); + const abortSignal = createAbortSignalFromTimeout(signalOrTimeout); + await waitForCondition(() => this.paymentProcess.isFinished(), { + signalOrTimeout: abortSignal, + }).catch((error) => { + this.paymentProcess.stop(); + if (error instanceof GolemTimeoutError) { + throw new GolemTimeoutError( + `The finalization of payment process has been aborted due to a timeout`, + abortSignal.reason, + ); + } + throw new GolemAbortError("The finalization of payment process has been aborted", abortSignal.reason); + }); + this.logger.info("Finalized payment process", { agreementId: this.agreement.id }); + } catch (error) { + this.logger.error("Filed to finalize payment process", { agreementId: this.agreement.id, error }); + throw error; + } finally { + this.events.emit("finalized"); + } + } + + /** + * Terminates the activity and agreement (stopping any ongoing work) and finalizes the payment process. + * Resolves when the rental will be fully terminated and all pending business operations finalized. + * If the rental is already finalized, it will resolve immediately. + * @param signalOrTimeout - timeout in milliseconds or an AbortSignal that will be used to cancel the finalization process, especially the payment process. + * Please note that canceling the payment process may fail to comply with the terms of the agreement. + * If this method is called multiple times, it will return the same promise, ignoring the signal or timeout. + */ + async stopAndFinalize(signalOrTimeout?: number | AbortSignal) { + if (this.finalizePromise) { + return this.finalizePromise; + } + this.finalizePromise = this.startStopAndFinalize(signalOrTimeout).finally(() => { + this.finalizePromise = undefined; + }); + return this.finalizePromise; + } + + public hasActivity(): boolean { + return this.currentExeUnit !== null; + } + + /** + * Creates an activity on the Provider, and returns a exe-unit that can be used to operate within the activity + * @param signalOrTimeout - timeout in milliseconds or an AbortSignal that will be used to cancel the exe-unit request, + * especially when the exe-unit is in the process of starting, deploying and preparing the environment (including setup function) + */ + async getExeUnit(signalOrTimeout?: number | AbortSignal): Promise { + if (this.finalizePromise || this.abortController.signal.aborted) { + throw new GolemUserError("The resource rental is not active. It may have been aborted or finalized"); + } + if (this.currentExeUnit !== null) { + return this.currentExeUnit; + } + const abortController = new AbortController(); + this.abortController.signal.addEventListener("abort", () => + abortController.abort(this.abortController.signal.reason), + ); + if (signalOrTimeout) { + const abortSignal = createAbortSignalFromTimeout(signalOrTimeout); + abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason)); + if (signalOrTimeout instanceof AbortSignal && signalOrTimeout.aborted) { + abortController.abort(signalOrTimeout.reason); + } + } + return this.createExeUnit(abortController.signal); + } + + /** + * Destroy previously created exe-unit. + * Please note that if ResourceRental is left without ExeUnit for some time (default 90s) + * the provider will terminate the Agreement and ResourceRental will be unuseble + */ + async destroyExeUnit() { + try { + if (this.currentExeUnit !== null) { + await this.activityModule.destroyActivity(this.currentExeUnit.activity); + this.currentExeUnit = null; + } else { + throw new GolemUserError(`There is no exe-unit to destroy.`); + } + } catch (error) { + this.events.emit("error", error); + this.logger.error(`Failed to destroy exe-unit. ${error}`, { activityId: this.currentExeUnit?.activity }); + throw error; + } + } + + async fetchAgreementState() { + return this.marketModule.fetchAgreement(this.agreement.id).then((agreement) => agreement.getState()); + } + + private async createExeUnit(abortSignal: AbortSignal) { + if (!this.exeUnitPromise) { + this.exeUnitPromise = (async () => { + const activity = await this.activityModule.createActivity(this.agreement); + this.currentExeUnit = await this.activityModule.createExeUnit(activity, { + storageProvider: this.storageProvider, + networkNode: this.resourceRentalOptions?.networkNode, + executionOptions: this.resourceRentalOptions?.activity, + signalOrTimeout: abortSignal, + ...this.resourceRentalOptions?.exeUnit, + }); + this.events.emit("exeUnitCreated", activity); + return this.currentExeUnit; + })() + .catch((error) => { + this.events.emit("error", error); + this.logger.error(`Failed to create exe-unit. ${error}`, { agreementId: this.agreement.id }); + throw error; + }) + .finally(() => { + this.exeUnitPromise = undefined; + }); + } + return this.exeUnitPromise; + } +} diff --git a/src/shared/cache/CacheService.ts b/src/shared/cache/CacheService.ts new file mode 100644 index 000000000..3e2f25e80 --- /dev/null +++ b/src/shared/cache/CacheService.ts @@ -0,0 +1,29 @@ +export class CacheService { + private readonly storage = new Map(); + + public set(key: string, value: T) { + this.storage.set(key, value); + + return value; + } + + public get(key: string) { + return this.storage.get(key); + } + + public delete(key: string) { + return this.storage.delete(key); + } + + public has(key: string) { + return this.storage.has(key); + } + + public getAll(): T[] { + return [...this.storage.values()]; + } + + public flushAll() { + return this.storage.clear(); + } +} diff --git a/src/error/golem-error.ts b/src/shared/error/golem-error.ts similarity index 98% rename from src/error/golem-error.ts rename to src/shared/error/golem-error.ts index e38d6036a..a260ee2f2 100644 --- a/src/error/golem-error.ts +++ b/src/shared/error/golem-error.ts @@ -63,7 +63,7 @@ export class GolemTimeoutError extends GolemError {} export abstract class GolemModuleError extends GolemError { protected constructor( message: string, - public code: number, + public code: string | number, previous?: Error, ) { super(message, previous); diff --git a/src/shared/storage/GftpServerAdapter.ts b/src/shared/storage/GftpServerAdapter.ts new file mode 100644 index 000000000..10312c014 --- /dev/null +++ b/src/shared/storage/GftpServerAdapter.ts @@ -0,0 +1,53 @@ +import { FileServerEntry, IFileServer } from "../../activity"; +import { GolemConfigError, GolemInternalError } from "../error/golem-error"; +import { StorageProvider } from "./provider"; +import fs from "fs"; +import jsSha3 from "js-sha3"; + +/** + * This class provides GFTP based implementation of the IFileServer interface used in the SDK + */ +export class GftpServerAdapter implements IFileServer { + private published = new Map(); + + constructor(private readonly storage: StorageProvider) {} + + async publishFile(sourcePath: string) { + if (!this.storage.isReady()) { + throw new GolemInternalError("The GFTP server as to be initialized before publishing a file "); + } + + if (!fs.existsSync(sourcePath) || fs.lstatSync(sourcePath).isDirectory()) { + throw new GolemConfigError(`File ${sourcePath} does not exist o is a directory`); + } + + const fileUrl = await this.storage.publishFile(sourcePath); + const fileHash = this.calculateFileHash(sourcePath); + + const entry = { + fileUrl, + fileHash, + }; + + this.published.set(sourcePath, entry); + + return entry; + } + + public isServing() { + return this.published.size !== 0; + } + + getPublishInfo(sourcePath: string) { + return this.published.get(sourcePath); + } + + isFilePublished(sourcePath: string): boolean { + return this.published.has(sourcePath); + } + + private calculateFileHash(localPath: string): string { + const fileBuffer = fs.readFileSync(localPath); + return jsSha3.sha3_224(fileBuffer); + } +} diff --git a/src/shared/storage/default.ts b/src/shared/storage/default.ts new file mode 100644 index 000000000..bf9f75ad8 --- /dev/null +++ b/src/shared/storage/default.ts @@ -0,0 +1,16 @@ +import { GftpStorageProvider } from "./gftp"; +import { WebSocketBrowserStorageProvider } from "./ws-browser"; +import { NullStorageProvider } from "./null"; +import { Logger, YagnaApi, isNode, isBrowser } from "../utils"; + +export function createDefaultStorageProvider(yagnaApi: YagnaApi, logger?: Logger) { + if (isNode) { + return new GftpStorageProvider(logger?.child("storage")); + } + if (isBrowser) { + return new WebSocketBrowserStorageProvider(yagnaApi, { + logger: logger?.child("storage"), + }); + } + return new NullStorageProvider(); +} diff --git a/src/storage/gftp.ts b/src/shared/storage/gftp.ts similarity index 97% rename from src/storage/gftp.ts rename to src/shared/storage/gftp.ts index 6eebed8ea..b15f98cb9 100644 --- a/src/storage/gftp.ts +++ b/src/shared/storage/gftp.ts @@ -1,5 +1,5 @@ import { StorageProvider } from "./provider"; -import { Logger, defaultLogger, runtimeContextChecker, sleep } from "../utils"; +import { Logger, defaultLogger, isBrowser, sleep } from "../utils"; import path from "path"; import fs from "fs"; import cp from "child_process"; @@ -24,7 +24,7 @@ export class GftpStorageProvider implements StorageProvider { private lock = false; constructor(logger?: Logger) { - if (runtimeContextChecker.isBrowser) { + if (isBrowser) { throw new GolemUserError(`File transfer by GFTP module is unsupported in the browser context.`); } this.logger = logger || defaultLogger("storage"); @@ -175,4 +175,8 @@ export class GftpStorageProvider implements StorageProvider { const links = await this.jsonrpc("publish", { files: [file.toString()] }); return links[0]?.url; } + + isReady(): boolean { + return this.isInitialized; + } } diff --git a/src/storage/index.ts b/src/shared/storage/index.ts similarity index 80% rename from src/storage/index.ts rename to src/shared/storage/index.ts index edae9ef65..51d7ba66f 100644 --- a/src/storage/index.ts +++ b/src/shared/storage/index.ts @@ -2,3 +2,4 @@ export { StorageProvider } from "./provider"; export { GftpStorageProvider } from "./gftp"; export { NullStorageProvider } from "./null"; export { WebSocketBrowserStorageProvider, WebSocketStorageProviderOptions } from "./ws-browser"; +export { createDefaultStorageProvider } from "./default"; diff --git a/src/storage/null.ts b/src/shared/storage/null.ts similarity index 97% rename from src/storage/null.ts rename to src/shared/storage/null.ts index a1af401b2..6f1a55c31 100644 --- a/src/storage/null.ts +++ b/src/shared/storage/null.ts @@ -44,4 +44,8 @@ export class NullStorageProvider implements StorageProvider { release(urls: string[]): Promise { return Promise.resolve(undefined); } + + isReady(): boolean { + return true; + } } diff --git a/src/storage/provider.ts b/src/shared/storage/provider.ts similarity index 92% rename from src/storage/provider.ts rename to src/shared/storage/provider.ts index 8da1fa703..93038a422 100644 --- a/src/storage/provider.ts +++ b/src/shared/storage/provider.ts @@ -6,6 +6,11 @@ export interface StorageProvider { */ init(): Promise; + /** + * Tells if the storage provider is ready for use + */ + isReady(): boolean; + /** * Close storage provider and release all resources. */ diff --git a/tests/unit/ws-browser.spec.ts b/src/shared/storage/ws-browser.test.ts similarity index 84% rename from tests/unit/ws-browser.spec.ts rename to src/shared/storage/ws-browser.test.ts index b5a12fc76..1d30cc987 100644 --- a/tests/unit/ws-browser.spec.ts +++ b/src/shared/storage/ws-browser.test.ts @@ -1,29 +1,54 @@ -import { WebSocketBrowserStorageProvider, pinoLogger } from "../../src"; -import { encode, toObject } from "flatbuffers/js/flexbuffers"; -import { LoggerMock, YagnaMock } from "../mock"; +// TODO: improve mocks - remove as any +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { GolemInternalError, Logger, nullLogger, WebSocketBrowserStorageProvider, YagnaApi } from "../../index"; +// .js added for ESM compatibility +import { encode, toObject } from "flatbuffers/js/flexbuffers.js"; import * as jsSha3 from "js-sha3"; -import { TEST_IDENTITY } from "../mock/fixtures"; -import { ServiceModel } from "../../src/utils/yagna/gsb"; -import { GolemInternalError } from "../../src/error/golem-error"; +import { GsbApi, IdentityApi } from "ya-ts-client"; +import { anything, imock, instance, mock, reset, verify, when } from "@johanblumenberg/ts-mockito"; jest.mock("uuid", () => ({ v4: () => "uuid" })); type UploadChunkChunk = { offset: number; content: Uint8Array }; -describe("WebSocketBrowserStorageProvider", () => { - let logger: LoggerMock; - const yagnaApi = new YagnaMock().getApi(); +const mockYagna = mock(YagnaApi); +const mockIdentity = mock(IdentityApi.DefaultService); +const mockGsb = mock(GsbApi.RequestorService); +const logger = imock(); +const yagnaApi = instance(mockYagna); +const TEST_IDENTITY = "0x19ee20228a4c4bf8d4aebc79d9d3af2a01433456"; +describe("WebSocketBrowserStorageProvider", () => { const createProvider = () => new WebSocketBrowserStorageProvider(yagnaApi, { - logger, + logger: instance(logger), }); let provider: WebSocketBrowserStorageProvider; beforeEach(() => { - logger = new LoggerMock(); provider = createProvider(); + jest.clearAllMocks(); + + reset(mockYagna); + reset(mockIdentity); + reset(mockGsb); + reset(logger); + + when(mockYagna.yagnaOptions).thenReturn({ + apiKey: "example-api-key", + basePath: "http://127.0.0.1:7465", + }); + + when(mockYagna.identity).thenReturn(instance(mockIdentity)); + + when(mockIdentity.getIdentity()).thenResolve({ + identity: TEST_IDENTITY, + name: "tester", + role: "tester", + }); + + when(mockYagna.gsb).thenReturn(instance(mockGsb)); }); describe("constructor", () => { @@ -33,7 +58,7 @@ describe("WebSocketBrowserStorageProvider", () => { }); it("should use provided logger", () => { - const logger = pinoLogger(); + const logger = nullLogger(); const provider = new WebSocketBrowserStorageProvider(yagnaApi, { logger }); expect(provider["logger"]).toBe(logger); }); @@ -131,7 +156,7 @@ describe("WebSocketBrowserStorageProvider", () => { expect(spy1).toHaveBeenCalled(); }); - it("should log invalid requests", async () => { + it("should not respond to invalid requests", async () => { const data = { id: "foo", component: "Foo", @@ -141,7 +166,6 @@ describe("WebSocketBrowserStorageProvider", () => { const spy1 = jest.spyOn(provider as any, "respond").mockReturnThis(); socket.dispatchEvent(new MessageEvent("message", { data: encode(data).buffer })); expect(spy1).not.toHaveBeenCalled(); - await logger.expectToInclude("[WebSocketBrowserStorageProvider] Unsupported message in publishData(): Foo"); }); }); }); @@ -221,7 +245,7 @@ describe("WebSocketBrowserStorageProvider", () => { expect(callback).toHaveBeenCalledWith(result); }); - it("should log invalid requests", async () => { + it("should not respond to invalid requests", async () => { const callback = jest.fn(); const data = { id: "foo", @@ -234,7 +258,6 @@ describe("WebSocketBrowserStorageProvider", () => { socket.dispatchEvent(new MessageEvent("message", { data: encode(data).buffer })); expect(spy1).not.toHaveBeenCalled(); expect(spy2).not.toHaveBeenCalled(); - await logger.expectToInclude("[WebSocketBrowserStorageProvider] Unsupported message in receiveData(): Foo"); }); }); }); @@ -256,14 +279,15 @@ describe("WebSocketBrowserStorageProvider", () => { describe("createService()", () => { it("should create service and return service info", async () => { const data = { servicesId: "ID" }; - jest.spyOn(yagnaApi.gsb, "createService").mockImplementation((fileInfo, components) => { - return Promise.resolve({ - servicesId: "ID", - }); - }); + + when(mockGsb.bindServices(anything())).thenResolve({ + servicesId: "ID", // FIXME: Incorrect type in ya-ts-client + } as any); const result = await provider["createService"]({ id: "foo", url: "" }, []); - expect(yagnaApi.gsb.createService).toHaveBeenCalled(); + + verify(mockGsb.bindServices(anything())).once(); + expect(result.serviceId).toEqual("ID"); expect(result.url.toString()).toEqual( `ws://127.0.0.1:7465/gsb-api/v1/services/${data.servicesId}?authToken=${yagnaApi.yagnaOptions.apiKey}`, @@ -272,44 +296,42 @@ describe("WebSocketBrowserStorageProvider", () => { it("should record the service for later release", async () => { const data = { servicesId: "ID" }; - jest.spyOn(yagnaApi.gsb, "createService").mockImplementation((fileInfo, components) => { - return Promise.resolve({ servicesId: "ID" }); - }); + + when(mockGsb.bindServices(anything())).thenResolve({ + servicesId: "ID", // FIXME: Incorrect type in ya-ts-client + } as any); + await provider["createService"]({ id: "foo", url: "/file" }, []); - expect(yagnaApi.gsb.createService).toHaveBeenCalled(); + + verify(mockGsb.bindServices(anything())).once(); + expect(provider["services"].size).toBe(1); expect(provider["services"].get("/file")).toEqual(data.servicesId); }); it("should throw when service creation fails", async () => { - jest.spyOn(yagnaApi.gsb, "createService").mockImplementation((fileInfo, components) => { - return Promise.reject(new Error("test_error")); - }); + when(mockGsb.bindServices(anything())).thenReject(new Error("test_error")); await expect(() => { return provider["createService"]({ id: "foo", url: "/file" }, []); }).rejects.toThrow(); - expect(yagnaApi.gsb.createService).toHaveBeenCalled(); }); }); describe("deleteService()", () => { it("should call delete service API", async () => { - jest.spyOn(yagnaApi.gsb, "deleteService").mockImplementation((id) => { - return Promise.resolve(); + when(mockGsb.unbindServices(anything())).thenResolve({ + message: "Ok", }); await provider["deleteService"]("Foo"); - expect(yagnaApi.gsb.deleteService).toHaveBeenCalled(); + verify(mockGsb.unbindServices(anything())).once(); }); it("should throw when delete API fails", async () => { - jest.spyOn(yagnaApi.gsb, "deleteService").mockImplementation((id) => { - return Promise.reject(new Error()); - }); - + when(mockGsb.unbindServices(anything())).thenReject(new Error("Some Error")); await expect(() => { return provider["deleteService"]("Foo"); }).rejects.toThrow(); - expect(yagnaApi.gsb.deleteService).toHaveBeenCalled(); + verify(mockGsb.unbindServices(anything())).once(); }); }); diff --git a/src/storage/ws-browser.ts b/src/shared/storage/ws-browser.ts similarity index 92% rename from src/storage/ws-browser.ts rename to src/shared/storage/ws-browser.ts index 4d44711da..fdf8115a9 100644 --- a/src/storage/ws-browser.ts +++ b/src/shared/storage/ws-browser.ts @@ -1,6 +1,7 @@ import { StorageProvider, StorageProviderDataCallback } from "./provider"; import { v4 } from "uuid"; -import { encode, toObject } from "flatbuffers/js/flexbuffers"; +// .js added for ESM compatibility +import { encode, toObject } from "flatbuffers/js/flexbuffers.js"; import * as jsSha3 from "js-sha3"; import { Logger, nullLogger, YagnaApi } from "../utils"; import { GolemInternalError } from "../error/golem-error"; @@ -58,6 +59,7 @@ export class WebSocketBrowserStorageProvider implements StorageProvider { */ private services = new Map(); private logger: Logger; + private ready = false; constructor( private readonly yagnaApi: YagnaApi, @@ -67,10 +69,12 @@ export class WebSocketBrowserStorageProvider implements StorageProvider { } close(): Promise { + this.ready = false; return this.release(Array.from(this.services.keys())); } init(): Promise { + this.ready = true; return Promise.resolve(undefined); } @@ -145,6 +149,10 @@ export class WebSocketBrowserStorageProvider implements StorageProvider { }); } + isReady(): boolean { + return this.ready; + } + private async createFileInfo(): Promise { const id = v4(); const data = await this.yagnaApi.identity.getIdentity(); @@ -167,7 +175,13 @@ export class WebSocketBrowserStorageProvider implements StorageProvider { } private async createService(fileInfo: GftpFileInfo, components: string[]): Promise { - const resp = await this.yagnaApi.gsb.createService(fileInfo, components); + const resp = (await this.yagnaApi.gsb.bindServices({ + listen: { + on: `/public/gftp/${fileInfo.id}`, + components, + }, + // FIXME: not present in ya-client for some reason + })) as { servicesId: string }; const servicesId = resp.servicesId; const messageEndpoint = `/gsb-api/v1/services/${servicesId}?authToken=${this.yagnaApi.yagnaOptions.apiKey}`; const url = new URL(messageEndpoint, this.yagnaApi.yagnaOptions.basePath); @@ -178,7 +192,7 @@ export class WebSocketBrowserStorageProvider implements StorageProvider { } private async deleteService(id: string): Promise { - await this.yagnaApi.gsb.deleteService(id); + await this.yagnaApi.gsb.unbindServices(id); } private respond(ws: WebSocket, id: string, payload: unknown) { diff --git a/src/shared/types.ts b/src/shared/types.ts new file mode 100644 index 000000000..7b4d4f0a0 --- /dev/null +++ b/src/shared/types.ts @@ -0,0 +1,3 @@ +import { StorageProvider } from "./storage"; + +export type DataTransferProtocol = "gftp" | "ws" | StorageProvider; diff --git a/src/shared/utils/abortSignal.ts b/src/shared/utils/abortSignal.ts new file mode 100644 index 000000000..354d1e597 --- /dev/null +++ b/src/shared/utils/abortSignal.ts @@ -0,0 +1,37 @@ +/** + * If provided an AbortSignal, returns it. + * If provided a number, returns an AbortSignal that will be aborted after the specified number of milliseconds. + * If provided undefined, returns an AbortSignal that will never be aborted. + */ +export function createAbortSignalFromTimeout(timeoutOrSignal: number | AbortSignal | undefined) { + if (timeoutOrSignal instanceof AbortSignal) { + return timeoutOrSignal; + } + if (typeof timeoutOrSignal === "number") { + return AbortSignal.timeout(timeoutOrSignal); + } + return new AbortController().signal; +} + +/** + * Combine multiple AbortSignals into a single signal that will be aborted if any + * of the input signals are aborted. If any of the input signals are already aborted, + * the returned signal will be aborted immediately. + * + * Polyfill for AbortSignal.any(), since it's only available starting in Node 20 + * https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static + */ +export function anyAbortSignal(...signals: AbortSignal[]) { + const controller = new AbortController(); + for (const signal of signals) { + if (signal.aborted) { + controller.abort(signal.reason); + break; + } + signal.addEventListener("abort", () => { + if (controller.signal.aborted) return; + controller.abort(signal.reason); + }); + } + return controller.signal; +} diff --git a/src/shared/utils/apiErrorMessage.ts b/src/shared/utils/apiErrorMessage.ts new file mode 100644 index 000000000..e5ac9cef6 --- /dev/null +++ b/src/shared/utils/apiErrorMessage.ts @@ -0,0 +1,22 @@ +import YaTsClient from "ya-ts-client"; +function isApiError(error: unknown): error is YaTsClient.ActivityApi.ApiError { + return typeof error == "object" && error !== null && "name" in error && error.name === "ApiError"; +} +/** + * Try to extract a message from a yagna API error. + * If the error is not an instance of `ApiError`, return the error message. + */ +export function getMessageFromApiError(error: unknown): string { + if (!(error instanceof Error)) { + return String(error); + } + + if (isApiError(error)) { + try { + return JSON.stringify(error.body, null, 2); + } catch (_jsonParseError) { + return error.message; + } + } + return error.message; +} diff --git a/tests/unit/env_utils.test.ts b/src/shared/utils/env.test.ts similarity index 98% rename from tests/unit/env_utils.test.ts rename to src/shared/utils/env.test.ts index 50809819d..06052ee82 100644 --- a/tests/unit/env_utils.test.ts +++ b/src/shared/utils/env.test.ts @@ -1,4 +1,4 @@ -import { EnvUtils } from "../../src/utils"; +import { EnvUtils } from "./index"; describe("EnvUtils", () => { describe("getYagnaApiUrl()", () => { diff --git a/src/utils/env.ts b/src/shared/utils/env.ts similarity index 100% rename from src/utils/env.ts rename to src/shared/utils/env.ts diff --git a/src/shared/utils/eventLoop.ts b/src/shared/utils/eventLoop.ts new file mode 100644 index 000000000..820cb15c2 --- /dev/null +++ b/src/shared/utils/eventLoop.ts @@ -0,0 +1,30 @@ +/** + * Run a callback on the next event loop iteration ("promote" a microtask to a task using setTimeout). + * Note that this is not guaranteed to run on the very next iteration, but it will run as soon as possible. + * This function is designed to avoid the problem of microtasks queueing other microtasks in an infinite loop. + * See the example below for a common pitfall that this function can help avoid. + * Learn more about microtasks and their relation to async/await here: + * https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#control_flow_effects_of_await + * @param cb The callback to run on the next event loop iteration. + * @example + * ```ts + * const signal = AbortSignal.timeout(1_000); + * // This loop will run for 1 second, then stop. + * while (!signal.aborted) { + * await runOnNextEventLoopIteration(() => Promise.resolve()); + * } + * + * const signal = AbortSignal.timeout(1_000); + * // This loop will run indefinitely. + * // Each while loop iteration queues a microtask, which itself queues another microtask, and so on. + * while (!signal.aborted) { + * await Promise.resolve(); + * } + * ``` + */ +export function runOnNextEventLoopIteration(cb: () => Promise): Promise { + return new Promise((resolve, reject) => { + setTimeout(() => cb().then(resolve).catch(reject)); + }); +} diff --git a/src/utils/index.ts b/src/shared/utils/index.ts similarity index 50% rename from src/utils/index.ts rename to src/shared/utils/index.ts index 9eb6d6ca8..63471f224 100644 --- a/src/utils/index.ts +++ b/src/shared/utils/index.ts @@ -1,10 +1,12 @@ import sleep from "./sleep"; + export { sleep }; -export * as runtimeContextChecker from "./runtimeContextChecker"; +export * from "./runtimeContextChecker"; export { Logger } from "./logger/logger"; -export { pinoLogger } from "./logger/pinoLogger"; -export { jsonLogger } from "./logger/jsonLogger"; export { nullLogger } from "./logger/nullLogger"; export { defaultLogger } from "./logger/defaultLogger"; export * as EnvUtils from "./env"; -export { Yagna, YagnaApi, YagnaOptions } from "./yagna/yagna"; +export { YagnaApi, YagnaOptions } from "../yagna/yagnaApi"; +export * from "./abortSignal"; +export * from "./eventLoop"; +export * from "./rxjs"; diff --git a/src/utils/logger/defaultLogger.ts b/src/shared/utils/logger/defaultLogger.ts similarity index 81% rename from src/utils/logger/defaultLogger.ts rename to src/shared/utils/logger/defaultLogger.ts index a3eb97b75..a518275f7 100644 --- a/src/utils/logger/defaultLogger.ts +++ b/src/shared/utils/logger/defaultLogger.ts @@ -1,6 +1,7 @@ import debugLogger from "debug"; +import { Logger } from "./logger"; -type DefaultLoggerOptions = { +export type DefaultLoggerOptions = { /** * Disables prefixing the root namespace with golem-js * @@ -27,36 +28,36 @@ export function defaultLogger( opts: DefaultLoggerOptions = { disableAutoPrefix: false, }, -) { +): Logger { const namespaceWithBase = getNamespace(namespace, opts.disableAutoPrefix); const logger = debugLogger(namespaceWithBase); - function log(msg: string, ctx?: Record | Error) { + function log(level: string, msg: string, ctx?: Record | Error) { if (ctx) { - logger(`${msg} %o`, ctx); + logger(`[${level}] ${msg} %o`, ctx); } else { - logger(msg); + logger(`[${level}] ${msg}`); } } function debug(msg: string): void; function debug(msg: string, ctx?: Record | Error) { - log(msg, ctx); + log("DEBUG", msg, ctx); } function info(msg: string): void; function info(msg: string, ctx?: Record | Error) { - log(msg, ctx); + log("INFO", msg, ctx); } function warn(msg: string): void; function warn(msg: string, ctx?: Record | Error) { - log(msg, ctx); + log("WARN", msg, ctx); } function error(msg: string): void; function error(msg: string, ctx?: Record | Error) { - log(msg, ctx); + log("ERROR", msg, ctx); } return { @@ -65,6 +66,5 @@ export function defaultLogger( error, warn, debug, - log, }; } diff --git a/src/utils/logger/logger.ts b/src/shared/utils/logger/logger.ts similarity index 100% rename from src/utils/logger/logger.ts rename to src/shared/utils/logger/logger.ts diff --git a/src/utils/logger/nullLogger.ts b/src/shared/utils/logger/nullLogger.ts similarity index 100% rename from src/utils/logger/nullLogger.ts rename to src/shared/utils/logger/nullLogger.ts diff --git a/src/utils/runtimeContextChecker.ts b/src/shared/utils/runtimeContextChecker.ts similarity index 100% rename from src/utils/runtimeContextChecker.ts rename to src/shared/utils/runtimeContextChecker.ts diff --git a/src/shared/utils/rxjs.ts b/src/shared/utils/rxjs.ts new file mode 100644 index 000000000..b2962c9de --- /dev/null +++ b/src/shared/utils/rxjs.ts @@ -0,0 +1,23 @@ +import { Observable, Subject, finalize, mergeWith, takeUntil } from "rxjs"; + +/** + * Merges two observables until the first one completes (or errors). + * The difference between this and `merge` is that this will complete when the first observable completes, + * while `merge` would only complete when _all_ observables complete. + */ +export function mergeUntilFirstComplete( + observable1: Observable, + observable2: Observable, +): Observable { + const completionSubject = new Subject(); + const observable1WithCompletion = observable1.pipe( + takeUntil(completionSubject), + finalize(() => completionSubject.next()), + ); + const observable2WithCompletion = observable2.pipe( + takeUntil(completionSubject), + finalize(() => completionSubject.next()), + ); + + return observable1WithCompletion.pipe(mergeWith(observable2WithCompletion)); +} diff --git a/src/utils/sleep.ts b/src/shared/utils/sleep.ts similarity index 100% rename from src/utils/sleep.ts rename to src/shared/utils/sleep.ts diff --git a/src/shared/utils/timeout.ts b/src/shared/utils/timeout.ts new file mode 100644 index 000000000..5ed2ea700 --- /dev/null +++ b/src/shared/utils/timeout.ts @@ -0,0 +1,13 @@ +import { GolemTimeoutError } from "../error/golem-error"; + +export async function withTimeout(promise: Promise, timeoutMs: number): Promise { + let timeoutId: NodeJS.Timeout; + const timeout = (milliseconds: number): Promise => + new Promise((_, reject) => { + timeoutId = setTimeout( + () => reject(new GolemTimeoutError("Timeout for the operation was reached")), + milliseconds, + ); + }); + return Promise.race([promise, timeout(timeoutMs)]).finally(() => clearTimeout(timeoutId)); +} diff --git a/src/utils/types.ts b/src/shared/utils/types.ts similarity index 78% rename from src/utils/types.ts rename to src/shared/utils/types.ts index f1f401eaa..0a534d6cf 100644 --- a/src/utils/types.ts +++ b/src/shared/utils/types.ts @@ -13,3 +13,8 @@ export type RequireAtLeastOne = Pick> & Partial>>; }[Keys]; + +/** + * Utility type extracting the type of the element of a typed array + */ +export type ElementOf = T extends Array ? U : never; diff --git a/src/shared/utils/wait.ts b/src/shared/utils/wait.ts new file mode 100644 index 000000000..2fe8bb636 --- /dev/null +++ b/src/shared/utils/wait.ts @@ -0,0 +1,63 @@ +import { GolemAbortError, GolemTimeoutError } from "../error/golem-error"; +import { createAbortSignalFromTimeout } from "./abortSignal"; + +/** + * Utility function that helps to block the execution until a condition is met (check returns true) or the timeout happens. + * + * @param {function} check - The function checking if the condition is met. + * @param {Object} [opts] - Options controlling the timeout and check interval in seconds. + * @param {number} [opts.signalOrTimeout=30_000] - The timeout value in miliseconds or AbortSignal. + * @param {number} [opts.intervalSeconds=1] - The interval between condition checks in seconds. + * + * @return {Promise} - Resolves when the condition is met or rejects with a timeout error if it wasn't met on time. + */ +export function waitForCondition( + check: () => boolean | Promise, + opts?: { signalOrTimeout?: number | AbortSignal; intervalSeconds?: number }, +): Promise { + const abortSignal = createAbortSignalFromTimeout(opts?.signalOrTimeout ?? 30_000); + const intervalSeconds = opts?.intervalSeconds ?? 1; + let verifyInterval: NodeJS.Timeout | undefined; + + const verify = new Promise((resolve) => { + verifyInterval = setInterval(async () => { + if (await check()) { + resolve(); + } + }, intervalSeconds * 1000); + }); + + const wait = new Promise((_, reject) => { + const abortError = new GolemAbortError("Waiting for a condition has been aborted", abortSignal.reason); + if (abortSignal.aborted) { + return reject(abortError); + } + abortSignal.addEventListener("abort", () => + reject( + abortSignal.reason.name === "TimeoutError" + ? new GolemTimeoutError(`Waiting for a condition has been aborted due to a timeout`, abortSignal.reason) + : abortError, + ), + ); + }); + + return Promise.race([verify, wait]).finally(() => { + clearInterval(verifyInterval); + }); +} + +/** + * Simple utility that allows you to wait n-seconds and then call the provided function + */ +export function waitAndCall(fn: () => Promise | T, waitSeconds: number): Promise { + return new Promise((resolve, reject) => { + setTimeout(async () => { + try { + const val = await fn(); + resolve(val); + } catch (err) { + reject(err); + } + }, waitSeconds * 1_000); + }); +} diff --git a/src/shared/yagna/adapters/activity-api-adapter.ts b/src/shared/yagna/adapters/activity-api-adapter.ts new file mode 100644 index 000000000..e0daae6b1 --- /dev/null +++ b/src/shared/yagna/adapters/activity-api-adapter.ts @@ -0,0 +1,111 @@ +import { Agreement } from "../../../market"; +import { ActivityApi } from "ya-ts-client"; +import { Activity, ActivityStateEnum, GolemWorkError, IActivityApi, Result, WorkErrorCode } from "../../../activity"; +import { IActivityRepository } from "../../../activity/activity"; +import { getMessageFromApiError } from "../../utils/apiErrorMessage"; +import { ExeScriptRequest } from "../../../activity/exe-script-executor"; +import { Observable } from "rxjs"; +import { StreamingBatchEvent } from "../../../activity/results"; +import { YagnaExeScriptObserver } from "../yagnaApi"; + +export class ActivityApiAdapter implements IActivityApi { + constructor( + private readonly state: ActivityApi.RequestorStateService, + private readonly control: ActivityApi.RequestorControlService, + private readonly exec: YagnaExeScriptObserver, + private readonly activityRepo: IActivityRepository, + ) {} + + getActivity(id: string): Promise { + return this.activityRepo.getById(id); + } + + async createActivity(agreement: Agreement): Promise { + try { + const activityOrError = await this.control.createActivity({ + agreementId: agreement.id, + }); + + if (typeof activityOrError !== "object" || !("activityId" in activityOrError)) { + // will be caught in the catch block and converted to GolemWorkError + throw new Error(activityOrError); + } + + return this.activityRepo.getById(activityOrError.activityId); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemWorkError( + `Failed to create activity: ${message}`, + WorkErrorCode.ActivityCreationFailed, + agreement, + undefined, + agreement.provider, + ); + } + } + + async destroyActivity(activity: Activity): Promise { + try { + await this.control.destroyActivity(activity.id, 30); + return this.activityRepo.getById(activity.id); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemWorkError( + `Failed to destroy activity: ${message}`, + WorkErrorCode.ActivityDestroyingFailed, + activity.agreement, + activity, + activity.agreement.provider, + ); + } + } + + async getActivityState(id: string): Promise { + return this.activityRepo.getStateOfActivity(id); + } + + async executeScript(activity: Activity, script: ExeScriptRequest): Promise { + try { + return await this.control.exec(activity.id, script); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemWorkError( + `Failed to execute script. ${message}`, + WorkErrorCode.ScriptExecutionFailed, + activity.agreement, + activity, + activity.agreement.provider, + ); + } + } + + async getExecBatchResults( + activity: Activity, + batchId: string, + commandIndex?: number, + timeout?: number, + ): Promise { + try { + const results = await this.control.getExecBatchResults(activity.id, batchId, commandIndex, timeout); + return results.map((r) => new Result(r)); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemWorkError( + `Failed to fetch activity results. ${message}`, + WorkErrorCode.ActivityResultsFetchingFailed, + activity.agreement, + activity, + activity.provider, + error, + ); + } + } + + getExecBatchEvents( + activity: Activity, + batchId: string, + // commandIndex?: number | undefined, TODO: when it will be implemented in yagna + ): Observable { + return this.exec.observeBatchExecResults(activity.id, batchId); + } +} diff --git a/src/shared/yagna/adapters/index.ts b/src/shared/yagna/adapters/index.ts new file mode 100644 index 000000000..bd0440b1f --- /dev/null +++ b/src/shared/yagna/adapters/index.ts @@ -0,0 +1,2 @@ +export * from "./payment-api-adapter"; +export * from "./market-api-adapter"; diff --git a/src/shared/yagna/adapters/market-api-adapter.test.ts b/src/shared/yagna/adapters/market-api-adapter.test.ts new file mode 100644 index 000000000..55ab5a14d --- /dev/null +++ b/src/shared/yagna/adapters/market-api-adapter.test.ts @@ -0,0 +1,232 @@ +import { _, deepEqual, imock, instance, mock, reset, verify, when } from "@johanblumenberg/ts-mockito"; +import * as YaTsClient from "ya-ts-client"; +import { YagnaAgreementOperationEvent, YagnaApi } from "../yagnaApi"; +import { DemandRequestBody, MarketApiAdapter } from "./market-api-adapter"; +import { Demand, DemandSpecification, OfferProposal } from "../../../market"; +import { Subject, take } from "rxjs"; +import { Logger } from "../../utils"; +import { DemandBodyPrototype, IDemandRepository } from "../../../market/demand"; +import { IAgreementRepository } from "../../../market/agreement/agreement"; +import { IProposalRepository } from "../../../market/proposal"; + +const mockLogger = imock(); +const mockMarket = mock(YaTsClient.MarketApi.RequestorService); +const mockYagna = mock(YagnaApi); +const mockAgreementRepo = imock(); +const mockProposalRepo = imock(); +const mockDemandRepo = imock(); + +/** Test subject mocking the one exposed by YagnaApi */ +const agreementEvents$ = new Subject(); + +/** Global instance of the system under test */ +let api: MarketApiAdapter; + +jest.useFakeTimers(); + +beforeEach(() => { + reset(mockYagna); + reset(mockMarket); + + when(mockYagna.market).thenReturn(instance(mockMarket)); + when(mockYagna.agreementEvents$).thenReturn(agreementEvents$); + + api = new MarketApiAdapter( + instance(mockYagna), + instance(mockAgreementRepo), + instance(mockProposalRepo), + instance(mockDemandRepo), + instance(mockLogger), + ); +}); + +describe("Market API Adapter", () => { + const samplePrototype: DemandBodyPrototype = { + constraints: ["constraints"], + properties: [ + { + key: "golem.com.usage.vector", + value: ["golem.usage.duration_sec", "golem.usage.cpu_sec"], + }, + { + key: "golem.com.pricing.model.linear.coeffs", + value: [0.1, 0.1], + }, + { + key: "property-key-1", + value: "property-value-1", + }, + { + key: "property-key-2", + value: "property-value-2", + }, + ], + }; + + const expectedBody: DemandRequestBody = { + constraints: "constraints", + properties: { + "golem.com.usage.vector": ["golem.usage.duration_sec", "golem.usage.cpu_sec"], + "golem.com.pricing.model.linear.coeffs": [0.1, 0.1], + "property-key-1": "property-value-1", + "property-key-2": "property-value-2", + }, + }; + + describe("publishDemandSpecification()", () => { + it("should publish a demand", async () => { + const specification = new DemandSpecification(samplePrototype, "my-selected-payment-platform"); + + when(mockMarket.subscribeDemand(deepEqual(expectedBody))).thenResolve("demand-id"); + + const demand = await api.publishDemandSpecification(specification); + + verify(mockMarket.subscribeDemand(deepEqual(expectedBody))).once(); + expect(demand).toBeInstanceOf(Demand); + expect(demand.id).toBe("demand-id"); + expect(demand.details).toBe(specification); + }); + + it("should throw an error if the demand is not published", async () => { + const specification = new DemandSpecification(samplePrototype, "my-selected-payment-platform"); + + when(mockMarket.subscribeDemand(deepEqual(expectedBody))).thenResolve({ + message: "error publishing demand", + }); + + await expect(api.publishDemandSpecification(specification)).rejects.toThrow( + "Failed to subscribe to demand: error publishing demand", + ); + }); + }); + + describe("unpublishDemand()", () => { + it("should unpublish a demand", async () => { + const demand = new Demand("demand-id", new DemandSpecification(samplePrototype, "my-selected-payment-platform")); + + when(mockMarket.unsubscribeDemand("demand-id")).thenResolve({}); + + await api.unpublishDemand(demand); + + verify(mockMarket.unsubscribeDemand("demand-id")).once(); + }); + + it("should throw an error if the demand is not unpublished", async () => { + const demand = new Demand("demand-id", new DemandSpecification(samplePrototype, "my-selected-payment-platform")); + + when(mockMarket.unsubscribeDemand("demand-id")).thenResolve({ + message: "error unpublishing demand", + }); + + await expect(api.unpublishDemand(demand)).rejects.toThrow( + "Failed to unsubscribe from demand: error unpublishing demand", + ); + }); + }); + + describe("counterProposal()", () => { + it("should negotiate a proposal with the selected payment platform", async () => { + const specification = new DemandSpecification(samplePrototype, "my-selected-payment-platform"); + + const receivedProposal = new OfferProposal( + { + ...expectedBody, + proposalId: "proposal-id", + timestamp: "0000-00-00", + issuerId: "issuer-id", + state: "Initial", + }, + new Demand("demand-id", specification), + ); + + when(mockMarket.counterProposalDemand(_, _, _)).thenResolve("counter-id"); + + when(mockMarket.getProposalOffer("demand-id", "counter-id")).thenResolve({ + ...expectedBody, + proposalId: "counter-id", + timestamp: "0000-00-00", + issuerId: "issuer-id", + state: "Draft", + }); + + await api.counterProposal(receivedProposal, specification); + + verify( + mockMarket.counterProposalDemand( + "demand-id", + "proposal-id", + deepEqual({ + constraints: expectedBody.constraints, + properties: { + ...expectedBody.properties, + "golem.com.payment.chosen-platform": "my-selected-payment-platform", + }, + }), + ), + ).once(); + }); + it("should throw an error if the counter proposal fails", async () => { + const specification = new DemandSpecification(samplePrototype, "my-selected-payment-platform"); + const receivedProposal = new OfferProposal( + { + ...expectedBody, + proposalId: "proposal-id", + timestamp: "0000-00-00", + issuerId: "issuer-id", + state: "Initial", + }, + new Demand("demand-id", specification), + ); + + when(mockMarket.counterProposalDemand(_, _, _)).thenResolve({ + message: "error counter proposing", + }); + + await expect(api.counterProposal(receivedProposal, specification)).rejects.toThrow( + "Counter proposal failed error counter proposing", + ); + }); + }); + + describe("observeDemandResponse()", () => { + it("should long poll for proposals", (done) => { + const mockDemand = mock(Demand); + when(mockDemand.id).thenReturn("demand-id"); + const mockProposalDTO = imock(); + when(mockProposalDTO.issuerId).thenReturn("issuer-id"); + when(mockProposalDTO.properties).thenReturn({ + "golem.com.usage.vector": ["golem.usage.cpu_sec", "golem.usage.duration_sec"], + "golem.com.pricing.model.linear.coeffs": [0.0001, 0.00005, 0], + }); + const mockProposalEvent: YaTsClient.MarketApi.ProposalEventDTO = { + eventType: "ProposalEvent", + eventDate: "0000-00-00", + proposal: instance(mockProposalDTO), + }; + + when(mockMarket.collectOffers("demand-id")).thenResolve([mockProposalEvent, mockProposalEvent]); + + const proposal$ = api.collectMarketProposalEvents(instance(mockDemand)).pipe(take(4)); + + let proposalsEmitted = 0; + + proposal$.subscribe({ + error: (error) => { + done(error); + }, + next: () => { + proposalsEmitted++; + }, + complete: () => { + try { + expect(proposalsEmitted).toBe(4); + verify(mockMarket.collectOffers("demand-id")).twice(); + done(); + } catch (error) { + done(error); + } + }, + }); + }); + }); +}); diff --git a/src/shared/yagna/adapters/market-api-adapter.ts b/src/shared/yagna/adapters/market-api-adapter.ts new file mode 100644 index 000000000..1d9caf8f9 --- /dev/null +++ b/src/shared/yagna/adapters/market-api-adapter.ts @@ -0,0 +1,461 @@ +import { Observable, switchMap } from "rxjs"; +import { + Agreement, + AgreementEvent, + AgreementState, + Demand, + DemandSpecification, + GolemMarketError, + IMarketApi, + MarketErrorCode, + MarketProposalEvent, + OfferProposal, +} from "../../../market"; +import { YagnaApi } from "../yagnaApi"; +import { GolemInternalError, GolemUserError } from "../../error/golem-error"; +import { Logger } from "../../utils"; +import { DemandBodyPrototype, DemandPropertyValue, IDemandRepository } from "../../../market/demand"; +import { getMessageFromApiError } from "../../utils/apiErrorMessage"; +import { withTimeout } from "../../utils/timeout"; +import { AgreementOptions, IAgreementRepository } from "../../../market/agreement/agreement"; +import { IProposalRepository, MarketProposal, OfferCounterProposal } from "../../../market/proposal"; +import EventSource from "eventsource"; +import { ScanSpecification, ScannedOffer } from "../../../market/scan"; + +/** + * A bit more user-friendly type definition of DemandOfferBaseDTO from ya-ts-client + * + * That's probably one of the most confusing elements around Golem Protocol and the API specificiation: + * + * - Providers create Offers + * - Requestors create Demands + * - Demands are used to create a subscription for Proposals - Initial ones reflect the Offer that was matched with the Demand used to subscribe + * - Once the proposal is countered, it's countered with a "counter proposal" which is no longer Offer + Demand, + * but rather a sketch of the agreement - here both parties try to agree on the values of certain properties that + * are interesting from their perspective. These "negotiated proposals (of) ...." are buit using DemandOffeBaseDTO + * + * #FIXME yagna - feedback in the note above + */ +export type DemandRequestBody = { + properties: Record; + constraints: string; +}; + +export class MarketApiAdapter implements IMarketApi { + constructor( + private readonly yagnaApi: YagnaApi, + private readonly agreementRepo: IAgreementRepository, + private readonly proposalRepo: IProposalRepository, + private readonly demandRepo: IDemandRepository, + private readonly logger: Logger, + ) {} + + async publishDemandSpecification(spec: DemandSpecification): Promise { + const idOrError = await this.yagnaApi.market.subscribeDemand(this.buildDemandRequestBody(spec.prototype)); + + if (typeof idOrError !== "string") { + throw new Error(`Failed to subscribe to demand: ${idOrError.message}`); + } + + const demand = new Demand(idOrError, spec); + this.demandRepo.add(demand); + + return demand; + } + + async unpublishDemand(demand: Demand): Promise { + const result = await this.yagnaApi.market.unsubscribeDemand(demand.id); + + if (result?.message) { + throw new Error(`Failed to unsubscribe from demand: ${result.message}`); + } + } + + collectMarketProposalEvents(demand: Demand): Observable { + return new Observable((observer) => { + let isCancelled = false; + + const longPoll = async () => { + if (isCancelled) { + return; + } + + try { + for (const event of await this.yagnaApi.market.collectOffers(demand.id)) { + const timestamp = new Date(Date.parse(event.eventDate)); + + switch (event.eventType) { + case "ProposalEvent": + { + try { + // @ts-expect-error FIXME #ya-ts-client, #ya-client: Fix mappings and type discriminators + const offerProposal = new OfferProposal(event.proposal, demand); + this.proposalRepo.add(offerProposal); + observer.next({ + type: "ProposalReceived", + proposal: offerProposal, + timestamp, + }); + } catch (err) { + observer.error(err); + this.logger.error("Failed to create offer proposal from the event", { err, event, demand }); + } + } + break; + case "ProposalRejectedEvent": { + // @ts-expect-error FIXME #ya-ts-client, #ya-client: Fix mappings and type discriminators + const { proposalId, reason } = event; + + const marketProposal = this.proposalRepo.getById(proposalId); + + if (marketProposal && this.isOfferCounterProposal(marketProposal)) { + observer.next({ + type: "ProposalRejected", + counterProposal: marketProposal, + reason: reason.message, + timestamp, + }); + } else { + this.logger.error( + "Could not locate counter proposal with ID issued by the Requestor while handling ProposalRejectedEvent", + { + event, + }, + ); + } + break; + } + case "PropertyQueryEvent": + observer.next({ + type: "PropertyQueryReceived", + timestamp, + }); + break; + default: + this.logger.warn("Unsupported demand subscription event", { event }); + } + } + } catch (error) { + // when the demand is unsubscribed the long poll will reject with a 404 + if ("status" in error && error.status === 404) { + return; + } + + this.logger.error("Polling yagna for offer proposal events failed", error); + observer.error(error); + } + + void longPoll(); + }; + + void longPoll(); + + return () => { + isCancelled = true; + }; + }); + } + + async counterProposal(receivedProposal: OfferProposal, demand: DemandSpecification): Promise { + const bodyClone = structuredClone(this.buildDemandRequestBody(demand.prototype)); + + bodyClone.properties["golem.com.payment.chosen-platform"] = demand.paymentPlatform; + + const maybeNewId = await this.yagnaApi.market.counterProposalDemand( + receivedProposal.demand.id, + receivedProposal.id, + bodyClone, + ); + this.logger.debug("Proposal counter result from yagna", { result: maybeNewId }); + + if (typeof maybeNewId !== "string") { + throw new GolemInternalError(`Counter proposal failed ${maybeNewId.message}`); + } + + const dto = await this.yagnaApi.market.getProposalOffer(receivedProposal.demand.id, maybeNewId); + + const counterProposal = new OfferCounterProposal(dto); + this.proposalRepo.add(counterProposal); + return counterProposal; + } + + async rejectProposal(receivedProposal: OfferProposal, reason: string): Promise { + try { + const result = await this.yagnaApi.market.rejectProposalOffer(receivedProposal.demand.id, receivedProposal.id, { + message: reason, + }); + + this.logger.debug("Proposal rejection result from yagna", { response: result }); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemMarketError( + `Failed to reject proposal. ${message}`, + MarketErrorCode.ProposalRejectionFailed, + error, + ); + } + } + + private buildDemandRequestBody(decorations: Partial): DemandRequestBody { + let constraints: string; + + if (!decorations.constraints?.length) constraints = "(&)"; + else if (decorations.constraints.length == 1) constraints = decorations.constraints[0]; + else constraints = `(&${decorations.constraints.join("\n\t")})`; + + const properties: Record = {}; + decorations.properties?.forEach((prop) => (properties[prop.key] = prop.value)); + + return { constraints, properties }; + } + + public async getPaymentRelatedDemandDecorations(allocationId: string): Promise { + return this.yagnaApi.payment.getDemandDecorations([allocationId]); + } + + async confirmAgreement(agreement: Agreement, options?: AgreementOptions): Promise { + try { + // FIXME #yagna, If we don't provide the app-session ID when confirming the agreement, we won't be able to collect invoices with that app-session-id + // it's hard to know when the appSessionId is mandatory and when it isn't + this.logger.debug("Confirming agreement by Requestor", { agreementId: agreement.id }); + await this.yagnaApi.market.confirmAgreement(agreement.id, this.yagnaApi.appSessionId); + this.logger.debug("Waiting for agreement approval by Provider", { agreementId: agreement.id }); + await this.yagnaApi.market.waitForApproval(agreement.id, options?.waitingForApprovalTimeoutSec || 60); + this.logger.debug(`Agreement approved by Provider`, { agreementId: agreement.id }); + + // Get fresh copy + return this.agreementRepo.getById(agreement.id); + } catch (error) { + throw new GolemMarketError( + `Unable to confirm agreement with provider`, + MarketErrorCode.AgreementApprovalFailed, + error, + ); + } + } + + async createAgreement(proposal: OfferProposal, options?: AgreementOptions): Promise { + const expirationSec = options?.expirationSec || 3600; + + try { + const agreementProposalRequest = { + proposalId: proposal.id, + validTo: new Date(+new Date() + expirationSec * 1000).toISOString(), + }; + + const agreementId = await withTimeout(this.yagnaApi.market.createAgreement(agreementProposalRequest), 30000); + + if (typeof agreementId !== "string") { + throw new GolemMarketError( + `Unable to create agreement. Invalid response from the server`, + MarketErrorCode.ResourceRentalCreationFailed, + ); + } + + const agreement = await this.agreementRepo.getById(agreementId); + + this.logger.debug(`Agreement created`, { + agreement, + proposalId: proposal.id, + demandId: proposal.demand.id, + }); + + return agreement; + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemMarketError( + `Unable to create agreement ${message}`, + MarketErrorCode.ResourceRentalCreationFailed, + error, + ); + } + } + + async proposeAgreement(proposal: OfferProposal, options?: AgreementOptions): Promise { + const agreement = await this.createAgreement(proposal, options); + const confirmed = await this.confirmAgreement(agreement); + const state = confirmed.getState(); + + if (state !== "Approved") { + throw new GolemMarketError( + `Agreement ${agreement.id} cannot be approved. Current state: ${state}`, + MarketErrorCode.AgreementApprovalFailed, + ); + } + + this.logger.debug("Established agreement", agreement); + + return confirmed; + } + + getAgreement(id: string): Promise { + return this.agreementRepo.getById(id); + } + + async getAgreementState(id: string): Promise { + const entry = await this.agreementRepo.getById(id); + return entry.getState(); + } + + async terminateAgreement(agreement: Agreement, reason: string = "Finished"): Promise { + try { + // Re-fetch entity before acting to be sure that we don't terminate a terminated activity + const current = await this.agreementRepo.getById(agreement.id); + + if (current.getState() === "Terminated") { + throw new GolemUserError("You can not terminate an agreement that's already terminated"); + } + + await withTimeout( + this.yagnaApi.market.terminateAgreement(current.id, { + message: reason, + }), + 30000, + ); + + this.logger.debug(`Agreement terminated`, { id: agreement.id, reason }); + + return this.agreementRepo.getById(agreement.id); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemMarketError( + `Unable to terminate agreement ${agreement.id}. ${message}`, + MarketErrorCode.ResourceRentalTerminationFailed, + error, + ); + } + } + + public collectAgreementEvents(): Observable { + return this.yagnaApi.agreementEvents$.pipe( + switchMap( + (event) => + new Observable((observer) => { + try { + this.logger.debug("Market API Adapter received agreement event", { event }); + + const timestamp = new Date(Date.parse(event.eventDate)); + + // @ts-expect-error FIXME #yagna, wasn't this fixed? {@issue https://github.com/golemfactory/yagna/pull/3136} + const eventType = event.eventType || event.eventtype; + + this.getAgreement(event.agreementId) + .then((agreement) => { + switch (eventType) { + case "AgreementApprovedEvent": + observer.next({ + type: "AgreementApproved", + agreement, + timestamp, + }); + break; + case "AgreementTerminatedEvent": + observer.next({ + type: "AgreementTerminated", + agreement, + // @ts-expect-error FIXME #ya-ts-client event type discrimination doesn't work nicely with the current generator + terminatedBy: event.terminator, + // @ts-expect-error FIXME #ya-ts-client event type discrimination doesn't work nicely with the current generator + reason: event.reason.message, + timestamp, + }); + break; + case "AgreementRejectedEvent": + observer.next({ + type: "AgreementRejected", + agreement, + // @ts-expect-error FIXME #ya-ts-client event type discrimination doesn't work nicely with the current generator + reason: event.reason.message, + timestamp, + }); + break; + case "AgreementCancelledEvent": + observer.next({ + type: "AgreementCancelled", + agreement, + timestamp, + }); + break; + default: + this.logger.warn("Unsupported agreement event type for event", { event }); + break; + } + }) + .catch((err) => this.logger.error("Failed to load agreement", { agreementId: event.agreementId, err })); + } catch (err) { + const golemMarketError = new GolemMarketError( + "Error while processing agreement event from yagna", + MarketErrorCode.InternalError, + err, + ); + this.logger.error(golemMarketError.message, { event, err }); + observer.error(err); + } + }), + ), + ); + } + + private isOfferCounterProposal(proposal: MarketProposal): proposal is OfferCounterProposal { + return proposal.issuer === "Requestor"; + } + + public scan(spec: ScanSpecification): Observable { + const ac = new AbortController(); + return new Observable((observer) => { + this.yagnaApi.market + .beginScan({ + type: "offer", + ...this.buildDemandRequestBody(spec), + }) + .then((iterator) => { + if (typeof iterator !== "string") { + throw new Error(`Something went wrong while starting the scan, ${iterator.message}`); + } + return iterator; + }) + .then(async (iterator) => { + const cleanupIterator = () => this.yagnaApi.market.endScan(iterator); + + if (ac.signal.aborted) { + await cleanupIterator(); + return; + } + + const eventSource = new EventSource( + `${this.yagnaApi.market.httpRequest.config.BASE}/scan/${iterator}/events`, + { + headers: { + Accept: "text/event-stream", + Authorization: `Bearer ${this.yagnaApi.yagnaOptions.apiKey}`, + }, + }, + ); + + eventSource.addEventListener("offer", (event) => { + try { + const parsed = JSON.parse(event.data); + observer.next(new ScannedOffer(parsed)); + } catch (error) { + observer.error(error); + } + }); + eventSource.addEventListener("error", (error) => observer.error(error)); + + ac.signal.onabort = async () => { + eventSource.close(); + await cleanupIterator(); + }; + }) + .catch((error) => { + const message = getMessageFromApiError(error); + observer.error( + new GolemMarketError(`Error while scanning for offers. ${message}`, MarketErrorCode.ScanFailed, error), + ); + }); + return () => { + ac.abort(); + }; + }); + } +} diff --git a/src/shared/yagna/adapters/network-api-adapter.ts b/src/shared/yagna/adapters/network-api-adapter.ts new file mode 100644 index 000000000..d4880c04c --- /dev/null +++ b/src/shared/yagna/adapters/network-api-adapter.ts @@ -0,0 +1,83 @@ +import { YagnaApi } from "../yagnaApi"; +import { GolemNetworkError, INetworkApi, Network, NetworkErrorCode, NetworkNode } from "../../../network"; +import { getMessageFromApiError } from "../../utils/apiErrorMessage"; + +export class NetworkApiAdapter implements INetworkApi { + constructor(private readonly yagnaApi: YagnaApi) {} + + async createNetwork(options: { ip: string; mask?: string; gateway?: string }): Promise { + try { + const { id, ip, mask, gateway } = await this.yagnaApi.net.createNetwork(options); + return new Network(id, ip, mask, gateway); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemNetworkError( + `Unable to create network. ${message}`, + NetworkErrorCode.NetworkCreationFailed, + undefined, + error, + ); + } + } + async removeNetwork(network: Network): Promise { + try { + await this.yagnaApi.net.removeNetwork(network.id); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemNetworkError( + `Unable to remove network. ${message}`, + NetworkErrorCode.NetworkRemovalFailed, + network.getNetworkInfo(), + error, + ); + } + } + async createNetworkNode(network: Network, nodeId: string, nodeIp: string): Promise { + try { + await this.yagnaApi.net.addNode(network.id, { id: nodeId, ip: nodeIp }); + const networkNode = new NetworkNode( + nodeId, + nodeIp, + network.getNetworkInfo.bind(network), + this.yagnaApi.net.httpRequest.config.BASE, + ); + + return networkNode; + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemNetworkError( + `Unable to add node to network. ${message}`, + NetworkErrorCode.NodeAddingFailed, + network.getNetworkInfo(), + error, + ); + } + } + async removeNetworkNode(network: Network, node: NetworkNode): Promise { + try { + await this.yagnaApi.net.removeNode(network.id, node.id); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemNetworkError( + `Unable to remove network node. ${message}`, + NetworkErrorCode.NodeRemovalFailed, + network.getNetworkInfo(), + error, + ); + } + } + + async getIdentity() { + try { + return await this.yagnaApi.identity.getIdentity().then((res) => res.identity); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemNetworkError( + `Unable to get requestor identity. ${message}`, + NetworkErrorCode.GettingIdentityFailed, + undefined, + error, + ); + } + } +} diff --git a/src/shared/yagna/adapters/payment-api-adapter.ts b/src/shared/yagna/adapters/payment-api-adapter.ts new file mode 100644 index 000000000..9e6156497 --- /dev/null +++ b/src/shared/yagna/adapters/payment-api-adapter.ts @@ -0,0 +1,222 @@ +import { from, mergeMap, of, Subject } from "rxjs"; +import { + Allocation, + CreateAllocationParams, + DebitNote, + GolemPaymentError, + Invoice, + IPaymentApi, + PaymentErrorCode, +} from "../../../payment"; +import { IInvoiceRepository } from "../../../payment/invoice"; +import { Logger, YagnaApi } from "../../utils"; +import { IDebitNoteRepository } from "../../../payment/debit_note"; +import { getMessageFromApiError } from "../../utils/apiErrorMessage"; + +export class PaymentApiAdapter implements IPaymentApi { + public receivedInvoices$ = new Subject(); + + public receivedDebitNotes$ = new Subject(); + + constructor( + private readonly yagna: YagnaApi, + private readonly invoiceRepo: IInvoiceRepository, + private readonly debitNoteRepo: IDebitNoteRepository, + private readonly logger: Logger, + ) {} + + async connect() { + this.logger.debug("Connecting Payment API Adapter"); + + from(this.yagna.invoiceEvents$) + .pipe( + mergeMap((invoice) => { + if (invoice && invoice.invoiceId) { + return this.invoiceRepo.getById(invoice.invoiceId); + } else { + return of(); + } + }), + ) + .subscribe({ + next: (event) => this.receivedInvoices$.next(event), + error: (err) => this.receivedInvoices$.error(err), + complete: () => this.logger.debug("Finished reading InvoiceEvents"), + }); + + from(this.yagna.debitNoteEvents$) + .pipe( + mergeMap((debitNote) => { + if (debitNote && debitNote.debitNoteId) { + return this.debitNoteRepo.getById(debitNote.debitNoteId); + } else { + return of(); + } + }), + ) + .subscribe({ + next: (event) => this.receivedDebitNotes$.next(event), + error: (err) => this.receivedDebitNotes$.error(err), + complete: () => this.logger.debug("Finished reading DebitNoteEvents"), + }); + + this.logger.debug("Payment API Adapter connected"); + } + + getInvoice(id: string): Promise { + return this.invoiceRepo.getById(id); + } + + getDebitNote(id: string): Promise { + return this.debitNoteRepo.getById(id); + } + + async acceptInvoice(invoice: Invoice, allocation: Allocation, amount: string): Promise { + try { + await this.yagna.payment.acceptInvoice(invoice.id, { + totalAmountAccepted: amount, + allocationId: allocation.id, + }); + + return this.invoiceRepo.getById(invoice.id); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Could not accept invoice. ${message}`, + PaymentErrorCode.InvoiceAcceptanceFailed, + allocation, + invoice.provider, + ); + } + } + + async rejectInvoice(invoice: Invoice, reason: string): Promise { + try { + await this.yagna.payment.rejectInvoice(invoice.id, { + rejectionReason: "BAD_SERVICE", + totalAmountAccepted: "0.00", + message: reason, + }); + + return this.invoiceRepo.getById(invoice.id); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Could not reject invoice. ${message}`, + PaymentErrorCode.InvoiceRejectionFailed, + undefined, + invoice.provider, + ); + } + } + + async acceptDebitNote(debitNote: DebitNote, allocation: Allocation, amount: string): Promise { + try { + await this.yagna.payment.acceptDebitNote(debitNote.id, { + totalAmountAccepted: amount, + allocationId: allocation.id, + }); + + return this.debitNoteRepo.getById(debitNote.id); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Could not accept debit note. ${message}`, + PaymentErrorCode.DebitNoteAcceptanceFailed, + allocation, + debitNote.provider, + ); + } + } + + async rejectDebitNote(debitNote: DebitNote): Promise { + try { + // TODO: this endpoint is not implemented in Yagna, it always responds 501:NotImplemented. + // Reported in https://github.com/golemfactory/yagna/issues/1249 + // await this.yagna.payment.rejectDebitNote(debitNote.id, { + // rejectionReason: "BAD_SERVICE", + // totalAmountAccepted: "0.00", + // message: reason, + // }); + + return this.debitNoteRepo.getById(debitNote.id); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Could not reject debit note. ${message}`, + PaymentErrorCode.DebitNoteRejectionFailed, + undefined, + debitNote.provider, + error, + ); + } + } + + async getAllocation(id: string) { + try { + const model = await this.yagna.payment.getAllocation(id); + return new Allocation(model); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Could not retrieve allocation. ${message}`, + PaymentErrorCode.AllocationCreationFailed, + undefined, + undefined, + error, + ); + } + } + + async createAllocation(params: CreateAllocationParams): Promise { + try { + const { identity: address } = await this.yagna.identity.getIdentity(); + + const now = new Date(); + + const model = await this.yagna.payment.createAllocation({ + totalAmount: params.budget.toString(), + paymentPlatform: params.paymentPlatform, + address, + timestamp: now.toISOString(), + timeout: new Date(+now + params.expirationSec * 1000).toISOString(), + makeDeposit: false, + remainingAmount: "", + spentAmount: "", + allocationId: "", + deposit: params.deposit, + }); + + this.logger.debug( + `Allocation ${model.allocationId} has been created for address ${address} using payment platform ${params.paymentPlatform}`, + ); + + const allocation = new Allocation(model); + + return allocation; + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Could not create new allocation. ${message}`, + PaymentErrorCode.AllocationCreationFailed, + undefined, + undefined, + error, + ); + } + } + + async releaseAllocation(allocation: Allocation): Promise { + try { + return this.yagna.payment.releaseAllocation(allocation.id); + } catch (error) { + throw new GolemPaymentError( + `Could not release allocation. ${error.response?.data?.message || error}`, + PaymentErrorCode.AllocationReleaseFailed, + allocation, + undefined, + error, + ); + } + } +} diff --git a/src/shared/yagna/event-reader.ts b/src/shared/yagna/event-reader.ts new file mode 100644 index 000000000..9322b9d7b --- /dev/null +++ b/src/shared/yagna/event-reader.ts @@ -0,0 +1,87 @@ +import { Logger } from "../utils"; +import { Subject } from "rxjs"; +import { EventDTO } from "ya-ts-client/dist/market-api"; +import { waitForCondition } from "../utils/wait"; + +export type CancellablePoll = { + /** User defined name of the event stream for ease of debugging */ + eventType: string; + + /** Flag indicating if the reader is finished and no longer polling */ + isFinished: boolean; + + /** Triggers the poll using the fetcher provided when the CancellablePoll was created */ + pollValues: () => AsyncGenerator; + + /** + * Cancels the polling operations, stopping the reader + */ + cancel: () => Promise; +}; + +type CancellablePromise = Promise & { cancel: () => void }; +export type CancellableEventsFetcherWithCursor = ( + lastEventTimestamp: string, +) => CancellablePromise; + +export class EventReader { + public constructor(private readonly logger: Logger) {} + + public async pollToSubject(generator: AsyncGenerator, subject: Subject) { + for await (const value of generator) { + subject.next(value); + } + + subject.complete(); + } + + public createReader( + eventType: string, + eventsFetcher: CancellableEventsFetcherWithCursor, + ): CancellablePoll { + let isFinished = false; + let keepReading = true; + let currentPoll: CancellablePromise | null = null; + let lastTimestamp = new Date().toISOString(); + + const logger = this.logger; + + return { + eventType, + isFinished, + pollValues: async function* () { + while (keepReading) { + try { + currentPoll = eventsFetcher(lastTimestamp); + const events = await currentPoll; + logger.debug("Polled events from Yagna", { + eventType, + count: events.length, + lastEventTimestamp: lastTimestamp, + }); + for (const event of events) { + yield event; + lastTimestamp = event.eventDate; + } + } catch (error) { + if (typeof error === "object" && error.name === "CancelError") { + logger.debug("Polling was cancelled", { eventType }); + continue; + } + logger.error("Error fetching events from Yagna", { eventType, error }); + } + } + logger.debug("Stopped reading events", { eventType }); + isFinished = true; + }, + cancel: async function () { + keepReading = false; + if (currentPoll) { + currentPoll.cancel(); + } + await waitForCondition(() => isFinished, { intervalSeconds: 0 }); + logger.debug("Cancelled reading the events", { eventType }); + }, + }; + } +} diff --git a/src/shared/yagna/index.ts b/src/shared/yagna/index.ts new file mode 100644 index 000000000..7b0bc7b55 --- /dev/null +++ b/src/shared/yagna/index.ts @@ -0,0 +1,4 @@ +export * from "./adapters"; +export * from "./repository"; +export * from "./yagnaApi"; +export * from "./event-reader"; diff --git a/src/shared/yagna/repository/activity-repository.ts b/src/shared/yagna/repository/activity-repository.ts new file mode 100644 index 000000000..aae7d15e7 --- /dev/null +++ b/src/shared/yagna/repository/activity-repository.ts @@ -0,0 +1,60 @@ +import { Activity, ActivityStateEnum, IActivityRepository } from "../../../activity/activity"; +import { ActivityApi } from "ya-ts-client"; +import { IAgreementRepository } from "../../../market/agreement/agreement"; +import { getMessageFromApiError } from "../../utils/apiErrorMessage"; +import { GolemWorkError, WorkErrorCode } from "../../../activity"; +import { CacheService } from "../../cache/CacheService"; + +export class ActivityRepository implements IActivityRepository { + private stateCache: CacheService = new CacheService(); + + constructor( + private readonly state: ActivityApi.RequestorStateService, + private readonly agreementRepo: IAgreementRepository, + ) {} + + async getById(id: string): Promise { + try { + const agreementId = await this.state.getActivityAgreement(id); + const agreement = await this.agreementRepo.getById(agreementId); + const previousState = this.stateCache.get(id) ?? ActivityStateEnum.New; + const state = await this.getStateOfActivity(id); + const usage = await this.state.getActivityUsage(id); + + return new Activity(id, agreement, state ?? ActivityStateEnum.Unknown, previousState, usage); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemWorkError( + `Failed to get activity: ${message}`, + WorkErrorCode.ActivityStatusQueryFailed, + undefined, + undefined, + undefined, + error, + ); + } + } + + async getStateOfActivity(id: string): Promise { + try { + const yagnaStateResponse = await this.state.getActivityState(id); + if (!yagnaStateResponse || yagnaStateResponse.state[0] === null) { + return ActivityStateEnum.Unknown; + } + + const state = ActivityStateEnum[yagnaStateResponse.state[0]]; + this.stateCache.set(id, state); + return state; + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemWorkError( + `Failed to get activity state: ${message}`, + WorkErrorCode.ActivityStatusQueryFailed, + undefined, + undefined, + undefined, + error, + ); + } + } +} diff --git a/src/shared/yagna/repository/agreement-repository.ts b/src/shared/yagna/repository/agreement-repository.ts new file mode 100644 index 000000000..6bfc825c0 --- /dev/null +++ b/src/shared/yagna/repository/agreement-repository.ts @@ -0,0 +1,31 @@ +import { Agreement, IAgreementRepository } from "../../../market/agreement/agreement"; +import { MarketApi } from "ya-ts-client"; +import { GolemInternalError } from "../../error/golem-error"; +import { IDemandRepository } from "../../../market/demand/demand"; +import { getMessageFromApiError } from "../../utils/apiErrorMessage"; +import { GolemMarketError, MarketErrorCode } from "../../../market"; + +export class AgreementRepository implements IAgreementRepository { + constructor( + private readonly api: MarketApi.RequestorService, + private readonly demandRepo: IDemandRepository, + ) {} + + async getById(id: string): Promise { + let dto; + try { + dto = await this.api.getAgreement(id); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemMarketError(`Failed to get agreement: ${message}`, MarketErrorCode.CouldNotGetAgreement, error); + } + const { demandId } = dto.demand; + const demand = this.demandRepo.getById(demandId); + + if (!demand) { + throw new GolemInternalError(`Could not find information for demand ${demandId} of agreement ${id}`); + } + const agreement = new Agreement(id, dto, demand); + return agreement; + } +} diff --git a/src/shared/yagna/repository/debit-note-repository.ts b/src/shared/yagna/repository/debit-note-repository.ts new file mode 100644 index 000000000..e921026dd --- /dev/null +++ b/src/shared/yagna/repository/debit-note-repository.ts @@ -0,0 +1,48 @@ +import { DebitNote, IDebitNoteRepository } from "../../../payment/debit_note"; +import { MarketApi, PaymentApi } from "ya-ts-client"; +import { getMessageFromApiError } from "../../utils/apiErrorMessage"; +import { GolemPaymentError, PaymentErrorCode } from "../../../payment"; +import { GolemMarketError, MarketErrorCode } from "../../../market"; + +export class DebitNoteRepository implements IDebitNoteRepository { + constructor( + private readonly paymentClient: PaymentApi.RequestorService, + private readonly marketClient: MarketApi.RequestorService, + ) {} + + async getById(id: string): Promise { + let model; + let agreement; + try { + model = await this.paymentClient.getDebitNote(id); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Failed to get debit note: ${message}`, + PaymentErrorCode.CouldNotGetDebitNote, + undefined, + undefined, + error, + ); + } + + try { + agreement = await this.marketClient.getAgreement(model.agreementId); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemMarketError( + `Failed to get agreement for debit note: ${message}`, + MarketErrorCode.CouldNotGetAgreement, + error, + ); + } + + const providerInfo = { + id: model.issuerId, + walletAddress: model.payeeAddr, + name: agreement.offer.properties["golem.node.id.name"], + }; + + return new DebitNote(model, providerInfo); + } +} diff --git a/src/shared/yagna/repository/demand-repository.ts b/src/shared/yagna/repository/demand-repository.ts new file mode 100644 index 000000000..13e55d4ea --- /dev/null +++ b/src/shared/yagna/repository/demand-repository.ts @@ -0,0 +1,23 @@ +import { Demand, IDemandRepository } from "../../../market/demand/demand"; +import { MarketApi } from "ya-ts-client"; +import { CacheService } from "../../cache/CacheService"; + +export class DemandRepository implements IDemandRepository { + constructor( + private readonly api: MarketApi.RequestorService, + private readonly cache: CacheService, + ) {} + + getById(id: string): Demand | undefined { + return this.cache.get(id); + } + + add(demand: Demand): Demand { + this.cache.set(demand.id, demand); + return demand; + } + + getAll(): Demand[] { + return this.cache.getAll(); + } +} diff --git a/src/shared/yagna/repository/index.ts b/src/shared/yagna/repository/index.ts new file mode 100644 index 000000000..9ef543a11 --- /dev/null +++ b/src/shared/yagna/repository/index.ts @@ -0,0 +1,2 @@ +export * from "./invoice-repository"; +export * from "./debit-note-repository"; diff --git a/src/shared/yagna/repository/invoice-repository.ts b/src/shared/yagna/repository/invoice-repository.ts new file mode 100644 index 000000000..82c2eb8fb --- /dev/null +++ b/src/shared/yagna/repository/invoice-repository.ts @@ -0,0 +1,47 @@ +import { IInvoiceRepository, Invoice } from "../../../payment/invoice"; +import { MarketApi, PaymentApi } from "ya-ts-client"; +import { getMessageFromApiError } from "../../utils/apiErrorMessage"; +import { GolemPaymentError, PaymentErrorCode } from "../../../payment"; +import { GolemMarketError, MarketErrorCode } from "../../../market"; + +export class InvoiceRepository implements IInvoiceRepository { + constructor( + private readonly paymentClient: PaymentApi.RequestorService, + private readonly marketClient: MarketApi.RequestorService, + ) {} + + async getById(id: string): Promise { + let model; + let agreement; + try { + model = await this.paymentClient.getInvoice(id); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemPaymentError( + `Failed to get debit note: ${message}`, + PaymentErrorCode.CouldNotGetInvoice, + undefined, + undefined, + error, + ); + } + + try { + agreement = await this.marketClient.getAgreement(model.agreementId); + } catch (error) { + const message = getMessageFromApiError(error); + throw new GolemMarketError( + `Failed to get agreement for invoice: ${message}`, + MarketErrorCode.CouldNotGetAgreement, + error, + ); + } + const providerInfo = { + id: model.issuerId, + walletAddress: model.payeeAddr, + name: agreement.offer.properties["golem.node.id.name"], + }; + + return new Invoice(model, providerInfo); + } +} diff --git a/src/shared/yagna/repository/proposal-repository.ts b/src/shared/yagna/repository/proposal-repository.ts new file mode 100644 index 000000000..5348da4d8 --- /dev/null +++ b/src/shared/yagna/repository/proposal-repository.ts @@ -0,0 +1,36 @@ +import { OfferProposal } from "../../../market/proposal/offer-proposal"; +import { MarketApi, IdentityApi } from "ya-ts-client"; +import { Demand, GolemMarketError, MarketErrorCode } from "../../../market"; +import { CacheService } from "../../cache/CacheService"; +import { IProposalRepository, MarketProposal } from "../../../market/proposal/market-proposal"; +import { OfferCounterProposal } from "../../../market/proposal/offer-counter-proposal"; + +export class ProposalRepository implements IProposalRepository { + constructor( + private readonly marketService: MarketApi.RequestorService, + private readonly identityService: IdentityApi.DefaultService, + private readonly cache: CacheService, + ) {} + + add(proposal: MarketProposal) { + this.cache.set(proposal.id, proposal); + return proposal; + } + + getById(id: string) { + return this.cache.get(id); + } + + async getByDemandAndId(demand: Demand, id: string): Promise { + try { + const dto = await this.marketService.getProposalOffer(demand.id, id); + const identity = await this.identityService.getIdentity(); + const isIssuedByRequestor = identity.identity === dto.issuerId ? "Requestor" : "Provider"; + + return isIssuedByRequestor ? new OfferCounterProposal(dto) : new OfferProposal(dto, demand); + } catch (error) { + const message = error.message; + throw new GolemMarketError(`Failed to get proposal: ${message}`, MarketErrorCode.CouldNotGetProposal, error); + } + } +} diff --git a/src/utils/yagna/yagna.spec.ts b/src/shared/yagna/yagna.spec.ts similarity index 65% rename from src/utils/yagna/yagna.spec.ts rename to src/shared/yagna/yagna.spec.ts index 40dea1724..a3de7ff1b 100644 --- a/src/utils/yagna/yagna.spec.ts +++ b/src/shared/yagna/yagna.spec.ts @@ -1,20 +1,17 @@ -import { MIN_SUPPORTED_YAGNA, Yagna } from "./yagna"; +import { MIN_SUPPORTED_YAGNA, YagnaApi } from "./yagnaApi"; import { imock, instance, spy, when } from "@johanblumenberg/ts-mockito"; -import { IdentityModel } from "./identity"; -import { GolemPlatformError } from "../../error/golem-error"; +import { GolemPlatformError } from "../error/golem-error"; +import { IdentityApi } from "ya-ts-client"; -const mockFetch = jest.spyOn(global, "fetch"); -const response = imock(); - -const mockIdentityModel = imock(); +const mockIdentityModel = imock(); describe("Yagna Utils", () => { describe("Yagna version support checking", () => { - describe("Positive cases - given min supported version is 0.13.2", () => { - it.each(["0.13.2", "0.15.0-rc5", "pre-rel-v0.15.0-rc5"])( + describe("Positive cases - given min supported version is 0.15.0", () => { + it.each(["0.15.2", "0.15.3-rc5", "pre-rel-v0.15.3-rc5"])( "should not throw when connect is called and the yagna version is %s", async (version) => { - when(response.json()).thenResolve({ + const mockVersionResponse = { current: { version: `${version}`, name: `v${version}`, @@ -23,15 +20,15 @@ describe("Yagna Utils", () => { insertionTs: "2023-12-07T18:22:45", updateTs: "2023-12-07T18:22:45", }, - pending: null, - }); - mockFetch.mockResolvedValue(instance(response)); - - const y = new Yagna({ + }; + const y = new YagnaApi({ apiKey: "test-key", }); - const spyIdentity = spy(y.getApi().identity); + const spyVersion = spy(y.version); + when(spyVersion.getVersion()).thenResolve(mockVersionResponse); + + const spyIdentity = spy(y.identity); const model = instance(mockIdentityModel); when(spyIdentity.getIdentity()).thenResolve(model); @@ -43,32 +40,32 @@ describe("Yagna Utils", () => { describe("Negative cases", () => { it("should throw when connect is called and yagna version is too low", async () => { - when(response.json()).thenResolve({ + const mockVersionResponse = { current: { - version: "0.12.0", - name: "v0.12.0", + version: "0.15.0", + name: "v0.15.0", seen: false, releaseTs: "2023-12-07T14:23:48", insertionTs: "2023-12-07T18:22:45", updateTs: "2023-12-07T18:22:45", }, - pending: null, - }); - mockFetch.mockResolvedValue(instance(response)); + }; - const y = new Yagna({ + const y = new YagnaApi({ apiKey: "test-key", }); + const spyVersion = spy(y.version); + when(spyVersion.getVersion()).thenResolve(mockVersionResponse); await expect(() => y.connect()).rejects.toMatchError( new GolemPlatformError( - `You run yagna in version 0.12.0 and the minimal version supported by the SDK is ${MIN_SUPPORTED_YAGNA}. Please consult the golem-js README to find matching SDK version or upgrade your yagna installation.`, + `You run yagna in version 0.15.0 and the minimal version supported by the SDK is ${MIN_SUPPORTED_YAGNA}. Please consult the golem-js README to find matching SDK version or upgrade your yagna installation.`, ), ); }); it("should throw when connect is called and yagna version is somehow broken", async () => { - when(response.json()).thenResolve({ + const mockVersionResponse = { current: { version: "broken", name: "broken", @@ -77,29 +74,31 @@ describe("Yagna Utils", () => { insertionTs: "2023-12-07T18:22:45", updateTs: "2023-12-07T18:22:45", }, - pending: null, - }); - mockFetch.mockResolvedValue(instance(response)); + }; - const y = new Yagna({ + const y = new YagnaApi({ apiKey: "test-key", }); + const spyVersion = spy(y.version); + when(spyVersion.getVersion()).thenResolve(mockVersionResponse); await expect(() => y.connect()).rejects.toMatchError( new GolemPlatformError( - `Unreadable yana version 'broken'. Can't proceed without checking yagna version support status.`, + `Unreadable yagna version 'broken'. Can't proceed without checking yagna version support status.`, ), ); }); it("should throw an GolemError if fetching of the version information will fail", async () => { const testError = new Error("Something bad happened when trying to read yagna version via API"); - mockFetch.mockRejectedValue(testError); - const y = new Yagna({ + const y = new YagnaApi({ apiKey: "test-key", }); + const spyVersion = spy(y.version); + when(spyVersion.getVersion()).thenReject(testError); + await expect(() => y.connect()).rejects.toMatchError( new GolemPlatformError(`Failed to establish yagna version due to: ${testError}`, testError), ); diff --git a/src/shared/yagna/yagnaApi.ts b/src/shared/yagna/yagnaApi.ts new file mode 100644 index 000000000..2a3cc1c99 --- /dev/null +++ b/src/shared/yagna/yagnaApi.ts @@ -0,0 +1,292 @@ +import * as YaTsClient from "ya-ts-client"; +import * as EnvUtils from "../utils/env"; +import { GolemConfigError, GolemPlatformError } from "../error/golem-error"; +import { v4 } from "uuid"; +import { defaultLogger, Logger } from "../utils"; +import semverSatisfies from "semver/functions/satisfies.js"; // .js added for ESM compatibility +import semverCoerce from "semver/functions/coerce.js"; // .js added for ESM compatibility +import { Observable, Subject } from "rxjs"; +import { CancellablePoll, EventReader } from "./event-reader"; +import EventSource from "eventsource"; +import { StreamingBatchEvent } from "../../activity/results"; +import { ElementOf } from "../utils/types"; + +export type YagnaOptions = { + apiKey?: string; + basePath?: string; + logger?: Logger; +}; + +export const MIN_SUPPORTED_YAGNA = "0.15.2"; + +// Workarounds for an issue with missing support for discriminators +// {@link https://github.com/ferdikoomen/openapi-typescript-codegen/issues/985} +export type YagnaAgreementOperationEvent = ElementOf< + Awaited> +>; +export type YagnaInvoiceEvent = ElementOf< + Awaited> +>; +export type YagnaDebitNoteEvent = ElementOf< + Awaited> +>; + +export interface YagnaExeScriptObserver { + observeBatchExecResults(activityId: string, batchId: string): Observable; +} + +/** + * Utility class that groups various Yagna APIs under a single wrapper + * + * This class has the following responsibilities: + * + * - selectively exposes services from ya-ts-client in a more user-friendly manner + * - implements an event reader that collects events from Yagna endpoints and allows subscribing to them as Observables + * for agreements, debit notes and invoices. These observables emit ya-ts-client types on outputs + * + * End users of the SDK should not use this class and make use of {@link golem-network/golem-network.GolemNetwork} instead. This class is designed for + * SDK developers to use. + */ +export class YagnaApi { + public readonly appSessionId: string; + + public readonly yagnaOptions: YagnaOptions; + /** + * Base path used to build paths to Yagna's API + * + * @example http://localhost:7465 + */ + public readonly basePath: string; + + public readonly identity: YaTsClient.IdentityApi.DefaultService; + public market: YaTsClient.MarketApi.RequestorService; + public activity: { + control: YaTsClient.ActivityApi.RequestorControlService; + state: YaTsClient.ActivityApi.RequestorStateService; + exec: YagnaExeScriptObserver; + }; + public net: YaTsClient.NetApi.RequestorService; + public payment: YaTsClient.PaymentApi.RequestorService; + public gsb: YaTsClient.GsbApi.RequestorService; + public version: YaTsClient.VersionApi.DefaultService; + + public debitNoteEvents$ = new Subject(); + private debitNoteEventsPoll: CancellablePoll | null = null; + + public invoiceEvents$ = new Subject(); + private invoiceEventPoll: CancellablePoll | null = null; + + public agreementEvents$ = new Subject(); + private agreementEventsPoll: CancellablePoll | null = null; + + private readonly logger: Logger; + private readonly reader: EventReader; + + constructor(options?: YagnaOptions) { + const apiKey = options?.apiKey || EnvUtils.getYagnaAppKey(); + this.basePath = options?.basePath || EnvUtils.getYagnaApiUrl(); + + const yagnaOptions: Pick = { + apiKey: apiKey, + basePath: this.basePath, + }; + + if (!yagnaOptions.apiKey) { + throw new GolemConfigError("Yagna API key not defined"); + } + + const commonHeaders = { + Authorization: `Bearer ${apiKey}`, + }; + + const marketClient = new YaTsClient.MarketApi.Client({ + BASE: `${this.basePath}/market-api/v1`, + HEADERS: commonHeaders, + }); + + this.market = marketClient.requestor; + + const paymentClient = new YaTsClient.PaymentApi.Client({ + BASE: `${this.basePath}/payment-api/v1`, + HEADERS: commonHeaders, + }); + + this.payment = paymentClient.requestor; + + const activityApiClient = new YaTsClient.ActivityApi.Client({ + BASE: `${this.basePath}/activity-api/v1`, + HEADERS: commonHeaders, + }); + this.activity = { + control: activityApiClient.requestorControl, + state: activityApiClient.requestorState, + exec: { + observeBatchExecResults: (activityId: string, batchId: string) => { + return new Observable((observer) => { + const eventSource = new EventSource( + `${this.basePath}/activity-api/v1/activity/${activityId}/exec/${batchId}`, + { + headers: { + Accept: "text/event-stream", + Authorization: `Bearer ${apiKey}`, + }, + }, + ); + + eventSource.addEventListener("runtime", (event) => observer.next(JSON.parse(event.data))); + eventSource.addEventListener("error", (error) => observer.error(error)); + return () => eventSource.close(); + }); + }, + }, + }; + + const netClient = new YaTsClient.NetApi.Client({ + BASE: `${this.basePath}/net-api/v1`, + HEADERS: commonHeaders, + }); + + this.net = netClient.requestor; + + const gsbClient = new YaTsClient.GsbApi.Client({ + BASE: `${this.basePath}/gsb-api/v1`, + HEADERS: commonHeaders, + }); + + this.gsb = gsbClient.requestor; + + this.logger = options?.logger ?? defaultLogger("yagna"); + + const identityClient = new YaTsClient.IdentityApi.Client({ + BASE: this.basePath, + HEADERS: commonHeaders, + }); + this.identity = identityClient.default; + + const versionClient = new YaTsClient.VersionApi.Client({ + BASE: this.basePath, + }); + this.version = versionClient.default; + + this.yagnaOptions = yagnaOptions; + + this.appSessionId = v4(); + + this.reader = new EventReader(this.logger); + } + + /** + * Effectively starts the Yagna API client including subscribing to events exposed via rxjs subjects + */ + async connect() { + this.logger.debug("Connecting to Yagna"); + + const version = await this.assertSupportedVersion(); + + const identity = await this.identity.getIdentity(); + + this.startPollingEvents(); + + this.logger.info("Connected to Yagna", { version, identity: identity.identity }); + + return identity; + } + + /** + * Terminates the Yagna API related activities + */ + async disconnect() { + this.logger.debug("Disconnecting from Yagna"); + await this.stopPollingEvents(); + this.logger.info("Disconnected from Yagna"); + } + + public async getVersion(): Promise { + try { + const res = await this.version.getVersion(); + return res.current.version; + } catch (err) { + throw new GolemPlatformError(`Failed to establish yagna version due to: ${err}`, err); + } + } + + private startPollingEvents() { + this.logger.debug("Starting to poll for events from Yagna", { appSessionId: this.appSessionId }); + + const pollIntervalSec = 5; + const maxEvents = 100; + + this.agreementEventsPoll = this.reader.createReader("AgreementEvents", (lastEventTimestamp) => + this.market.collectAgreementEvents(pollIntervalSec, lastEventTimestamp, maxEvents, this.appSessionId), + ); + + this.debitNoteEventsPoll = this.reader.createReader("DebitNoteEvents", (lastEventTimestamp) => { + return this.payment.getDebitNoteEvents(pollIntervalSec, lastEventTimestamp, maxEvents, this.appSessionId); + }); + + this.invoiceEventPoll = this.reader.createReader("InvoiceEvents", (lastEventTimestamp) => + this.payment.getInvoiceEvents(pollIntervalSec, lastEventTimestamp, maxEvents, this.appSessionId), + ); + + // Run the readers and don't block execution + this.reader + .pollToSubject(this.agreementEventsPoll.pollValues(), this.agreementEvents$) + .then(() => this.logger.debug("Finished polling agreement events from Yagna")) + .catch((err) => this.logger.error("Error while polling agreement events from Yagna", err)); + + this.reader + .pollToSubject(this.debitNoteEventsPoll.pollValues(), this.debitNoteEvents$) + .then(() => this.logger.debug("Finished polling debit note events from Yagna")) + .catch((err) => this.logger.error("Error while polling debit note events from Yagna", err)); + + this.reader + .pollToSubject(this.invoiceEventPoll.pollValues(), this.invoiceEvents$) + .then(() => this.logger.debug("Finished polling invoice events from Yagna")) + .catch((err) => this.logger.error("Error while polling invoice events from Yagna", err)); + } + + private async stopPollingEvents() { + this.logger.debug("Stopping polling events from Yagna"); + + const promises: Promise[] = []; + if (this.invoiceEventPoll) { + promises.push(this.invoiceEventPoll.cancel()); + } + + if (this.debitNoteEventsPoll) { + promises.push(this.debitNoteEventsPoll.cancel()); + } + + if (this.agreementEventsPoll) { + promises.push(this.agreementEventsPoll.cancel()); + } + await Promise.allSettled(promises); + + this.logger.debug("Stopped polling events from Yagna"); + } + + private async assertSupportedVersion() { + const version = await this.getVersion(); + const normVersion = semverCoerce(version); + + this.logger.debug("Checking Yagna version support", { + userInstalled: normVersion?.raw, + minSupported: MIN_SUPPORTED_YAGNA, + }); + + if (!normVersion) { + throw new GolemPlatformError( + `Unreadable yagna version '${version}'. Can't proceed without checking yagna version support status.`, + ); + } + + if (!semverSatisfies(normVersion, `>=${MIN_SUPPORTED_YAGNA}`)) { + throw new GolemPlatformError( + `You run yagna in version ${version} and the minimal version supported by the SDK is ${MIN_SUPPORTED_YAGNA}. ` + + `Please consult the golem-js README to find matching SDK version or upgrade your yagna installation.`, + ); + } + + return normVersion.version; + } +} diff --git a/src/stats/abstract_aggregator.ts b/src/stats/abstract_aggregator.ts deleted file mode 100644 index 1aad263e6..000000000 --- a/src/stats/abstract_aggregator.ts +++ /dev/null @@ -1,31 +0,0 @@ -import collect, { Collection } from "collect.js"; - -export interface ItemInfo { - id: string; -} - -export abstract class AbstractAggregator { - protected items = new Map(); - add(event: T) { - const item = this.beforeAdd(event); - this.items.set(item.id, item); - } - abstract beforeAdd(item: T): R; - getById(id: string) { - return this.items.get(id); - } - protected getByField(field: string, value: string | number): Collection { - return this.getAll().where(field, "==", value); - } - protected updateItemInfo(id: string, data: Partial) { - const item = this.items.get(id); - if (!item) return; - this.items?.set(id, { - ...item, - ...data, - }); - } - getAll(): Collection { - return collect([...this.items.values()]); - } -} diff --git a/src/stats/activities.ts b/src/stats/activities.ts deleted file mode 100644 index c36a00e27..000000000 --- a/src/stats/activities.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AbstractAggregator } from "./abstract_aggregator"; - -export interface ActivityInfo { - id: string; - agreementId: string; - taskId: string; -} - -interface Payload { - id: string; - taskId: string; - agreementId: string; -} - -export class Activities extends AbstractAggregator { - beforeAdd(payload: ActivityInfo): ActivityInfo { - return payload; - } - getByTaskId(taskId: string) { - return this.getByField("taskId", taskId); - } - getByAgreementId(agreementId: string) { - return this.getByField("agreementId", agreementId); - } -} diff --git a/src/stats/agreements.ts b/src/stats/agreements.ts deleted file mode 100644 index 9b646577c..000000000 --- a/src/stats/agreements.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { AbstractAggregator } from "./abstract_aggregator"; -import { ProviderInfo } from "../agreement"; - -export enum AgreementStatusEnum { - Pending = "pending", - Confirmed = "confirmed", - Rejected = "rejected", -} - -export interface AgreementInfo { - id: string; - proposalId: string; - provider: ProviderInfo; - status: AgreementStatusEnum; -} - -interface Payload { - id: string; - proposalId: string; - provider: ProviderInfo; -} - -export class Agreements extends AbstractAggregator { - beforeAdd(payload: AgreementInfo): AgreementInfo { - return { ...payload, status: AgreementStatusEnum.Pending }; - } - confirm(id: string) { - this.updateItemInfo(id, { status: AgreementStatusEnum.Confirmed }); - } - reject(id: string) { - this.updateItemInfo(id, { status: AgreementStatusEnum.Rejected }); - } - getByProviderId(providerId: string) { - return this.getByField("provider.id", providerId); - } - getByProposalId(proposalId: string) { - return this.getByField("proposalId", proposalId).first(); - } - getByStatus(status: AgreementStatusEnum) { - return this.getByField("status", status); - } -} diff --git a/src/stats/allocations.ts b/src/stats/allocations.ts deleted file mode 100644 index c5baea68a..000000000 --- a/src/stats/allocations.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { AbstractAggregator } from "./abstract_aggregator"; - -export interface AllocationInfo { - id: string; - amount: number; - platform?: string; -} - -interface Payload { - id: string; - amount: number; - platform?: string; -} - -export class Allocations extends AbstractAggregator { - beforeAdd(payload: Payload): AllocationInfo { - return payload; - } -} diff --git a/src/stats/debit_notes.ts b/src/stats/debit_notes.ts deleted file mode 100644 index b4faa9ee4..000000000 --- a/src/stats/debit_notes.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AbstractAggregator } from "./abstract_aggregator"; - -export interface DebitNoteInfo { - id: string; -} -interface Payload { - id: string; - amount: number; -} - -export class DebitNotes extends AbstractAggregator { - beforeAdd(item: Payload): DebitNoteInfo { - return item; - } -} diff --git a/src/stats/invoices.ts b/src/stats/invoices.ts deleted file mode 100644 index b342657cc..000000000 --- a/src/stats/invoices.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { AbstractAggregator } from "./abstract_aggregator"; -import { ProviderInfo } from "../agreement"; - -export interface InvoiceInfo { - id: string; - agreementId: string; - amount: string; - provider: ProviderInfo; -} -interface Payload { - id: string; - agreementId: string; - amount: string; - provider: ProviderInfo; -} - -export class Invoices extends AbstractAggregator { - beforeAdd(payload: Payload): InvoiceInfo { - return payload; - } - getByProviderId(providerId: string) { - return this.getByField("provider.id", providerId); - } - getByAgreementId(agreementId: string) { - return this.getByField("agreementId", agreementId); - } -} diff --git a/src/stats/payments.ts b/src/stats/payments.ts deleted file mode 100644 index 1c1785fa9..000000000 --- a/src/stats/payments.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { AbstractAggregator } from "./abstract_aggregator"; -import { ProviderInfo } from "../agreement"; - -export interface PaymentInfo { - id: string; - agreementId: string; - amount: string; - provider: ProviderInfo; -} -interface Payload { - id: string; - agreementId: string; - amount: string; - provider: ProviderInfo; -} - -export class Payments extends AbstractAggregator { - beforeAdd(payload: Payload): PaymentInfo { - return payload; - } - getByProviderId(providerId: string) { - return this.getByField("provider.id", providerId); - } - - getByAgreementId(agreementId: string) { - return this.getByField("agreementId", agreementId); - } -} diff --git a/src/stats/proposals.ts b/src/stats/proposals.ts deleted file mode 100644 index 9f3790b8c..000000000 --- a/src/stats/proposals.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { AbstractAggregator } from "./abstract_aggregator"; - -export interface ProposalInfo { - id: string; - providerId: string; -} -interface Payload { - id: string; - providerId: string; -} - -export class Proposals extends AbstractAggregator { - beforeAdd(payload: Payload): ProposalInfo { - return payload; - } - getByProviderId(providerId: string) { - return this.getByField("providerId", providerId); - } -} diff --git a/src/stats/providers.ts b/src/stats/providers.ts deleted file mode 100644 index fc567b1e3..000000000 --- a/src/stats/providers.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { AbstractAggregator } from "./abstract_aggregator"; - -export interface ProviderInfo { - id: string; - name: string; - walletAddress: string; -} -interface Payload { - id: string; - name: string; - walletAddress: string; -} - -export class Providers extends AbstractAggregator { - beforeAdd(payload: Payload): ProviderInfo { - return payload; - } -} diff --git a/src/stats/service.ts b/src/stats/service.ts deleted file mode 100644 index 2b524ca69..000000000 --- a/src/stats/service.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { Events, EVENT_TYPE, BaseEvent } from "../events"; -import { Logger, defaultLogger } from "../utils"; -import { Providers } from "./providers"; -import { Tasks } from "./tasks"; -import { Payments } from "./payments"; -import { Agreements } from "./agreements"; -import { Invoices } from "./invoices"; -import { Proposals } from "./proposals"; -import { Allocations } from "./allocations"; -import { Activities } from "./activities"; -import { Times } from "./times"; - -interface StatsOptions { - eventTarget: EventTarget; - logger?: Logger; -} - -/** - * @internal - */ -export class StatsService { - private eventTarget: EventTarget; - private logger: Logger; - private allocations: Allocations; - private agreements: Agreements; - private activities: Activities; - private invoices: Invoices; - private proposals: Proposals; - private providers: Providers; - private payments: Payments; - private tasks: Tasks; - private times: Times; - - constructor(options: StatsOptions) { - this.eventTarget = options.eventTarget; - this.logger = options.logger || defaultLogger("stats"); - this.allocations = new Allocations(); - this.activities = new Activities(); - this.agreements = new Agreements(); - this.invoices = new Invoices(); - this.proposals = new Proposals(); - this.providers = new Providers(); - this.payments = new Payments(); - this.tasks = new Tasks(); - this.times = new Times(); - } - - async run() { - this.eventTarget.addEventListener(EVENT_TYPE, (event) => this.handleEvents(event as BaseEvent)); - this.logger.info("Stats service has started"); - } - - async end() { - this.eventTarget.removeEventListener(EVENT_TYPE, null); - this.logger.info("Stats service has stopped"); - } - - getAllCostsSummary() { - return this.agreements - .getAll() - .map((agreement) => { - const provider = this.providers.getById(agreement.provider.id); - const tasks = this.tasks.getByAgreementId(agreement.id); - const invoices = this.invoices.getByAgreementId(agreement.id); - const payments = this.payments.getByAgreementId(agreement.id); - return { - Agreement: agreement.id.substring(0, 10), - "Provider Name": provider ? provider.name : "unknown", - "Task Computed": tasks.where("status", "finished").count(), - Cost: invoices.sum("amount"), - "Payment Status": payments.count() > 0 ? "paid" : "unpaid", - }; - }) - .all(); - } - - getAllCosts() { - const costs = { total: 0, paid: 0 }; - this.agreements - .getAll() - .all() - .forEach((agreement) => { - const invoices = this.invoices.getByAgreementId(agreement.id); - const payments = this.payments.getByAgreementId(agreement.id); - costs.total += invoices.sum("amount") as number; - costs.paid += payments.count() > 0 ? (invoices.sum("amount") as number) : 0; - }); - return costs; - } - - getComputationTime(): string { - const duration = this.times.getById("all")?.duration; - return `${duration ? (duration / 1000).toFixed(1) : 0}s`; - } - - getStatsTree() { - return { - allocations: this.allocations - .getAll() - .map((allocation) => allocation) - .all(), - providers: this.providers - .getAll() - .map((provider) => { - return { - ...provider, - proposals: this.proposals - .getByProviderId(provider.id) - .map((proposal) => { - const agreement = this.agreements.getByProposalId(proposal.id); - return { - ...proposal, - agreement: agreement - ? { - ...agreement, - activities: this.activities - .getByAgreementId(agreement.id) - .map((activity) => { - return { - ...activity, - task: this.tasks.getById(activity.taskId), - }; - }) - .all(), - invoices: this.invoices - .getByAgreementId(agreement.id) - .map((invoice) => invoice) - .all(), - payments: this.payments - .getByAgreementId(agreement.id) - .map((payment) => payment) - .all(), - } - : null, - }; - }) - .all(), - }; - }) - .all(), - agreements: this.agreements - .getAll() - .map((agreement) => { - const provider = this.providers.getById(agreement.provider.id); - const tasks = this.tasks.getByAgreementId(agreement.id); - const invoices = this.invoices.getByAgreementId(agreement.id); - const payments = this.payments.getByAgreementId(agreement.id); - return { - agreementId: agreement.id, - provider, - tasks: tasks.where("status", "finished").count(), - cost: invoices.sum("amount"), - paymentStatus: payments.count() > 0 ? "paid" : "unpaid", - }; - }) - .all(), - costs: this.getAllCosts(), - }; - } - - private handleEvents(event: BaseEvent) { - if (event instanceof Events.ComputationStarted) { - this.times.add({ id: "all", startTime: event.timeStamp }); - } else if (event instanceof Events.ComputationFinished) { - this.times.stop({ id: "all", stopTime: event.timeStamp }); - } else if (event instanceof Events.TaskStarted) { - this.activities.add({ - id: event.detail.activityId, - taskId: event.detail.id, - agreementId: event.detail.agreementId, - }); - this.tasks.add({ - id: event.detail.id, - startTime: event.timeStamp, - agreementId: event.detail.agreementId, - }); - } else if (event instanceof Events.TaskRedone) { - this.tasks.retry(event.detail.id, event.detail.retriesCount); - } else if (event instanceof Events.TaskRejected) { - this.tasks.reject(event.detail.id, event.timeStamp, event.detail.reason); - } else if (event instanceof Events.TaskFinished) { - this.tasks.finish(event.detail.id, event.timeStamp); - } else if (event instanceof Events.AllocationCreated) { - this.allocations.add({ id: event.detail.id, amount: event.detail.amount, platform: event.detail.platform }); - } else if (event instanceof Events.AgreementCreated) { - this.agreements.add({ - id: event.detail.id, - provider: event.detail.provider, - proposalId: event.detail.proposalId, - }); - this.providers.add(event.detail.provider); - } else if (event instanceof Events.AgreementConfirmed) { - this.agreements.confirm(event.detail.id); - } else if (event instanceof Events.AgreementRejected) { - this.agreements.reject(event.detail.id); - } else if (event instanceof Events.ProposalReceived) { - this.proposals.add({ id: event.detail.id, providerId: event.detail.provider.id }); - this.providers.add({ ...event.detail.provider }); - } else if (event instanceof Events.InvoiceReceived) { - this.invoices.add({ - id: event.detail.id, - provider: event.detail.provider, - agreementId: event.detail.agreementId, - amount: event.detail.amountPrecise, - }); - } else if (event instanceof Events.PaymentAccepted) { - this.payments.add({ - id: event.detail.id, - agreementId: event.detail.agreementId, - amount: event.detail.amountPrecise, - provider: event.detail.provider, - }); - } - } -} diff --git a/src/stats/tasks.ts b/src/stats/tasks.ts deleted file mode 100644 index 31e96245c..000000000 --- a/src/stats/tasks.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { AbstractAggregator, ItemInfo } from "./abstract_aggregator"; - -export enum TaskStatusEnum { - Pending = "pending", - Finished = "finished", - Rejected = "rejected", -} - -export interface TaskInfo extends ItemInfo { - agreementId: string; - startTime: number; - stopTime: number; - retriesCount: number; - reason?: string; - status: TaskStatusEnum; -} - -interface Payload { - id: string; - agreementId: string; - startTime: number; -} - -export class Tasks extends AbstractAggregator { - beforeAdd(payload: Payload): TaskInfo { - return { - ...payload, - stopTime: 0, - retriesCount: 0, - status: TaskStatusEnum.Pending, - }; - } - retry(id: string, retriesCount: number) { - this.updateItemInfo(id, { retriesCount }); - } - reject(id: string, timeStamp: number, reason?: string) { - this.updateItemInfo(id, { stopTime: timeStamp, reason: reason, status: TaskStatusEnum.Rejected }); - } - finish(id: string, timeStamp: number) { - this.updateItemInfo(id, { stopTime: timeStamp, status: TaskStatusEnum.Finished }); - } - getByAgreementId(agreementId: string) { - return this.getByField("agreementId", agreementId); - } -} diff --git a/src/stats/times.ts b/src/stats/times.ts deleted file mode 100644 index 511f6929a..000000000 --- a/src/stats/times.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AbstractAggregator, ItemInfo } from "./abstract_aggregator"; - -export interface TimesInfo extends ItemInfo { - startTime: number; - stopTime: number; - duration: number; -} - -interface Payload { - id: string; - startTime: number; - stopTime?: number; -} - -export class Times extends AbstractAggregator { - beforeAdd({ id, startTime, stopTime }: Payload): TimesInfo { - return { id, stopTime: stopTime || 0, startTime, duration: stopTime ? stopTime - startTime : 0 }; - } - - stop({ id, stopTime }: { id: string; stopTime: number }) { - const item = this.items.get(id); - this.updateItemInfo(id, { stopTime, duration: item?.startTime ? stopTime - item.startTime : 0 }); - } -} diff --git a/src/task/config.ts b/src/task/config.ts deleted file mode 100644 index b75dcf95b..000000000 --- a/src/task/config.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { TaskServiceOptions } from "./service"; -import { ActivityConfig } from "../activity"; -import { Logger, defaultLogger } from "../utils"; -import { StorageProvider } from "../storage"; - -const DEFAULTS = { - maxParallelTasks: 5, - taskRunningInterval: 1000, - taskTimeout: 30000, - activityStateCheckingInterval: 2000, - activityPreparingTimeout: 1000 * 60 * 4, // 2 min -}; - -/** - * @internal - */ -export class TaskConfig extends ActivityConfig { - public readonly maxParallelTasks: number; - public readonly taskRunningInterval: number; - public readonly taskTimeout: number; - public readonly activityStateCheckingInterval: number; - public readonly activityPreparingTimeout: number; - public readonly storageProvider?: StorageProvider; - public readonly logger: Logger; - - constructor(options?: TaskServiceOptions) { - super(options); - this.maxParallelTasks = options?.maxParallelTasks || DEFAULTS.maxParallelTasks; - this.taskRunningInterval = options?.taskRunningInterval || DEFAULTS.taskRunningInterval; - this.taskTimeout = options?.taskTimeout || DEFAULTS.taskTimeout; - this.activityStateCheckingInterval = - options?.activityStateCheckingInterval || DEFAULTS.activityStateCheckingInterval; - this.logger = options?.logger || defaultLogger("work"); - this.storageProvider = options?.storageProvider; - this.activityPreparingTimeout = options?.activityPreparingTimeout || DEFAULTS.activityPreparingTimeout; - } -} diff --git a/src/task/error.ts b/src/task/error.ts deleted file mode 100644 index c6ec9b024..000000000 --- a/src/task/error.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { GolemModuleError } from "../error/golem-error"; -import { Agreement, ProviderInfo } from "../agreement"; -import { Activity } from "../activity"; - -export enum WorkErrorCode { - ServiceNotInitialized, - ScriptExecutionFailed, - ActivityDestroyingFailed, - ActivityResultsFetchingFailed, - ActivityCreationFailed, - TaskAddingFailed, - TaskExecutionFailed, - TaskRejected, - NetworkSetupMissing, - ScriptInitializationFailed, - ActivityDeploymentFailed, - ActivityStatusQueryFailed, -} -export class GolemWorkError extends GolemModuleError { - constructor( - message: string, - public code: WorkErrorCode, - public agreement?: Agreement, - public activity?: Activity, - public provider?: ProviderInfo, - public previous?: Error, - ) { - super(message, code, previous); - } -} diff --git a/src/task/index.ts b/src/task/index.ts deleted file mode 100644 index 7a5dd52bd..000000000 --- a/src/task/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { TaskService } from "./service"; -export { Task, TaskOptions } from "./task"; -export { TaskQueue, QueueableTask } from "./queue"; -export { WorkContext, Worker, WorkOptions } from "./work"; -export { Batch } from "./batch"; -export { GolemWorkError, WorkErrorCode } from "./error"; -export { TcpProxy } from "../network/tcpProxy"; diff --git a/src/task/process.spec.ts b/src/task/process.spec.ts deleted file mode 100644 index 8472e15d8..000000000 --- a/src/task/process.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { RemoteProcess } from "./process"; -import { ActivityMock } from "../../tests/mock/activity.mock"; -import { YagnaMock } from "../../tests/mock"; -import { Run, Script } from "../script"; -import { ResultState } from "../activity"; -import { agreement } from "../../tests/mock/entities/agreement"; - -describe("RemoteProcess", () => { - let activity: ActivityMock; - beforeEach(() => { - activity = new ActivityMock("test_id", agreement, new YagnaMock().getApi()); - }); - - it("should create remote process", async () => { - const expectedResult = ActivityMock.createResult({ stdout: "Ok" }); - activity.mockResults([expectedResult]); - const exeScriptRequest = new Script([new Run("test_command")]).getExeScriptRequest(); - const streamOfActivityResults = await activity.execute(exeScriptRequest, true); - const remoteProcess = new RemoteProcess(streamOfActivityResults, activity); - expect(remoteProcess).toBeDefined(); - }); - - it("should read stdout from remote process", async () => { - const expectedResult = ActivityMock.createResult({ stdout: "Output" }); - activity.mockResults([expectedResult]); - const exeScriptRequest = new Script([new Run("test_command")]).getExeScriptRequest(); - const streamOfActivityResults = await activity.execute(exeScriptRequest, true); - const remoteProcess = new RemoteProcess(streamOfActivityResults, activity); - for await (const stdout of remoteProcess.stdout) { - expect(stdout).toEqual("Output"); - } - }); - - it("should read stderr from remote process", async () => { - const expectedResult = ActivityMock.createResult({ stderr: "Error" }); - activity.mockResults([expectedResult]); - const exeScriptRequest = new Script([new Run("test_command")]).getExeScriptRequest(); - const streamOfActivityResults = await activity.execute(exeScriptRequest, true); - const remoteProcess = new RemoteProcess(streamOfActivityResults, activity); - for await (const stderr of remoteProcess.stderr) { - expect(stderr).toEqual("Error"); - } - }); - - it("should wait for exit", async () => { - const expectedResult = ActivityMock.createResult({ stdout: "Output", stderr: "Error" }); - activity.mockResults([expectedResult]); - const exeScriptRequest = new Script([new Run("test_command")]).getExeScriptRequest(); - const streamOfActivityResults = await activity.execute(exeScriptRequest, true); - const remoteProcess = new RemoteProcess(streamOfActivityResults, activity); - const finalResult = await remoteProcess.waitForExit(); - expect(finalResult.result).toEqual(ResultState.Ok); - }); -}); diff --git a/src/task/queue.ts b/src/task/queue.ts deleted file mode 100644 index 16bdf1e9e..000000000 --- a/src/task/queue.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Task } from "./task"; -import { GolemWorkError, WorkErrorCode } from "./error"; - -/** - * @internal - */ -export interface QueueableTask { - isQueueable(): boolean; -} - -/** - * @internal - */ -export class TaskQueue { - protected itemsStack: Array = []; - - addToEnd(task: T) { - this.checkIfTaskIsEligibleForAdd(task); - this.itemsStack.push(task); - } - - addToBegin(task: T) { - this.checkIfTaskIsEligibleForAdd(task); - this.itemsStack.unshift(task); - } - - get size(): number { - return this.itemsStack.length; - } - - get(): T | undefined { - return this.itemsStack.shift(); - } - - private checkIfTaskIsEligibleForAdd(task: T) { - if (!task.isQueueable()) - throw new GolemWorkError( - "You cannot add a task that is not in the correct state", - WorkErrorCode.TaskAddingFailed, - ); - } -} diff --git a/src/task/service.ts b/src/task/service.ts deleted file mode 100644 index 019d7c075..000000000 --- a/src/task/service.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { TaskQueue } from "./queue"; -import { WorkContext } from "./work"; -import { defaultLogger, Logger, sleep, YagnaApi } from "../utils"; -import { StorageProvider } from "../storage"; -import { Agreement, AgreementPoolService } from "../agreement"; -import { PaymentService } from "../payment"; -import { NetworkNode, NetworkService } from "../network"; -import { Activity, ActivityOptions } from "../activity"; -import { TaskConfig } from "./config"; -import { Events } from "../events"; -import { Task } from "./task"; - -export interface TaskServiceOptions extends ActivityOptions { - /** Number of maximum parallel running task on one TaskExecutor instance */ - maxParallelTasks?: number; - taskRunningInterval?: number; - activityStateCheckingInterval?: number; - activityPreparingTimeout?: number; - taskTimeout?: number; - logger?: Logger; - storageProvider?: StorageProvider; -} - -/** - * @internal - */ -export class TaskService { - private activeTasksCount = 0; - private activities = new Map(); - private activitySetupDone: Set = new Set(); - private isRunning = false; - private logger: Logger; - private options: TaskConfig; - - constructor( - private yagnaApi: YagnaApi, - private tasksQueue: TaskQueue, - private agreementPoolService: AgreementPoolService, - private paymentService: PaymentService, - private networkService?: NetworkService, - options?: TaskServiceOptions, - ) { - this.options = new TaskConfig(options); - this.logger = options?.logger || defaultLogger("work"); - } - - public async run() { - this.isRunning = true; - this.logger.info("Task Service has started"); - while (this.isRunning) { - if (this.activeTasksCount >= this.options.maxParallelTasks) { - await sleep(this.options.taskRunningInterval, true); - continue; - } - const task = this.tasksQueue.get(); - if (!task) { - await sleep(this.options.taskRunningInterval, true); - continue; - } - task.onStateChange(() => { - if (task.isRetry()) { - this.retryTask(task).catch((error) => this.logger.error(`Issue with retrying a task on Golem`, error)); - } else if (task.isFinished()) { - this.stopTask(task).catch((error) => this.logger.error(`Issue with stopping a task on Golem`, error)); - } - }); - this.startTask(task).catch( - (error) => this.isRunning && this.logger.error(`Issue with starting a task on Golem`, error), - ); - } - } - - async end() { - this.isRunning = false; - this.logger.debug(`Trying to stop all activities`, { size: this.activities.size }); - await Promise.all( - [...this.activities.values()].map((activity) => - activity - .stop() - .catch((error) => this.logger.warn(`Stopping activity failed`, { activityId: activity.id, error })), - ), - ); - this.logger.info("Task Service has been stopped"); - } - - private async startTask(task: Task) { - task.init(); - this.logger.debug(`Starting task`, { taskId: task.id, attempt: task.getRetriesCount() + 1 }); - ++this.activeTasksCount; - - const agreement = await this.agreementPoolService.getAgreement(); - let activity: Activity | undefined; - let networkNode: NetworkNode | undefined; - - try { - activity = await this.getOrCreateActivity(agreement); - task.start(activity, networkNode); - this.options.eventTarget?.dispatchEvent( - new Events.TaskStarted({ - id: task.id, - agreementId: agreement.id, - activityId: activity.id, - provider: agreement.getProviderInfo(), - }), - ); - this.logger.info(`Task started`, { - taskId: task.id, - providerName: agreement.getProviderInfo().name, - activityId: activity.id, - }); - - const activityReadySetupFunctions = task.getActivityReadySetupFunctions(); - const worker = task.getWorker(); - if (this.networkService && !this.networkService.hasNode(agreement.getProviderInfo().id)) { - networkNode = await this.networkService.addNode(agreement.getProviderInfo().id); - } - - const ctx = new WorkContext(activity, { - yagnaOptions: this.yagnaApi.yagnaOptions, - activityReadySetupFunctions: this.activitySetupDone.has(activity.id) ? [] : activityReadySetupFunctions, - storageProvider: this.options.storageProvider, - networkNode, - logger: this.logger, - activityPreparingTimeout: this.options.activityPreparingTimeout, - activityStateCheckingInterval: this.options.activityStateCheckingInterval, - }); - - await ctx.before(); - - if (activityReadySetupFunctions.length && !this.activitySetupDone.has(activity.id)) { - this.activitySetupDone.add(activity.id); - this.logger.debug(`Activity setup completed`, { activityId: activity.id }); - } - const results = await worker(ctx); - task.stop(results); - } catch (error) { - task.stop(undefined, error); - } finally { - --this.activeTasksCount; - } - } - - private async stopActivity(activity: Activity) { - await activity.stop(); - this.activities.delete(activity.agreement.id); - } - - private async getOrCreateActivity(agreement: Agreement) { - const previous = this.activities.get(agreement.id); - if (previous) { - return previous; - } else { - const activity = await Activity.create(agreement, this.yagnaApi, this.options); - this.activities.set(agreement.id, activity); - this.paymentService.acceptPayments(agreement); - return activity; - } - } - - private async retryTask(task: Task) { - if (!this.isRunning) return; - task.cleanup(); - await this.releaseTaskResources(task); - const reason = task.getError()?.message; - this.options.eventTarget?.dispatchEvent( - new Events.TaskRedone({ - id: task.id, - activityId: task.getActivity()?.id, - agreementId: task.getActivity()?.agreement.id, - provider: task.getActivity()?.getProviderInfo(), - retriesCount: task.getRetriesCount(), - reason, - }), - ); - this.logger.warn(`Task execution failed. Trying to redo the task.`, { - taskId: task.id, - attempt: task.getRetriesCount(), - reason, - }); - this.tasksQueue.addToBegin(task); - } - - private async stopTask(task: Task) { - task.cleanup(); - await this.releaseTaskResources(task); - if (task.isRejected()) { - const reason = task.getError()?.message; - this.options.eventTarget?.dispatchEvent( - new Events.TaskRejected({ - id: task.id, - agreementId: task.getActivity()?.agreement.id, - activityId: task.getActivity()?.id, - provider: task.getActivity()?.getProviderInfo(), - reason, - }), - ); - this.logger.error(`Task has been rejected`, { - taskId: task.id, - reason: task.getError()?.message, - retries: task.getRetriesCount(), - providerName: task.getActivity()?.getProviderInfo().name, - }); - } else { - this.options.eventTarget?.dispatchEvent(new Events.TaskFinished({ id: task.id })); - this.logger.info(`Task computed`, { - taskId: task.id, - retries: task.getRetriesCount(), - providerName: task.getActivity()?.getProviderInfo().name, - }); - } - } - - private async releaseTaskResources(task: Task) { - const activity = task.getActivity(); - if (activity) { - if (task.isFailed()) { - /** - * Activity should only be terminated when the task fails. - * We assume that the next attempt should be performed on a new activity instance. - * For successfully completed tasks, activities remain in the ready state - * and are ready to be used for other tasks. - * For them, termination will be completed with the end of service - */ - await this.stopActivity(activity).catch((error) => - this.logger.error(`Stopping activity failed`, { activityId: activity.id, error }), - ); - } - await this.agreementPoolService - .releaseAgreement(activity.agreement.id, task.isDone()) - .catch((error) => - this.logger.error(`Releasing agreement failed`, { agreementId: activity.agreement.id, error }), - ); - } - const networkNode = task.getNetworkNode(); - if (this.networkService && networkNode) { - await this.networkService - .removeNode(networkNode.id) - .catch((error) => this.logger.error(`Removing network node failed`, { nodeId: networkNode.id, error })); - } - } -} diff --git a/src/task/task.ts b/src/task/task.ts deleted file mode 100644 index 48345eb75..000000000 --- a/src/task/task.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { QueueableTask } from "./queue"; -import { Worker } from "./work"; -import { GolemConfigError, GolemTimeoutError } from "../error/golem-error"; -import { Activity } from "../activity"; -import { NetworkNode } from "../network"; - -export enum TaskState { - New = "new", - Queued = "queued", - Pending = "pending", - Done = "done", - Retry = "retry", - Rejected = "rejected", -} - -export type TaskOptions = { - /** maximum number of retries if task failed due to provider reason, default = 5 */ - maxRetries?: number; - /** timeout in ms for task execution, including retries, default = 300_000 (5min) */ - timeout?: number; - /** array of setup functions to run on each activity */ - activityReadySetupFunctions?: Worker[]; -}; - -const DEFAULTS = { - MAX_RETRIES: 5, - TIMEOUT: 1000 * 60 * 5, -}; - -/** - * One computation unit. - * - * @description Represents one computation unit that will be run on the one provider machine (e.g. rendering of one frame of an animation). - */ -export class Task implements QueueableTask { - private state = TaskState.New; - private results?: OutputType; - private error?: Error; - private retriesCount = 0; - private listeners = new Set<(state: TaskState) => void>(); - private timeoutId?: NodeJS.Timeout; - private readonly timeout: number; - private readonly maxRetries: number; - private readonly activityReadySetupFunctions: Worker[]; - private activity?: Activity; - private networkNode?: NetworkNode; - - constructor( - public readonly id: string, - private worker: Worker, - options?: TaskOptions, - ) { - this.timeout = options?.timeout ?? DEFAULTS.TIMEOUT; - this.maxRetries = options?.maxRetries ?? DEFAULTS.MAX_RETRIES; - this.activityReadySetupFunctions = options?.activityReadySetupFunctions ?? []; - if (this.maxRetries < 0) { - throw new GolemConfigError("The maxRetries parameter cannot be less than zero"); - } - } - - onStateChange(listener: (state: TaskState) => void) { - this.listeners.add(listener); - } - cleanup() { - // prevent memory leaks - this.listeners.clear(); - } - init() { - this.state = TaskState.Queued; - } - - start(activity: Activity, networkNode?: NetworkNode) { - this.state = TaskState.Pending; - this.activity = activity; - this.networkNode = networkNode; - this.listeners.forEach((listener) => listener(this.state)); - this.timeoutId = setTimeout( - () => this.stop(undefined, new GolemTimeoutError(`Task ${this.id} timeout.`), true), - this.timeout, - ); - } - stop(results?: OutputType, error?: Error, retry = true) { - if (this.isFinished() || this.isRetry()) { - return; - } - clearTimeout(this.timeoutId); - if (error) { - this.error = error; - if (retry && this.retriesCount < this.maxRetries) { - this.state = TaskState.Retry; - ++this.retriesCount; - } else { - this.state = TaskState.Rejected; - } - } else { - this.state = TaskState.Done; - this.results = results; - } - this.listeners.forEach((listener) => listener(this.state)); - } - isQueueable(): boolean { - return this.state === TaskState.New || this.state === TaskState.Retry; - } - isRetry(): boolean { - return this.state === TaskState.Retry; - } - isDone(): boolean { - return this.state === TaskState.Done; - } - isFinished(): boolean { - return this.state === TaskState.Done || this.state === TaskState.Rejected; - } - isRejected(): boolean { - return this.state === TaskState.Rejected; - } - isQueued(): boolean { - return this.state === TaskState.Queued; - } - isPending(): boolean { - return this.state === TaskState.Pending; - } - isNew(): boolean { - return this.state === TaskState.New; - } - isFailed(): boolean { - return this.state === TaskState.Rejected || this.state === TaskState.Retry; - } - getResults(): OutputType | undefined { - return this.results; - } - getWorker(): Worker { - return this.worker; - } - getActivityReadySetupFunctions(): Worker[] { - return this.activityReadySetupFunctions; - } - getRetriesCount(): number { - return this.retriesCount; - } - getError(): Error | undefined { - return this.error; - } - getActivity(): Activity | undefined { - return this.activity; - } - getNetworkNode(): NetworkNode | undefined { - return this.networkNode; - } -} diff --git a/src/task/work.spec.ts b/src/task/work.spec.ts deleted file mode 100644 index 5529d414f..000000000 --- a/src/task/work.spec.ts +++ /dev/null @@ -1,272 +0,0 @@ -import { WorkContext } from "./work"; -import { Batch } from "./batch"; -import { LoggerMock, YagnaMock } from "../../tests/mock"; -import { ActivityStateEnum, ResultState } from "../activity"; -import { DownloadData, DownloadFile, Run, Script, Transfer, UploadData, UploadFile } from "../script"; -import { ActivityMock } from "../../tests/mock/activity.mock"; -import { agreement } from "../../tests/mock/entities/agreement"; -import { YagnaApi } from "../utils"; - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -const logger = new LoggerMock(); -describe("Work Context", () => { - let context: WorkContext; - let activity: ActivityMock; - let api: YagnaApi; - - beforeEach(() => { - logger.clear(); - api = new YagnaMock().getApi(); - activity = new ActivityMock("test_id", agreement, api); - context = new WorkContext(activity, { - yagnaOptions: api.yagnaOptions, - logger: logger, - }); - }); - - describe("Commands", () => { - let runSpy: jest.SpyInstance; - - beforeEach(() => { - runSpy = jest.spyOn(context as any, "runOneCommand"); - }); - - describe("run()", () => { - it("should execute run command", async () => { - const result = ActivityMock.createResult({ stdout: "Ok" }); - runSpy.mockImplementation((cmd) => { - expect(cmd).toBeInstanceOf(Run); - return Promise.resolve(result); - }); - expect(await context.run("rm -rf")).toBe(result); - }); - - it("should execute run command", async () => { - const result = ActivityMock.createResult({ stdout: "Ok" }); - runSpy.mockImplementation((cmd) => { - expect(cmd).toBeInstanceOf(Run); - return Promise.resolve(result); - }); - expect(await context.run("/bin/ls", ["-R"])).toBe(result); - }); - }); - - describe("spawn()", () => { - it("should execute spawn command", async () => { - const expectedResult = ActivityMock.createResult({ stdout: "Output", stderr: "Error", isBatchFinished: true }); - activity.mockResults([expectedResult]); - const remoteProcess = await context.runAndStream("rm -rf"); - for await (const result of remoteProcess.stdout) { - expect(result).toBe("Output"); - } - for await (const result of remoteProcess.stderr) { - expect(result).toBe("Error"); - } - const finalResult = await remoteProcess.waitForExit(); - expect(finalResult.result).toBe(ResultState.Ok); - }); - }); - - describe("transfer()", () => { - it("should execute transfer command", async () => { - const result = ActivityMock.createResult({ stdout: "Ok" }); - runSpy.mockImplementation((cmd) => { - expect(cmd).toBeInstanceOf(Transfer); - expect(cmd["from"]).toBe("http://golem.network/test.txt"); - expect(cmd["to"]).toBe("/golem/work/test.txt"); - return Promise.resolve(result); - }); - expect(await context.transfer("http://golem.network/test.txt", "/golem/work/test.txt")).toBe(result); - }); - }); - - describe("uploadFile()", () => { - it("should execute upload file command", async () => { - const result = ActivityMock.createResult(); - runSpy.mockImplementation((cmd: UploadFile) => { - expect(cmd).toBeInstanceOf(UploadFile); - expect(cmd["src"]).toBe("/tmp/file.txt"); - expect(cmd["dstPath"]).toBe("/golem/file.txt"); - - return Promise.resolve(result); - }); - expect(await context.uploadFile("/tmp/file.txt", "/golem/file.txt")).toBe(result); - }); - }); - - describe("uploadJson()", () => { - it("should execute upload json command", async () => { - const input = { hello: "world" }; - const result = ActivityMock.createResult(); - runSpy.mockImplementation((cmd: UploadData) => { - expect(cmd).toBeInstanceOf(UploadData); - const data = new TextDecoder().decode(cmd["src"]); - expect(JSON.parse(data)).toEqual(input); - expect(cmd["dstPath"]).toBe("/golem/file.txt"); - - return Promise.resolve(result); - }); - expect(await context.uploadJson(input, "/golem/file.txt")).toBe(result); - }); - }); - - describe("uploadData()", () => { - it("should execute upload json command", async () => { - const input = "Hello World"; - const result = ActivityMock.createResult(); - runSpy.mockImplementation((cmd: UploadData) => { - expect(cmd).toBeInstanceOf(UploadData); - expect(new TextDecoder().decode(cmd["src"])).toEqual(input); - expect(cmd["dstPath"]).toBe("/golem/file.txt"); - - return Promise.resolve(result); - }); - expect(await context.uploadData(new TextEncoder().encode(input), "/golem/file.txt")).toBe(result); - }); - }); - - describe("downloadFile()", () => { - it("should execute download file command", async () => { - const result = ActivityMock.createResult(); - runSpy.mockImplementation((cmd: UploadData) => { - expect(cmd).toBeInstanceOf(DownloadFile); - expect(cmd["srcPath"]).toBe("/golem/file.txt"); - expect(cmd["dstPath"]).toBe("/tmp/file.txt"); - - return Promise.resolve(result); - }); - expect(await context.downloadFile("/golem/file.txt", "/tmp/file.txt")).toBe(result); - }); - }); - - describe("downloadJson()", () => { - it("should execute download json command", async () => { - const json = { hello: "World" }; - const data = new TextEncoder().encode(JSON.stringify(json)).buffer; - const resultInput = ActivityMock.createResult({ data: data }); - runSpy.mockImplementation((cmd: DownloadData) => { - expect(cmd).toBeInstanceOf(DownloadData); - expect(cmd["srcPath"]).toBe("/golem/file.txt"); - - return Promise.resolve(resultInput); - }); - - const result = await context.downloadJson("/golem/file.txt"); - expect(result.result).toEqual(ResultState.Ok); - expect(result.data).toEqual(json); - }); - }); - - describe("downloadData()", () => { - it("should execute download data command", async () => { - const result = ActivityMock.createResult({ data: new Uint8Array(10) }); - runSpy.mockImplementation((cmd: UploadData) => { - expect(cmd).toBeInstanceOf(DownloadData); - expect(cmd["srcPath"]).toBe("/golem/file.txt"); - - return Promise.resolve(result); - }); - expect(await context.downloadData("/golem/file.txt")).toBe(result); - }); - }); - - describe("runOneCommand()", () => { - it("should abort if script.before() fails", async () => { - jest.spyOn(Script.prototype, "before").mockRejectedValue(new Error("[test]")); - try { - await context["runOneCommand"](new Run("test")); - fail("Should throw error"); - } catch (e) { - expect(e.message).toContain("[test]"); - } - }); - - it("should return result on success", async () => { - jest.spyOn(Script.prototype, "before").mockResolvedValue(undefined); - activity.mockResults([ActivityMock.createResult({ stdout: "SUCCESS" })]); - const result = await context["runOneCommand"](new Run("test")); - expect(result.result).toEqual(ResultState.Ok); - expect(result.stdout).toEqual("SUCCESS"); - }); - - it("should handle error result", async () => { - jest.spyOn(Script.prototype, "before").mockResolvedValue(undefined); - activity.mockResults([ActivityMock.createResult({ result: ResultState.Error, stdout: "FAILURE" })]); - const result = await context["runOneCommand"](new Run("test")); - expect(result.result).toEqual(ResultState.Error); - expect(result.stdout).toEqual("FAILURE"); - await logger.expectToInclude("Task error", { - error: "Error: undefined. Stdout: FAILURE. Stderr: undefined", - provider: "Test Provider", - }); - }); - }); - }); - - describe("getState()", () => { - it("should return activity state", async () => { - activity.mockCurrentState(ActivityStateEnum.Deployed); - await expect(context.getState()).resolves.toEqual(ActivityStateEnum.Deployed); - activity.mockCurrentState(ActivityStateEnum.Ready); - await expect(context.getState()).resolves.toEqual(ActivityStateEnum.Ready); - }); - }); - - describe("getWebsocketUri()", () => { - it("should throw error if there is no network node", () => { - expect(() => context.getIp()).toThrow(new Error("There is no network in this work context")); - }); - - it("should return websocket URI", () => { - (context as any)["networkNode"] = { - getWebsocketUri: (port: number) => `ws://localhost:${port}`, - }; - const spy = jest.spyOn(context["networkNode"] as any, "getWebsocketUri").mockReturnValue("ws://local"); - expect(context.getWebsocketUri(20)).toEqual("ws://local"); - expect(spy).toHaveBeenCalledWith(20); - }); - }); - - describe("getIp()", () => { - it("should throw error if there is no network node", () => { - expect(() => context.getIp()).toThrow(new Error("There is no network in this work context")); - }); - - it("should return ip address of provider vpn network node", () => { - (context as any)["networkNode"] = { - ip: "192.168.0.2", - }; - expect(context.getIp()).toEqual("192.168.0.2"); - }); - }); - - describe("beginBatch()", () => { - it("should create a batch object", () => { - const o = {}; - const spy = jest.spyOn(Batch, "create").mockReturnValue(o as any); - const result = context.beginBatch(); - expect(result).toBe(o); - expect(spy).toHaveBeenCalledWith(context["activity"], context["storageProvider"], context["logger"]); - }); - }); - - describe("setupActivity()", () => { - it("should call all setup functions in the order they were registered", async () => { - const calls: string[] = []; - const activityReadySetupFunctions = [ - async () => calls.push("1"), - async () => calls.push("2"), - async () => calls.push("3"), - ]; - context = new WorkContext(activity, { - yagnaOptions: api.yagnaOptions, - logger: logger, - activityReadySetupFunctions, - }); - - await context["setupActivity"](); - expect(calls).toEqual(["1", "2", "3"]); - }); - }); -}); diff --git a/src/utils/logger/jsonLogger.ts b/src/utils/logger/jsonLogger.ts deleted file mode 100644 index 3f5686066..000000000 --- a/src/utils/logger/jsonLogger.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Logger } from "./logger"; -import { pinoLogger } from "./pinoLogger"; - -/** - * Create a logger that writes a JSON object for every log line. - * @param filename path to the file to write to, if not specified, logs are written to stdout - */ -export function jsonLogger(filename?: string): Logger { - return pinoLogger({ - transport: { - target: "pino/file", - options: { - destination: filename, - ignore: "pid,hostname", - }, - }, - }); -} diff --git a/src/utils/logger/pinoLogger.ts b/src/utils/logger/pinoLogger.ts deleted file mode 100644 index 09a2e508f..000000000 --- a/src/utils/logger/pinoLogger.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Logger } from "./logger"; -import * as pino from "pino"; - -export function pinoLogger(optionsOrStream?: pino.LoggerOptions | pino.DestinationStream): Logger { - const logger = pino.pino(optionsOrStream); - - function debug(msg: string): void; - function debug(msg: string, ctx?: Record | Error) { - logger.debug(ctx, msg); - } - - function info(msg: string): void; - function info(msg: string, ctx?: Record | Error) { - logger.info(ctx, msg); - } - - function warn(msg: string): void; - function warn(msg: string, ctx?: Record | Error) { - logger.warn(ctx, msg); - } - - function error(msg: string): void; - function error(msg: string, ctx?: Record | Error) { - logger.error(ctx, msg); - } - - return { - child: (namespace: string) => pinoLogger(logger.child({ namespace })), - debug, - warn, - error, - info, - }; -} diff --git a/src/utils/yagna/activity.ts b/src/utils/yagna/activity.ts deleted file mode 100644 index fbf9a76fd..000000000 --- a/src/utils/yagna/activity.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { RequestorStateApi } from "ya-ts-client/dist/ya-activity/api"; -import { GolemPlatformError } from "../../error/golem-error"; -import { Agreement } from "ya-ts-client/dist/ya-market"; - -export class RequestorApi extends RequestorStateApi { - async getActivityAgreementId(activityId: string): Promise { - try { - const res = await fetch(this.basePath + "/activity/" + activityId + "/agreement", { - headers: { authorization: `Bearer ${this.configuration?.apiKey}` }, - }); - - if (!res.ok) { - throw new GolemPlatformError(`Failed to get activity agreement: ${res.statusText}`); - } - - return await res.json(); - } catch (e) { - throw new GolemPlatformError(`Failed to get activity agreement: ${e}`, e); - } - } -} diff --git a/src/utils/yagna/gsb.ts b/src/utils/yagna/gsb.ts deleted file mode 100644 index 7dabd0184..000000000 --- a/src/utils/yagna/gsb.ts +++ /dev/null @@ -1,57 +0,0 @@ -// TODO: replace with a proper REST API client once ya-client and ya-ts-client are updated -import { BaseAPI } from "ya-ts-client/dist/ya-net/base"; -import { GolemPlatformError } from "../../error/golem-error"; - -export type ServiceModel = { - servicesId: string; -}; - -export type GftpFileInfo = { - id: string; - url: string; -}; - -interface GsbRequestorApi { - createService(fileInfo: GftpFileInfo, components: string[]): Promise; - - deleteService(id: string): Promise; -} - -export class RequestorApi extends BaseAPI implements GsbRequestorApi { - async createService(fileInfo: GftpFileInfo, components: string[]): Promise { - const response = await fetch(`${this.basePath}/services`, { - method: "POST", - body: JSON.stringify({ - listen: { - on: `/public/gftp/${fileInfo.id}`, - components, - }, - }), - headers: { - "Content-Type": "application/json", - authorization: `Bearer ${this.configuration?.apiKey}`, - }, - }).catch((e) => { - throw new GolemPlatformError(`Failed to create service: ${e}`, e); - }); - - if (!response.ok) { - throw new GolemPlatformError(`Failed to create service: ${response.statusText}`); - } - - return await response.json(); - } - - async deleteService(id: string): Promise { - const response = await fetch(`${this.basePath}/services/${id}`, { - method: "DELETE", - headers: { authorization: `Bearer ${this.configuration?.apiKey}` }, - }).catch((e) => { - throw new GolemPlatformError(`Failed to delete service: ${e}`); - }); - - if (!response.ok) { - throw new GolemPlatformError(`Failed to delete service: ${response.statusText}`); - } - } -} diff --git a/src/utils/yagna/identity.ts b/src/utils/yagna/identity.ts deleted file mode 100644 index 88cd9ee36..000000000 --- a/src/utils/yagna/identity.ts +++ /dev/null @@ -1,28 +0,0 @@ -// TODO: replace with a proper REST API client once ya-client and ya-ts-client are updated -// https://github.com/golemfactory/golem-js/issues/290 -import { BaseAPI } from "ya-ts-client/dist/ya-net/base"; -import { GolemPlatformError } from "../../error/golem-error"; - -export interface IdentityModel { - identity: string; - name: string; - role: string; -} - -interface IdentityRequestorApi { - getIdentity(): Promise; -} - -export class RequestorApi extends BaseAPI implements IdentityRequestorApi { - async getIdentity(): Promise { - const res = await fetch(this.basePath + "/me", { - headers: { authorization: `Bearer ${this.configuration?.apiKey}` }, - }); - - if (!res.ok) { - throw new GolemPlatformError(`Failed to get identity: ${res.statusText}`); - } - - return await res.json(); - } -} diff --git a/src/utils/yagna/yagna.ts b/src/utils/yagna/yagna.ts deleted file mode 100644 index 3feea2913..000000000 --- a/src/utils/yagna/yagna.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { RequestorControlApi } from "ya-ts-client/dist/ya-activity/api"; -import { RequestorApi as MarketRequestorApi } from "ya-ts-client/dist/ya-market/api"; -import { RequestorApi as NetworkRequestorApi } from "ya-ts-client/dist/ya-net/api"; -import { RequestorApi as PaymentRequestorApi } from "ya-ts-client/dist/ya-payment/api"; -import { RequestorApi as IdentityRequestorApi } from "./identity"; -import { RequestorApi as GsbRequestorApi } from "./gsb"; -import { RequestorApi as RequestorStateApi } from "./activity"; -import { Agent } from "http"; -import { Configuration } from "ya-ts-client/dist/ya-payment"; -import * as EnvUtils from "../env"; -import { GolemConfigError, GolemPlatformError, GolemUserError } from "../../error/golem-error"; -import { v4 } from "uuid"; -import semverSatisfies from "semver/functions/satisfies"; -import semverCoerce from "semver/functions/coerce"; -import { Logger } from "../logger/logger"; -import { defaultLogger } from "../logger/defaultLogger"; - -export type YagnaApi = { - market: MarketRequestorApi; - activity: { control: RequestorControlApi; state: RequestorStateApi }; - net: NetworkRequestorApi; - payment: PaymentRequestorApi; - identity: IdentityRequestorApi; - gsb: GsbRequestorApi; - yagnaOptions: YagnaOptions; - appSessionId: string; -}; - -export type YagnaOptions = { - apiKey?: string; - basePath?: string; - abortController?: AbortController; - logger?: Logger; -}; - -type YagnaVersionInfo = { - // @example 0.13.2 - version: string; - // @example v0.13.2 - name: string; - seen: boolean; - // @example "2023-12-07T14:23:48" - releaseTs: string; - insertionTs: string; - updateTs: string; -}; - -type YagnaVersionResponse = { - current: YagnaVersionInfo; - pending: YagnaVersionInfo | null; -}; - -const CONNECTIONS_ERROR_CODES = ["ECONNREFUSED"]; - -export const MIN_SUPPORTED_YAGNA = "0.13.2"; - -export class Yagna { - private readonly httpAgent: Agent; - private readonly controller: AbortController; - protected readonly apiKey: string; - protected readonly apiBaseUrl: string; - private readonly api: YagnaApi; - private readonly logger: Logger; - - /** Used to track the lifetime of the interceptor and align it with the lifetime of API instance */ - private axiosErrorInterceptorId: number; - - constructor(options?: YagnaOptions) { - this.httpAgent = new Agent({ keepAlive: true }); - this.controller = options?.abortController ?? new AbortController(); - this.apiKey = options?.apiKey || EnvUtils.getYagnaAppKey(); - if (!this.apiKey) throw new GolemConfigError("Api key not defined"); - this.apiBaseUrl = options?.basePath || EnvUtils.getYagnaApiUrl(); - this.logger = options?.logger ?? defaultLogger("yagna"); - - this.api = this.createApi(); - this.axiosErrorInterceptorId = this.addErrorHandler(this.api); - } - - getApi(): YagnaApi { - return this.api; - } - - async connect() { - this.logger.info("Connecting to yagna"); - await this.assertSupportedVersion(); - return this.api.identity.getIdentity(); - } - - private async assertSupportedVersion() { - this.logger.debug("Checking yagna version support"); - const version = await this.getVersion(); - - const normVersion = semverCoerce(version); - if (!normVersion) { - throw new GolemPlatformError( - `Unreadable yana version '${version}'. Can't proceed without checking yagna version support status.`, - ); - } - - if (!semverSatisfies(normVersion, `>=${MIN_SUPPORTED_YAGNA}`)) { - throw new GolemPlatformError( - `You run yagna in version ${version} and the minimal version supported by the SDK is ${MIN_SUPPORTED_YAGNA}. ` + - `Please consult the golem-js README to find matching SDK version or upgrade your yagna installation.`, - ); - } - - return normVersion.version; - } - - async end(): Promise { - this.logger.debug("Disconnecting from yagna"); - this.controller.abort(); - this.httpAgent.destroy?.(); - this.removeErrorHandler(this.api); - } - - public async getVersion(): Promise { - try { - const res: YagnaVersionResponse = await fetch(`${this.apiBaseUrl}/version/get`, { - method: "GET", - signal: this.controller.signal, - }).then((res) => res.json()); - - return res.current.version; - } catch (err) { - throw new GolemPlatformError(`Failed to establish yagna version due to: ${err}`, err); - } - } - - protected createApi(): YagnaApi { - const apiConfig = this.getApiConfig(); - - const api = { - market: new MarketRequestorApi(apiConfig, this.getApiUrl("market")), - activity: { - control: new RequestorControlApi(apiConfig, this.getApiUrl("activity")), - state: new RequestorStateApi(apiConfig, this.getApiUrl("activity")), - }, - net: new NetworkRequestorApi(apiConfig, this.getApiUrl("net")), - payment: new PaymentRequestorApi(apiConfig, this.getApiUrl("payment")), - identity: new IdentityRequestorApi(apiConfig, this.getApiUrl()), - gsb: new GsbRequestorApi(apiConfig, this.getApiUrl("gsb")), - yagnaOptions: { - logger: this.logger, - apiKey: this.apiKey, - basePath: this.apiBaseUrl, - }, - appSessionId: v4(), - }; - - return api; - } - - protected getApiConfig(): Configuration { - return new Configuration({ - apiKey: this.apiKey, - accessToken: this.apiKey, - baseOptions: { - httpAgent: this.httpAgent, - signal: this.controller.signal, - }, - }); - } - - protected getApiUrl(apiName?: string): string { - return apiName ? `${this.apiBaseUrl}/${apiName}-api/v1` : this.apiBaseUrl; - } - - protected errorHandler(error: Error): Promise { - this.logger.debug("Invoked error handler for error: %o", error); - if ("code" in error && CONNECTIONS_ERROR_CODES.includes((error.code as string) ?? "")) { - return Promise.reject( - new GolemUserError( - `No connection to Yagna. Make sure the service is running at the address ${this.apiBaseUrl}`, - error, - ), - ); - } - return Promise.reject(new GolemPlatformError(`Yagna request failed. ${error}`, error)); - } - - protected addErrorHandler(api: YagnaApi) { - /** - * Ugly solution until Yagna binding is refactored or replaced, - * and it will be possible to pass interceptors as the config params. - * - * All RequestorAPI instances (market, identity, payment, etc.) use the same Axios instance, - * so it is enough to add one interceptor to one of them to make it effective in each API. - */ - this.logger.debug("Attaching error handler to internal axios instance"); - return api.identity["axios"].interceptors.response.use(undefined, this.errorHandler.bind(this)); - } - - protected removeErrorHandler(api: YagnaApi) { - this.logger.debug("Removing error handler to internal axios instance"); - api.identity["axios"].interceptors.response.eject(this.axiosErrorInterceptorId); - } -} diff --git a/tests/cypress/fixtures/activity_events.json b/tests/cypress/fixtures/activity_events.json deleted file mode 100644 index 9319da58b..000000000 --- a/tests/cypress/fixtures/activity_events.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "type": "runtime", - "data": "{\"batch_id\":\"04a9b0f49e564db99e6f15ba95c35817\",\"index\":0,\"timestamp\":\"2022-06-23T10:42:38.626573153\",\"kind\":{\"stdout\":\"{\\\"startMode\\\":\\\"blocking\\\",\\\"valid\\\":{\\\"Ok\\\":\\\"\\\"},\\\"vols\\\":[]}\"}}" - }, - { - "type": "runtime", - "data": "{\"batch_id\":\"04a9b0f49e564db99e6f15ba95c35817\",\"index\":0,\"timestamp\":\"2022-06-23T10:42:38.626958777\",\"kind\":{\"finished\":{\"return_code\":0,\"message\":null}}}" - }, - { - "type": "runtime", - "data": "{\"batch_id\":\"04a9b0f49e564db99e6f15ba95c35817\",\"index\":1,\"timestamp\":\"2022-06-23T10:42:38.626960850\",\"kind\":{\"started\":{\"command\":{\"start\":{\"args\":[]}}}}}" - }, - { - "type": "runtime", - "data": "{\"batch_id\":\"04a9b0f49e564db99e6f15ba95c35817\",\"index\":1,\"timestamp\":\"2022-06-23T10:42:39.946031527\",\"kind\":{\"finished\":{\"return_code\":0,\"message\":null}}}" - }, - { - "type": "runtime", - "data": "{\"batch_id\":\"04a9b0f49e564db99e6f15ba95c35817\",\"index\":2,\"timestamp\":\"2022-06-23T10:42:39.946034161\",\"kind\":{\"started\":{\"command\":{\"run\":{\"entry_point\":\"/bin/sh\",\"args\":[\"-c\",\"echo +\\\"test\\\"\"],\"capture\":{\"stdout\":{\"stream\":{\"format\":\"str\"}},\"stderr\":{\"stream\":{\"format\":\"str\"}}}}}}}}" - }, - { - "type": "runtime", - "data": "{\"batch_id\":\"04a9b0f49e564db99e6f15ba95c35817\",\"index\":2,\"timestamp\":\"2022-06-23T10:42:39.957927713\",\"kind\":{\"stdout\":\"test event 1\"}}" - }, - { - "type": "runtime", - "data": "{\"batch_id\":\"04a9b0f49e564db99e6f15ba95c35817\",\"index\":2,\"timestamp\":\"2022-06-23T10:42:39.958238754\",\"kind\":{\"finished\":{\"return_code\":0,\"message\":null}}}" - }, - { - "type": "runtime", - "data": "{\"batch_id\":\"04a9b0f49e564db99e6f15ba95c35817\",\"index\":3,\"timestamp\":\"2022-06-23T10:42:39.962014674\",\"kind\":{\"started\":{\"command\":{\"terminate\":{}}}}}" - }, - { - "type": "runtime", - "data": "{\"batch_id\":\"04a9b0f49e564db99e6f15ba95c35817\",\"index\":3,\"timestamp\":\"2022-06-23T10:42:40.009603540\",\"kind\":{\"finished\":{\"return_code\":0,\"message\":null}}}" - } -] diff --git a/tests/cypress/fixtures/example.json b/tests/cypress/fixtures/example.json deleted file mode 100644 index 02e425437..000000000 --- a/tests/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} diff --git a/tests/cypress/fixtures/golem.png b/tests/cypress/fixtures/golem.png deleted file mode 100644 index c5b60776a..000000000 Binary files a/tests/cypress/fixtures/golem.png and /dev/null differ diff --git a/tests/cypress/support/e2e.ts b/tests/cypress/support/e2e.ts index c8db190d8..c1a920a1c 100644 --- a/tests/cypress/support/e2e.ts +++ b/tests/cypress/support/e2e.ts @@ -18,7 +18,7 @@ beforeEach(() => { cy.intercept("GET", "https://unpkg.com/@golem-sdk/golem-js", (req) => { - req.url = "http://localhost:3000/golem-js.min.js"; + req.url = "http://localhost:3000/dist/golem-js.min.js"; req.continue(); }); }); diff --git a/tests/cypress/ui/docs-example-transfer-data.cy.ts b/tests/cypress/ui/docs-example-transfer-data.cy.ts deleted file mode 100644 index dad36c792..000000000 --- a/tests/cypress/ui/docs-example-transfer-data.cy.ts +++ /dev/null @@ -1,13 +0,0 @@ -describe("Docs Examples Transfer Data", () => { - it("should transfer image file to provider", () => { - cy.visit("/docs-example-transfer-data"); - cy.get("#YAGNA_API_BASEPATH").clear().type(Cypress.env("YAGNA_API_BASEPATH")); - cy.get("#SUBNET_TAG").clear().type(Cypress.env("YAGNA_SUBNET")); - cy.fixture("golem.png", { encoding: null }).as("imageFile"); - cy.get("#MEME_IMG").selectFile("@imageFile"); - cy.get("#RUN").click(); - cy.get("#RESULT_MEME").should("have.attr", "src").and("contain", "blob:http://localhost:3000", { timeout: 60000 }); - cy.get("#logs").contains("Task computed"); - cy.get("#logs").contains("Task Executor has shut down"); - }); -}); diff --git a/tests/cypress/ui/docs-example-transfer-json.cy.ts b/tests/cypress/ui/docs-example-transfer-json.cy.ts deleted file mode 100644 index 65fdb4e63..000000000 --- a/tests/cypress/ui/docs-example-transfer-json.cy.ts +++ /dev/null @@ -1,11 +0,0 @@ -describe("Docs Examples Transfer JSON", () => { - it("should transfer json to provider", () => { - cy.visit("/docs-example-transfer-json"); - cy.get("#YAGNA_API_BASEPATH").clear().type(Cypress.env("YAGNA_API_BASEPATH")); - cy.get("#SUBNET_TAG").clear().type(Cypress.env("YAGNA_SUBNET")); - cy.get("#echo").click(); - cy.get("#results").should("include.text", `"Hello World"`, { timeout: 60000 }); - cy.get("#logs").contains("Task computed"); - cy.get("#logs").contains("Task Executor has shut down"); - }); -}); diff --git a/tests/cypress/ui/docs-quickstart.cy.ts b/tests/cypress/ui/docs-quickstart.cy.ts deleted file mode 100644 index 4abd87352..000000000 --- a/tests/cypress/ui/docs-quickstart.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -describe("Docs Examples Quickstart", () => { - it("should print hello world", () => { - cy.visit("/docs-quickstart"); - cy.get("#YAGNA_API_BASEPATH").clear().type(Cypress.env("YAGNA_API_BASEPATH")); - cy.get("#SUBNET_TAG").clear().type(Cypress.env("YAGNA_SUBNET")); - cy.get("#echo").click(); - cy.get("#results").should("include.text", "Hello World", { timeout: 60000 }); - cy.get("#logs").contains("Task Executor has shut down"); - }); -}); diff --git a/tests/cypress/ui/docs-tutorial.cy.ts b/tests/cypress/ui/docs-tutorial.cy.ts deleted file mode 100644 index 97f4af931..000000000 --- a/tests/cypress/ui/docs-tutorial.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -describe("Docs Examples Tutorial", () => { - it("should print hello world", () => { - cy.visit("/docs-tutorial"); - cy.get("#YAGNA_API_BASEPATH").clear().type(Cypress.env("YAGNA_API_BASEPATH")); - cy.get("#SUBNET_TAG").clear().type(Cypress.env("YAGNA_SUBNET")); - cy.get("#echo").click(); - cy.get("#results").should("include.text", "Hello World", { timeout: 60000 }); - cy.get("#logs").contains("Task Executor has shut down"); - }); -}); diff --git a/tests/cypress/ui/hello-world.cy.ts b/tests/cypress/ui/hello-world.cy.ts index 34199739e..31597c681 100644 --- a/tests/cypress/ui/hello-world.cy.ts +++ b/tests/cypress/ui/hello-world.cy.ts @@ -4,8 +4,9 @@ describe("Test TaskExecutor API", () => { cy.get("#YAGNA_APPKEY").clear().type(Cypress.env("YAGNA_APPKEY")); cy.get("#YAGNA_API_BASEPATH").clear().type(Cypress.env("YAGNA_API_BASEPATH")); cy.get("#SUBNET_TAG").clear().type(Cypress.env("YAGNA_SUBNET")); + cy.get("#PAYMENT_NETWORK").clear().type(Cypress.env("PAYMENT_NETWORK")); cy.get("#echo").click(); - cy.get("#results").should("include.text", "Hello World", { timeout: 60000 }); - cy.get("#logs").contains("Task Executor has shut down"); + cy.get("#results").should("include.text", "Hello Golem", { timeout: 60000 }); + cy.get("#results").should("include.text", "Finalized renting process", { timeout: 10000 }); }); }); diff --git a/tests/cypress/ui/image.cy.ts b/tests/cypress/ui/image.cy.ts deleted file mode 100644 index 02cf85f44..000000000 --- a/tests/cypress/ui/image.cy.ts +++ /dev/null @@ -1,14 +0,0 @@ -describe("Test TaskExecutor API", () => { - it("should run transfer file example", () => { - cy.visit("/image"); - cy.get("#YAGNA_APPKEY").clear().type(Cypress.env("YAGNA_APPKEY")); - cy.get("#YAGNA_API_BASEPATH").clear().type(Cypress.env("YAGNA_API_BASEPATH")); - cy.get("#SUBNET_TAG").clear().type(Cypress.env("YAGNA_SUBNET")); - cy.fixture("golem.png", { encoding: null }).as("imageFile"); - cy.get("#MEME_IMG").selectFile("@imageFile"); - cy.get("#RUN").click(); - cy.get("#RESULT_MEME").should("have.attr", "src").and("contain", "blob:http://localhost:3000", { timeout: 60000 }); - cy.get("#logs").contains("Task computed"); - cy.get("#logs").contains("Task Executor has shut down"); - }); -}); diff --git a/tests/docker/Provider.Dockerfile b/tests/docker/Provider.Dockerfile index 9c62cbbd1..cbcd4dede 100644 --- a/tests/docker/Provider.Dockerfile +++ b/tests/docker/Provider.Dockerfile @@ -1,5 +1,5 @@ ARG UBUNTU_VERSION=22.04 -ARG YA_CORE_PROVIDER_VERSION=v0.12.3 +ARG YA_CORE_PROVIDER_VERSION=v0.15.2 ARG YA_WASI_VERSION=v0.2.2 ARG YA_VM_VERSION=v0.3.0 diff --git a/tests/docker/Requestor.Dockerfile b/tests/docker/Requestor.Dockerfile index d456154ce..8c7f8d05a 100644 --- a/tests/docker/Requestor.Dockerfile +++ b/tests/docker/Requestor.Dockerfile @@ -1,5 +1,5 @@ ARG UBUNTU_VERSION=22.04 -ARG YA_CORE_REQUESTOR_VERSION=v0.12.3 +ARG YA_CORE_REQUESTOR_VERSION=v0.15.2 FROM node:18.18.2 ARG YA_CORE_REQUESTOR_VERSION diff --git a/tests/docker/data-node/ya-provider/presets.json b/tests/docker/data-node/ya-provider/presets.json index 367c0199f..9e392f582 100644 --- a/tests/docker/data-node/ya-provider/presets.json +++ b/tests/docker/data-node/ya-provider/presets.json @@ -1,34 +1,32 @@ { + "ver": "V1", "active": ["wasmtime", "vm"], "presets": [ { "name": "default", "exeunit-name": "wasmtime", "pricing-model": "linear", - "usage-coeffs": { - "cpu": 0.100000001, - "duration": 0.0, - "initial": 0.0 - } + "initial-price": 0.0, + "usage-coeffs": {} }, { "name": "vm", "exeunit-name": "vm", "pricing-model": "linear", + "initial-price": 0.0, "usage-coeffs": { - "cpu": 0.100000001, - "duration": 0.0, - "initial": 0.0 + "golem.usage.cpu_sec": 0.0001, + "golem.usage.duration_sec": 0.0 } }, { "name": "wasmtime", "exeunit-name": "wasmtime", "pricing-model": "linear", + "initial-price": 0.0, "usage-coeffs": { - "cpu": 0.100000001, - "duration": 0.0, - "initial": 0.0 + "golem.usage.cpu_sec": 0.0001, + "golem.usage.duration_sec": 0.0 } } ] diff --git a/tests/docker/docker-compose.yml b/tests/docker/docker-compose.yml index aa38e11f1..314b31d53 100644 --- a/tests/docker/docker-compose.yml +++ b/tests/docker/docker-compose.yml @@ -60,6 +60,7 @@ services: - GSB_URL=tcp://0.0.0.0:7464 - YAGNA_SUBNET=${YAGNA_SUBNET:-golemjstest} - YAGNA_APPKEY=try_golem + - PAYMENT_NETWORK=${PAYMENT_NETWORK} healthcheck: test: ["CMD-SHELL", "curl -s -o /dev/null -w '%{http_code}' http://localhost:7465 | grep -q 401"] diff --git a/tests/docker/fundRequestor.sh b/tests/docker/fundRequestor.sh index f0912f1b8..d828e4b74 100755 --- a/tests/docker/fundRequestor.sh +++ b/tests/docker/fundRequestor.sh @@ -1,7 +1,7 @@ #!/bin/bash -for i in {1..3}; do - yagna payment fund --network holesky && exit 0 +for i in {1..5}; do + yagna payment fund --network ${PAYMENT_NETWORK} && yagna payment status --network ${PAYMENT_NETWORK} && exit 0 done echo "yagna payment fund failed" >&2 diff --git a/tests/docker/startRequestor.sh b/tests/docker/startRequestor.sh index c92860449..333fad050 100755 --- a/tests/docker/startRequestor.sh +++ b/tests/docker/startRequestor.sh @@ -2,7 +2,7 @@ get_funds_from_faucet() { echo "Sending request to the faucet" - yagna payment fund --network holesky + yagna payment fund --network ${PAYMENT_NETWORK} } echo "Starting Yagna" yagna service run --api-allow-origin="*" diff --git a/tests/e2e/_setupEnv.ts b/tests/e2e/_setupEnv.ts new file mode 100644 index 000000000..e258417c4 --- /dev/null +++ b/tests/e2e/_setupEnv.ts @@ -0,0 +1 @@ +process.env.DEBUG = "golem-js:*"; diff --git a/tests/e2e/_setupLogging.ts b/tests/e2e/_setupLogging.ts index e2ae407f2..ea57cafde 100644 --- a/tests/e2e/_setupLogging.ts +++ b/tests/e2e/_setupLogging.ts @@ -5,13 +5,18 @@ * which is considered as an issue when using jest. Once we change the way we store Goth related logs, then we'll be * able to remove this file. */ +import chalk from "chalk"; const jestConsole = console; -beforeAll(() => { - global.console = require("console"); +beforeAll(async () => { + global.console = await import("console"); }); afterAll(() => { global.console = jestConsole; }); + +beforeEach(() => { + console.log(chalk.yellow(`\n\n---- Starting test: "${expect.getState().currentTestName}" ----\n\n`)); +}); diff --git a/tests/e2e/blender.spec.ts b/tests/e2e/blender.spec.ts deleted file mode 100644 index e63533a4b..000000000 --- a/tests/e2e/blender.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { TaskExecutor } from "../../src"; -import { LoggerMock } from "../mock"; -import fs from "fs"; - -const logger = new LoggerMock(false); -const blenderParams = (frame) => ({ - scene_file: "/golem/resource/scene.blend", - resolution: [400, 300], - use_compositing: false, - crops: [ - { - outfilebasename: "out", - borders_x: [0.0, 1.0], - borders_y: [0.0, 1.0], - }, - ], - samples: 100, - frames: [frame], - output_format: "PNG", - RESOURCES_DIR: "/golem/resources", - WORK_DIR: "/golem/work", - OUTPUT_DIR: "/golem/output", -}); - -describe("Blender rendering", function () { - it( - "should render images by blender", - async () => { - const executor = await TaskExecutor.create({ - package: "golem/blender:latest", - logger, - }); - - executor.onActivityReady(async (ctx) => { - const sourcePath = fs.realpathSync(__dirname + "/../mock/fixtures/cubes.blend"); - await ctx.uploadFile(sourcePath, "/golem/resource/scene.blend"); - }); - - const data = [0, 10, 20, 30, 40, 50]; - - const futureResults = data.map((frame) => - executor.run(async (ctx) => { - const result = await ctx - .beginBatch() - .uploadJson(blenderParams(frame), "/golem/work/params.json") - .run("/golem/entrypoints/run-blender.sh") - .downloadFile(`/golem/output/out${frame?.toString().padStart(4, "0")}.png`, `output_${frame}.png`) - .end() - .catch((error) => console.error(error.toString())); - return result ? `output_${frame}.png` : ""; - }), - ); - - const results = await Promise.all(futureResults); - const expectedResults = data.map((d) => `output_${d}.png`); - - for (const result of results) { - expect(expectedResults).toContain(result); - } - - for (const file of expectedResults) { - expect(fs.existsSync(`${process.env.GOTH_GFTP_VOLUME || ""}${file}`)).toEqual(true); - } - - await executor.shutdown(); - }, - 1000 * 240, - ); -}); diff --git a/tests/e2e/express.spec.ts b/tests/e2e/express.spec.ts index c4b14554a..bbe631076 100644 --- a/tests/e2e/express.spec.ts +++ b/tests/e2e/express.spec.ts @@ -1,13 +1,14 @@ import express from "express"; -import { GolemNetwork, JobState } from "../../src/experimental"; +import { JobManager, JobState } from "../../src/experimental"; import supertest from "supertest"; import fs from "fs"; +import { jest } from "@jest/globals"; describe("Express", function () { - let golemClient: GolemNetwork; + let golemClient: JobManager; const consoleSpy = jest.fn(); beforeEach(async () => { - golemClient = new GolemNetwork({}); + golemClient = new JobManager(); await golemClient.init(); consoleSpy.mockReset(); }); @@ -27,8 +28,19 @@ describe("Express", function () { return; } const job = golemClient.createJob({ - package: { - imageTag: "severyn/espeak:latest", + demand: { + workload: { + imageTag: "severyn/espeak:latest", + }, + }, + market: { + rentHours: 1, + pricing: { + model: "linear", + maxStartPrice: 1, + maxEnvPerHourPrice: 1, + maxCpuPerHourPrice: 1, + }, }, }); @@ -45,9 +57,9 @@ describe("Express", function () { consoleSpy("Job succeeded", job.results); }); - job.startWork(async (ctx) => { + job.startWork(async (exe) => { const fileName = `EXPRESS_SPEC_OUTPUT.wav`; - await ctx + await exe .beginBatch() .run(`espeak "${req.body}" -w /golem/output/output.wav`) .downloadFile("/golem/output/output.wav", fileName) diff --git a/tests/e2e/gftp.spec.ts b/tests/e2e/gftp.spec.ts deleted file mode 100644 index ec6bc2a98..000000000 --- a/tests/e2e/gftp.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { TaskExecutor } from "../../src"; -import { LoggerMock } from "../mock"; -import fs from "fs"; - -const logger = new LoggerMock(false); - -describe("GFTP transfers", function () { - it( - "should upload and download big files simultaneously", - async () => { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - }); - - executor.onActivityReady(async (ctx) => { - const sourcePath = fs.realpathSync(__dirname + "/../mock/fixtures/eiffel.blend"); - await ctx.uploadFile(sourcePath, "/golem/work/eiffel.blend"); - }); - - const data = [0, 1, 2, 3, 4, 5]; - - const futureResults = data.map((frame) => - executor.run(async (ctx) => { - const result = await ctx - .beginBatch() - .run("ls -Alh /golem/work/eiffel.blend") - .downloadFile(`/golem/work/eiffel.blend`, `copy_${frame}.blend`) - .end() - .catch((error) => console.error(error.toString())); - return result ? `copy_${frame}.blend` : ""; - }), - ); - const results = await Promise.all(futureResults); - - const expectedResults = data.map((d) => `copy_${d}.blend`); - - for (const result of results) { - expect(expectedResults).toContain(result); - } - - for (const file of expectedResults) { - expect(fs.existsSync(file)).toEqual(true); - } - - await executor.shutdown(); - }, - 1000 * 240, - ); -}); diff --git a/tests/e2e/jest.config.json b/tests/e2e/jest.config.json index 12b3bf748..0e96155ee 100644 --- a/tests/e2e/jest.config.json +++ b/tests/e2e/jest.config.json @@ -1,8 +1,18 @@ { - "preset": "ts-jest", "testEnvironment": "node", + "setupFiles": ["/_setupEnv.ts"], "setupFilesAfterEnv": ["/_setupLogging.ts"], "testTimeout": 180000, + "extensionsToTreatAsEsm": [".ts"], + "transform": { + "^.+\\.tsx?$": [ + "ts-jest", + { + "tsconfig": "tsconfig.spec.json", + "useESM": true + } + ] + }, "reporters": [ [ "jest-junit", diff --git a/tests/e2e/proxy.spec.ts b/tests/e2e/proxy.spec.ts deleted file mode 100644 index 31c0ee90b..000000000 --- a/tests/e2e/proxy.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TaskExecutor } from "../../src"; -import { LoggerMock } from "../mock"; -import { sleep } from "../../src/utils"; -import fs from "fs"; - -const logger = new LoggerMock(false); - -describe("TcpProxy", function () { - beforeEach(function () { - logger.clear(); - }); - it("should send and receive message to the http server on the provider", async () => { - const executor = await TaskExecutor.create({ - package: "golem/node:latest", - capabilities: ["vpn"], - networkIp: "192.168.0.0/24", - logger, - }); - let response; - let providerStdout = ""; - await executor.run(async (ctx) => { - await ctx.uploadFile(fs.realpathSync(__dirname + "../../../examples/proxy/server.js"), "/golem/work/server.js"); - const server = await ctx.runAndStream("node /golem/work/server.js"); - server.stdout.on("data", (data) => (providerStdout += data.toString())); - const proxy = ctx.createTcpProxy(80); - await proxy.listen(7777); - await sleep(10); - response = await fetch("http://localhost:7777"); - await proxy.close(); - }); - await executor.shutdown(); - expect((await response.text()).trim()).toEqual("Hello Golem!"); - expect(providerStdout).toContain('HTTP server started at "http://localhost:80"'); - }); -}); diff --git a/tests/e2e/resourceRentalPool.spec.ts b/tests/e2e/resourceRentalPool.spec.ts new file mode 100644 index 000000000..a1e06d180 --- /dev/null +++ b/tests/e2e/resourceRentalPool.spec.ts @@ -0,0 +1,264 @@ +import { Subscription } from "rxjs"; +import { Allocation, DraftOfferProposalPool, GolemAbortError, GolemNetwork } from "../../src"; + +describe("ResourceRentalPool", () => { + const glm = new GolemNetwork(); + let proposalPool: DraftOfferProposalPool; + let allocation: Allocation; + let draftProposalSub: Subscription; + + beforeAll(async () => { + await glm.connect(); + allocation = await glm.payment.createAllocation({ + budget: 5, + // 30 minutes + expirationSec: 60 * 30, + }); + }); + + afterAll(async () => { + await glm.payment.releaseAllocation(allocation); + await glm.disconnect(); + }); + + beforeEach(async () => { + proposalPool = new DraftOfferProposalPool(); + const demandSpecification = await glm.market.buildDemandDetails( + { + workload: { + imageTag: "golem/alpine:latest", + }, + }, + { + rentHours: 1, + pricing: { + model: "burn-rate", + avgGlmPerHour: 1, + }, + }, + allocation, + ); + + const draftProposal$ = glm.market.collectDraftOfferProposals({ + demandSpecification, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + }); + + draftProposalSub = proposalPool.readFrom(draftProposal$); + }); + + afterEach(async () => { + draftProposalSub.unsubscribe(); + await proposalPool.clear(); + }); + + it("should run a simple script on the activity from the pool", async () => { + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 1 }); + pool.events.on("errorCreatingRental", ({ error }) => { + throw error; + }); + const resourceRental = await pool.acquire(); + expect(pool.getSize()).toEqual(1); + expect(pool.getAvailableSize()).toEqual(0); + expect(pool.getBorrowedSize()).toEqual(1); + const result = await resourceRental.getExeUnit().then((exe) => exe.run("echo Hello World")); + expect(result.stdout?.toString().trim()).toEqual("Hello World"); + await pool.destroy(resourceRental); + await pool.drainAndClear(); + }); + + it("should prepare two activity ready to use", async () => { + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 2 }); + pool.events.on("errorCreatingRental", ({ error }) => { + throw error; + }); + await pool.ready(); + expect(pool.getSize()).toEqual(2); + expect(pool.getAvailableSize()).toEqual(2); + expect(pool.getBorrowedSize()).toEqual(0); + const rental1 = await pool.acquire(); + const activity1 = await rental1.getExeUnit(); + expect(pool.getAvailableSize()).toEqual(1); + expect(pool.getBorrowedSize()).toEqual(1); + const rental2 = await pool.acquire(); + const activity2 = await rental2.getExeUnit(); + expect(pool.getAvailableSize()).toEqual(0); + expect(pool.getBorrowedSize()).toEqual(2); + expect(activity1).toBeDefined(); + expect(activity2).toBeDefined(); + expect(activity1.provider.id).not.toEqual(activity2.provider.id); + await pool.release(rental1); + expect(pool.getAvailableSize()).toEqual(1); + expect(pool.getBorrowedSize()).toEqual(1); + await pool.release(rental2); + expect(pool.getAvailableSize()).toEqual(2); + expect(pool.getBorrowedSize()).toEqual(0); + await pool.drainAndClear(); + }); + + it("should release the activity and reuse it again", async () => { + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 1 }); + pool.events.on("errorCreatingRental", ({ error }) => { + throw error; + }); + const rental = await pool.acquire(); + const activity = await rental.getExeUnit(); + const result1 = await activity.run("echo result-1"); + expect(result1.stdout?.toString().trim()).toEqual("result-1"); + await pool.release(rental); + const sameRental = await pool.acquire(); + const activityAfterRelease = await sameRental.getExeUnit(); + const result2 = await activityAfterRelease.run("echo result-2"); + expect(result2.stdout?.toString().trim()).toEqual("result-2"); + await pool.destroy(sameRental); + expect(activity.activity.id).toEqual(activityAfterRelease.activity.id); + await pool.drainAndClear(); + }); + + it("should terminate all agreements after drain and clear the poll", async () => { + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 2 }); + pool.events.on("errorDestroyingRental", ({ error }) => { + throw error; + }); + const agreementTerminatedIds: string[] = []; + pool.events.on("destroyed", ({ agreement }) => agreementTerminatedIds.push(agreement.id)); + + const rental1 = await pool.acquire(); + const rental2 = await pool.acquire(); + + const activity1 = await rental1.getExeUnit(); + const activity2 = await rental2.getExeUnit(); + + await activity1.run("echo result-1"); + await activity2.run("echo result-2"); + + await pool.release(rental1); + await pool.release(rental2); + await pool.drainAndClear(); + expect(agreementTerminatedIds.sort()).toEqual( + [activity1.activity.agreement.id, activity2.activity.agreement.id].sort(), + ); + }); + + it("should establish a connection between two activities from pool via vpn", async () => { + const network = await glm.network.createNetwork(); + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 2, network }); + pool.events.on("errorCreatingRental", ({ error }) => { + throw error; + }); + const resourceRental1 = await pool.acquire(); + const resourceRental2 = await pool.acquire(); + const exe1 = await resourceRental1.getExeUnit(); + const exe2 = await resourceRental2.getExeUnit(); + const result1 = await exe1.run(`ping ${exe2.getIp()} -c 4`); + const result2 = await exe2.run(`ping ${exe1.getIp()} -c 4`); + expect(result1.stdout?.toString().trim()).toMatch("4 packets transmitted, 4 packets received, 0% packet loss"); + expect(result2.stdout?.toString().trim()).toMatch("4 packets transmitted, 4 packets received, 0% packet loss"); + expect(Object.keys(network.getNetworkInfo().nodes)).toEqual(["192.168.0.1", "192.168.0.2", "192.168.0.3"]); + await pool.destroy(resourceRental1); + await pool.destroy(resourceRental2); + await pool.drainAndClear(); + await glm.network.removeNetwork(network); + }); + + it("should not rent more resources than maximum size", async () => { + const maxPoolSize = 3; + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { + poolSize: { min: 1, max: maxPoolSize }, + }); + const poolSizesDuringWork: number[] = []; + pool.events.on("acquired", () => poolSizesDuringWork.push(pool.getSize())); + const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + await Promise.allSettled( + data.map((item) => + pool.withRental((rental) => + rental.getExeUnit().then((exe) => exe.run(`echo ${item} from provider ${exe.provider.name}`)), + ), + ), + ); + expect(Math.max(...poolSizesDuringWork)).toEqual(maxPoolSize); + }); + + it("should abort acquiring resource rental by signal", async () => { + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 1 }); + const abortController = new AbortController(); + abortController.abort(); + await expect(pool.acquire(abortController.signal)).rejects.toThrow("The signing of the agreement has been aborted"); + }); + + it("should abort acquiring resource rental by timeout", async () => { + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 1 }); + await expect(pool.acquire(1_000)).rejects.toThrow("Could not sign any agreement in time"); + }); + + it("should finalize the resource rental during execution", async () => { + expect.assertions(1); + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 1 }); + const resourceRental = await pool.acquire(); + const exe = await resourceRental.getExeUnit(); + return new Promise(async (res) => { + resourceRental.events.on("finalized", async () => res(true)); + setTimeout(() => resourceRental.stopAndFinalize(), 8_000); + await expect(exe.run("sleep 10 && echo Hello World")).rejects.toThrow( + new GolemAbortError("Execution of script has been aborted"), + ); + }); + }); + + it("should abort getting the newly created exe-unit by timeout", async () => { + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 1 }); + const rental = await pool.acquire(); + // wait for init and destroy the exe-unit created automatically on startup renatl + await rental.getExeUnit(); + await rental.destroyExeUnit(); + await expect(rental.getExeUnit(10)).rejects.toThrow( + new GolemAbortError("Initializing of the exe-unit has been aborted due to a timeout"), + ); + }); + + it("should abort getting the newly created exe-unit by signal", async () => { + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 1 }); + const abortController = new AbortController(); + const rental = await pool.acquire(); + // wait for init and destroy the exe-unit created automatically on startup renatl + await rental.getExeUnit(); + await rental.destroyExeUnit(); + abortController.abort(); + await expect(rental.getExeUnit(abortController.signal)).rejects.toThrow( + new GolemAbortError("Initializing of the exe-unit has been aborted"), + ); + }); + + it("should abort finalizing resource rental by timeout", async () => { + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 1 }); + const rental = await pool.acquire(); + await rental.getExeUnit(); + await expect(rental.stopAndFinalize(10)).rejects.toThrow( + new GolemAbortError("The finalization of payment process has been aborted due to a timeout"), + ); + }); + + it("should abort finalizing resource rental by signal", async () => { + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 1 }); + const abortController = new AbortController(); + const rental = await pool.acquire(); + await rental.getExeUnit(); + abortController.abort(); + await expect(rental.stopAndFinalize(abortController.signal)).rejects.toThrow( + new GolemAbortError("The finalization of payment process has been aborted"), + ); + }); + + it("should abort creating a resource rental when drained", async () => { + const pool = glm.rental.createResourceRentalPool(proposalPool, allocation, { poolSize: 1 }); + const acquirePromise = pool.acquire(); + await pool.drainAndClear(); + await expect(acquirePromise).rejects.toThrow("The signing of the agreement has been aborted"); + expect(pool.getSize()).toEqual(0); + }); +}); diff --git a/tests/e2e/ssh.spec.ts b/tests/e2e/ssh.spec.ts deleted file mode 100644 index 5a9055a89..000000000 --- a/tests/e2e/ssh.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { LoggerMock } from "../mock"; -import crypto from "crypto"; -import { TaskExecutor } from "../../src"; -import { spawn } from "child_process"; -const logger = new LoggerMock(false); - -describe("SSH connection", function () { - let executor: TaskExecutor; - it("should connect to provider via ssh", async () => { - executor = await TaskExecutor.create({ - package: "golem/examples-ssh:latest", - capabilities: ["vpn"], - networkIp: "192.168.0.0/24", - logger, - }); - let websocketUri; - const password = crypto.randomBytes(3).toString("hex"); - let stdout = ""; - let processSsh; - await executor.run(async (ctx) => { - websocketUri = ctx.getWebsocketUri(22); - const results = await ctx - .beginBatch() - .run("syslogd") - .run("ssh-keygen -A") - .run(`echo -e "${password}\n${password}" | passwd`) - .run("/usr/sbin/sshd") - .end() - .catch((error) => console.error(error)); - expect(results?.[3]?.result).toEqual("Ok"); - expect(websocketUri).toEqual(expect.any(String)); - processSsh = spawn( - `sshpass -p ${password} ssh`, - [ - "-o", - "UserKnownHostsFile=/dev/null", - "-o", - "StrictHostKeyChecking=no", - "-o", - `ProxyCommand='websocat asyncstdio: ${websocketUri} --binary -H=Authorization:"Bearer ${process.env.YAGNA_APPKEY}"'`, - `root@${crypto.randomBytes(10).toString("hex")}`, - "uname -v", - ], - { shell: true }, - ); - processSsh.stdout.on("data", (data) => (stdout += data.toString())); - }); - await new Promise((res) => setTimeout(res, 3000)); - expect(stdout).toContain("1-Alpine SMP"); - processSsh.kill(); - await executor.shutdown(); - }); -}); diff --git a/tests/e2e/strategies.spec.ts b/tests/e2e/strategies.spec.ts deleted file mode 100644 index 3efe89c34..000000000 --- a/tests/e2e/strategies.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { ProposalFilterFactory, TaskExecutor } from "../../src"; -import { LoggerMock } from "../mock"; - -const logger = new LoggerMock(false); - -describe("Strategies", function () { - beforeEach(function () { - logger.clear(); - }); - describe("Proposals", () => { - it("should filtered providers by black list names", async () => { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - proposalFilter: ProposalFilterFactory.disallowProvidersByNameRegex(/provider-2/), - logger, - }); - const data = ["one", "two", "three"]; - const futureResults = data.map((x) => - executor.run(async (ctx) => { - const res = await ctx.run(`echo "${x}"`); - return res.stdout?.toString().trim(); - }), - ); - const finalOutputs = (await Promise.all(futureResults)).filter((x) => !!x); - expect(finalOutputs).toEqual(expect.arrayContaining(data)); - await logger.expectToMatch(/Proposal rejected by Proposal Filter/, 5000); - await logger.expectToInclude( - `Task computed`, - { providerName: "provider-1", taskId: "1", retries: expect.anything() }, - 5000, - ); - await logger.expectToInclude( - `Task computed`, - { providerName: "provider-1", taskId: "2", retries: expect.anything() }, - 5000, - ); - await logger.expectToInclude( - `Task computed`, - { providerName: "provider-1", taskId: "3", retries: expect.anything() }, - 5000, - ); - await executor.shutdown(); - }); - - it("should filtered providers by white list names", async () => { - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - proposalFilter: ProposalFilterFactory.allowProvidersByNameRegex(/provider-2/), - logger, - }); - const data = ["one", "two", "three"]; - const futureResults = data.map((x) => - executor.run(async (ctx) => { - const res = await ctx.run(`echo "${x}"`); - return res.stdout?.toString().trim(); - }), - ); - const finalOutputs = (await Promise.all(futureResults)).filter((x) => !!x); - expect(finalOutputs).toEqual(expect.arrayContaining(data)); - await logger.expectToMatch(/Proposal rejected by Proposal Filter/, 5000); - await logger.expectToInclude( - `Task computed`, - { providerName: `provider-2`, taskId: "1", retries: expect.anything() }, - 5000, - ); - await logger.expectToInclude( - `Task computed`, - { providerName: `provider-2`, taskId: "2", retries: expect.anything() }, - 5000, - ); - await logger.expectToInclude( - `Task computed`, - { providerName: `provider-2`, taskId: "3", retries: expect.anything() }, - 5000, - ); - await executor.shutdown(); - }); - }); -}); diff --git a/tests/e2e/tasks.spec.ts b/tests/e2e/tasks.spec.ts deleted file mode 100644 index 4137cbb15..000000000 --- a/tests/e2e/tasks.spec.ts +++ /dev/null @@ -1,323 +0,0 @@ -import { LoggerMock } from "../mock"; -import { readFileSync } from "fs"; -import { TaskExecutor, EVENT_TYPE, BaseEvent, Events, Result } from "../../src"; -const logger = new LoggerMock(false); - -describe("Task Executor", function () { - let executor: TaskExecutor; - beforeEach(function () { - logger.clear(); - }); - - afterEach(async function () { - logger.clear(); - await executor?.shutdown(); - }); - - it("should run simple task", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - }); - const result = await executor.run(async (ctx) => ctx.run("echo 'Hello World'")); - - expect(result?.stdout).toContain("Hello World"); - expect(logger.logs).toContain("Demand published on the market"); - expect(logger.logs).toContain("New proposal has been received"); - expect(logger.logs).toContain("Proposal has been responded"); - expect(logger.logs).toContain("New proposal added to pool"); - expect(logger.logs).toMatch(/Agreement confirmed by provider/); - expect(logger.logs).toMatch(/Activity created/); - }); - - it("should run simple task and get error for invalid command", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - }); - const result1 = await executor.run(async (ctx) => ctx.run("echo 'Hello World'")); - const result2 = await executor.run(async (ctx) => ctx.run("invalid-command")); - - expect(result1?.stdout).toContain("Hello World"); - expect(result2?.result).toEqual("Error"); - expect(result2?.stderr).toContain("sh: invalid-command: not found"); - expect(result2?.message).toEqual("ExeScript command exited with code 127"); - }); - - it("should run simple task using package tag", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - }); - const result = await executor.run(async (ctx) => ctx.run("echo 'Hello World'")); - - expect(result?.stdout).toContain("Hello World"); - expect(logger.logs).toContain("Demand published on the market"); - expect(logger.logs).toContain("New proposal has been received"); - expect(logger.logs).toContain("Proposal has been responded"); - expect(logger.logs).toContain("New proposal added to pool"); - expect(logger.logs).toMatch(/Agreement confirmed by provider/); - expect(logger.logs).toMatch(/Activity created/); - }); - - it("should run simple tasks by map function", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - }); - const data = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]; - const futureResults = data.map((x) => - executor.run(async (ctx) => { - const res = await ctx.run(`echo "${x}"`); - return res.stdout?.toString().trim(); - }), - ); - const finalOutputs = (await Promise.all(futureResults)).filter((x) => !!x); - expect(finalOutputs).toEqual(expect.arrayContaining(data)); - }); - - it("should run simple batch script and get results as stream", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - }); - const outputs: string[] = []; - let onEnd = ""; - await executor - .run(async (ctx) => { - const results = await ctx - .beginBatch() - .run('echo "Hello Golem"') - .run('echo "Hello World"') - .run('echo "OK"') - .endStream(); - results.on("data", ({ stdout }) => outputs.push(stdout.toString().trim())); - results.on("close", () => (onEnd = "END")); - }) - .catch((e) => { - executor.shutdown(); - expect(e).toBeUndefined(); - }); - await logger.expectToInclude( - "Task computed", - { taskId: "1", providerName: expect.anything(), retries: expect.anything() }, - 5000, - ); - expect(outputs[0]).toEqual("Hello Golem"); - expect(outputs[1]).toEqual("Hello World"); - expect(outputs[2]).toEqual("OK"); - expect(onEnd).toEqual("END"); - }); - - it("should run simple batch script and get results as promise", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - }); - const outputs: string[] = []; - await executor - .run(async (ctx) => { - const results = await ctx - .beginBatch() - .run('echo "Hello Golem"') - .run('echo "Hello World"') - .run('echo "OK"') - .end(); - results.map((r) => outputs.push(r?.stdout?.toString().trim() ?? "Missing STDOUT!")); - }) - .catch((e) => { - expect(e).toBeUndefined(); - }); - expect(outputs[0]).toEqual("Hello Golem"); - expect(outputs[1]).toEqual("Hello World"); - expect(outputs[2]).toEqual("OK"); - }); - - it("should run transfer file", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - }); - const result = await executor.run(async (ctx) => { - await ctx.uploadJson({ test: "1234" }, "/golem/work/test.json"); - const res = await ctx.downloadFile("/golem/work/test.json", "new_test.json"); - return res?.result; - }); - expect(result).toEqual("Ok"); - expect(readFileSync(`${process.env.GOTH_GFTP_VOLUME || ""}new_test.json`, "utf-8")).toEqual('{"test":"1234"}'); - }); - - it("should run transfer file via http", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - }); - const result = await executor.run(async (ctx) => { - const res = await ctx.transfer( - "http://registry.golem.network/download/a2bb9119476179fac36149723c3ad4474d8d135e8d2d2308eb79907a6fc74dfa", - "/golem/work/alpine.gvmi", - ); - return res.result; - }); - expect(result).toEqual("Ok"); - }); - - it("should get ip address", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - capabilities: ["vpn"], - networkIp: "192.168.0.0/24", - logger, - }); - const result = await executor.run(async (ctx) => ctx.getIp()); - expect(["192.168.0.2", "192.168.0.3"]).toContain(result); - }); - - it("should spawn command as external process", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - }); - let stdout = ""; - let stderr = ""; - const finalResult = await executor.run(async (ctx) => { - const remoteProcess = await ctx.runAndStream("sleep 1 && echo 'Hello World' && echo 'Hello Golem' >&2"); - remoteProcess.stdout.on("data", (data) => (stdout += data.trim())); - remoteProcess.stderr.on("data", (data) => (stderr += data.trim())); - return remoteProcess.waitForExit(); - }); - expect(stdout).toContain("Hello World"); - expect(stderr).toContain("Hello Golem"); - expect(finalResult?.result).toContain("Ok"); - expect(logger.logs).toContain("Demand published on the market"); - expect(logger.logs).toContain("New proposal has been received"); - expect(logger.logs).toContain("Proposal has been responded"); - expect(logger.logs).toContain("New proposal added to pool"); - expect(logger.logs).toMatch(/Agreement confirmed by provider/); - expect(logger.logs).toMatch(/Activity created/); - }); - - it("should spawn commands as external processes and handle timeout errors", async () => { - executor = await TaskExecutor.create("golem/alpine:latest"); - const stdouts: string[] = []; - const errors: Error[] = []; - const results: Result[] = []; - for (const i of [1, 2, 3, 4]) { - await executor.run(async (ctx) => { - const remoteProcess = await ctx.runAndStream("sleep 1 && echo 'Hello World'"); - remoteProcess.stdout.on("data", (data) => (stdouts[i] = data)); - const timeout = i % 2 === 0 ? 50 : 20_000; - results[i] = await remoteProcess.waitForExit(timeout).catch((e) => (errors[i] = e)); - - if (i % 2 === 0) { - expect(stdouts[i]).not.toBeDefined(); - expect(results[i]?.result).not.toBeDefined(); - expect(errors[i]?.message).toContain( - "Unable to get activity results. The waiting time (50 ms) for the final result has been exceeded", - ); - } else { - expect(stdouts[i]).toContain("Hello World"); - expect(results[i]?.result).toContain("Ok"); - expect(errors[i]).not.toBeDefined(); - } - }); - } - }); - - it("should not retry the task if maxTaskRetries is zero", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - maxTaskRetries: 0, - }); - try { - executor.onActivityReady(async (ctx) => Promise.reject("Error")); - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (error) { - await executor.shutdown(); - } - expect(logger.logs).not.toContain("Trying to redo the task"); - }); - - it("should not retry the task if taskRetries is zero", async () => { - executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - logger, - maxTaskRetries: 7, - }); - try { - executor.onActivityReady(async (ctx) => Promise.reject("Error")); - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout), { maxRetries: 0 }); - } catch (error) { - await executor.shutdown(); - } - expect(logger.logs).not.toContain("Trying to redo the task"); - }); - - it("should clean up the agreements in the pool if the agreement has been terminated by provider", async () => { - const eventTarget = new EventTarget(); - const executor = await TaskExecutor.create({ - package: "golem/alpine:latest", - eventTarget, - // we set mid-agreement payment and a filter that will not pay for debit notes - // which should result in termination of the agreement by provider - debitNotesFilter: () => Promise.resolve(false), - debitNotesAcceptanceTimeoutSec: 10, - midAgreementPaymentTimeoutSec: 10, - midAgreementDebitNoteIntervalSec: 10, - }); - let createdAgreementsCount = 0; - eventTarget.addEventListener(EVENT_TYPE, (event) => { - const ev = event as BaseEvent; - if (ev instanceof Events.AgreementCreated) createdAgreementsCount++; - }); - try { - await executor.run(async (ctx) => { - const proc = await ctx.runAndStream("timeout 15 ping 127.0.0.1"); - proc.stdout.on("data", (data) => console.log(data)); - return await proc.waitForExit(20_000); - }); - // the first task should be terminated by the provider, the second one should not use the same agreement - await executor.run(async (ctx) => console.log((await ctx.run("echo 'Hello World'")).stdout)); - } catch (error) { - throw new Error(`Test failed. ${error}`); - } finally { - await executor.shutdown(); - } - expect(createdAgreementsCount).toBeGreaterThan(1); - }); - - it("should only accept debit notes for agreements that were created by the executor", async () => { - const eventTarget1 = new EventTarget(); - const eventTarget2 = new EventTarget(); - const executor1 = await TaskExecutor.create("golem/alpine:latest"); - const executor2 = await TaskExecutor.create("golem/alpine:latest"); - const createdAgreementsIds1 = new Set(); - const createdAgreementsIds2 = new Set(); - const acceptedDebitNoteAgreementIds1 = new Set(); - const acceptedDebitNoteAgreementIds2 = new Set(); - eventTarget1.addEventListener(EVENT_TYPE, (event) => { - const ev = event as BaseEvent; - if (ev instanceof Events.AgreementCreated) createdAgreementsIds1.add(ev.detail.id); - if (ev instanceof Events.DebitNoteAccepted) acceptedDebitNoteAgreementIds1.add(ev.detail.agreementId); - }); - eventTarget2.addEventListener(EVENT_TYPE, (event) => { - const ev = event as BaseEvent; - if (ev instanceof Events.AgreementCreated) createdAgreementsIds2.add(ev.detail.id); - if (ev instanceof Events.DebitNoteAccepted) acceptedDebitNoteAgreementIds2.add(ev.detail.agreementId); - }); - try { - await Promise.all([ - executor1.run(async (ctx) => console.log((await ctx.run("echo 'Executor 1'")).stdout)), - executor2.run(async (ctx) => console.log((await ctx.run("echo 'Executor 2'")).stdout)), - ]); - } catch (error) { - throw new Error(`Test failed. ${error}`); - } finally { - await executor1.shutdown(); - await executor2.shutdown(); - } - expect(acceptedDebitNoteAgreementIds1).toEqual(createdAgreementsIds1); - expect(acceptedDebitNoteAgreementIds2).toEqual(createdAgreementsIds2); - }); -}); diff --git a/tests/e2e/yacat.spec.ts b/tests/e2e/yacat.spec.ts deleted file mode 100644 index f335f6684..000000000 --- a/tests/e2e/yacat.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { TaskExecutor } from "../../src"; -import { LoggerMock } from "../mock"; -const logger = new LoggerMock(false); - -const range = (start: number, end: number, step = 1): number[] => { - const list: number[] = []; - for (let index = start; index < end; index += step) list.push(index); - return list; -}; - -describe("Password cracking", function () { - let executor: TaskExecutor; - it( - "should crack password", - async () => { - const mask = "?a?a"; - const hash = "$P$5ZDzPE45CigTC6EY4cXbyJSLj/pGee0"; - executor = await TaskExecutor.create({ - /** - * Using the latest yacat image tag `golem/examples-hashcat:latest` - * causes problems with providers in Goth: - * Error: `Device #1: Not enough allocatable device memory for this attack`, - * So for now we leave the old version with image hash for Goth test - */ - package: "055911c811e56da4d75ffc928361a78ed13077933ffa8320fb1ec2db", - budget: 10, - logger, - }); - const keyspace = await executor.run(async (ctx) => { - const result = await ctx.run(`hashcat --keyspace -a 3 ${mask} -m 400`); - return parseInt(result.stdout?.toString() || ""); - }); - expect(keyspace).toEqual(95); - if (!keyspace) return; - const step = Math.floor(keyspace / 3); - const ranges = range(0, keyspace, step); - - const findPasswordInRange = async (skip: number) => { - const password = await executor.run(async (ctx) => { - const [, potfileResult] = await ctx - .beginBatch() - .run( - `hashcat -a 3 -m 400 '${hash}' '${mask}' --skip=${skip} --limit=${skip + step} -o pass.potfile || true`, - ) - .run("cat pass.potfile || true") - .end(); - if (!potfileResult.stdout) return false; - // potfile format is: hash:password - return potfileResult.stdout.toString().trim().split(":")[1]; - }); - if (!password) { - throw new Error(`Cannot find password in range ${skip} - ${skip + step}`); - } - return password; - }; - - await expect(Promise.any(ranges.map(findPasswordInRange))).resolves.toEqual("yo"); - await executor.shutdown(); - }, - 1000 * 60 * 5, - ); -}); diff --git a/tests/examples/examples.json b/tests/examples/examples.json index 896c1f42d..8d336155e 100644 --- a/tests/examples/examples.json +++ b/tests/examples/examples.json @@ -1,68 +1,20 @@ [ - { "cmd": "node", "path": "examples/docs-examples/quickstarts/retrievable-task/task.mjs", "noGoth": true }, - { "cmd": "node", "path": "examples/docs-examples/examples/composing-tasks/batch-end.mjs", "noGoth": true }, - { - "cmd": "node", - "path": "examples/docs-examples/examples/composing-tasks/batch-endstream-chunks.mjs", - "noGoth": true - }, - { - "cmd": "node", - "path": "examples/docs-examples/examples/composing-tasks/batch-endstream-forawait.mjs", - "noGoth": true - }, - { "cmd": "node", "path": "examples/docs-examples/examples/composing-tasks/multiple-run-prosaic.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/composing-tasks/single-command.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/composing-tasks/single-command.cjs" }, - { "cmd": "ts-node", "path": "examples/docs-examples/examples/composing-tasks/single-command.ts" }, - { "cmd": "ts-node", "path": "examples/docs-examples/examples/composing-tasks/alert-code.mjs" }, - - { "cmd": "node", "path": "examples/docs-examples/examples/executing-tasks/on-activity-ready.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/executing-tasks/foreach.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/executing-tasks/map.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/executing-tasks/max-parallel-tasks.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/executing-tasks/single-run.mjs" }, - - { "cmd": "node", "path": "examples/docs-examples/examples/selecting-providers/custom-price.mjs", "noGoth": true }, - { "cmd": "node", "path": "examples/docs-examples/examples/selecting-providers/demand.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/selecting-providers/whitelist.mjs" }, - - { "cmd": "node", "path": "examples/docs-examples/examples/sending-data/downloading-file.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/sending-data/uploading-file.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/sending-data/uploading-json.mjs" }, - - { "cmd": "node", "path": "examples/docs-examples/examples/transferring-data/download-file.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/transferring-data/upload-file.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/transferring-data/upload-json.mjs" }, - - { "cmd": "node", "path": "examples/docs-examples/examples/using-app-keys/index.mjs" }, - - { "cmd": "node", "path": "examples/docs-examples/examples/working-with-images/hash.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/working-with-images/tag.mjs" }, - - { "cmd": "node", "path": "examples/docs-examples/examples/working-with-results/multi-command-end.mjs" }, - { - "cmd": "node", - "path": "examples/docs-examples/examples/working-with-results/multi-command-endstream.mjs", - "noGoth": true - }, - { - "cmd": "node", - "path": "examples/docs-examples/examples/working-with-results/multi-command-fail.mjs", - "skip": true - }, - { "cmd": "node", "path": "examples/docs-examples/examples/working-with-results/single-command.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/examples/working-with-results/single-command-fail.mjs" }, - - { "cmd": "node", "path": "examples/docs-examples/quickstarts/quickstart/requestor.mjs" }, - - { "cmd": "node", "path": "examples/docs-examples/tutorials/building-custom-image/index.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/tutorials/accessing-internet/outbound-ipfs.mjs" }, - { "cmd": "node", "path": "examples/docs-examples/tutorials/quickstart/index.mjs" }, - { - "cmd": "node", - "path": "examples/docs-examples/tutorials/running-parallel-tasks/index.mjs", - "args": ["--mask", "?a?a", "--hash", "$P$5ZDzPE45CigTC6EY4cXbyJSLj/pGee0"], - "timeout": 1000 - } + { "cmd": "tsx", "path": "examples/basic/many-of.ts" }, + { "cmd": "tsx", "path": "examples/basic/one-of.ts" }, + { "cmd": "tsx", "path": "examples/basic/vpn.ts" }, + { "cmd": "tsx", "path": "examples/basic/transfer.ts" }, + { "cmd": "tsx", "path": "examples/basic/run-and-stream.ts" }, + { "cmd": "tsx", "path": "examples/advanced/step-by-step.ts" }, + { "cmd": "tsx", "path": "examples/advanced/manual-pools.ts" }, + { "cmd": "tsx", "path": "examples/advanced/payment-filters.ts" }, + { "cmd": "tsx", "path": "examples/advanced/proposal-filter.ts" }, + { "cmd": "tsx", "path": "examples/advanced/proposal-predefined-filter.ts" }, + { "cmd": "tsx", "path": "examples/advanced/override-module.ts" }, + { "cmd": "tsx", "path": "examples/advanced/scan.ts" }, + { "cmd": "tsx", "path": "examples/advanced/reuse-allocation.ts" }, + { "cmd": "tsx", "path": "examples/advanced/setup-and-teardown.ts" }, + { "cmd": "tsx", "path": "examples/experimental/deployment/new-api.ts" }, + { "cmd": "tsx", "path": "examples/experimental/job/getJobById.ts" }, + { "cmd": "tsx", "path": "examples/experimental/job/waitForResults.ts" }, + { "cmd": "tsx", "path": "examples/experimental/job/cancel.ts" } ] diff --git a/tests/examples/examples.test.ts b/tests/examples/examples.test.ts index 9dfbe6cdc..b49898c74 100644 --- a/tests/examples/examples.test.ts +++ b/tests/examples/examples.test.ts @@ -3,7 +3,13 @@ import { dirname, basename, resolve } from "path"; import chalk from "chalk"; import testExamples from "./examples.json"; -const criticalLogsRegExp = [/Task *. timeout/, /Task *. has been rejected/, /ERROR: TypeError/, /ERROR: Error/gim]; +const criticalLogsRegExp = [ + /GolemInternalError/, + /GolemPlatformError/, + /GolemWorkError/, + /ERROR: TypeError/, + /ERROR: Error/gim, +]; type Example = { cmd: string; @@ -18,7 +24,8 @@ const exitOnError = process.argv.includes("--exitOnError"); async function test(cmd: string, path: string, args: string[] = [], timeout = 360) { const file = basename(path); const cwd = dirname(path); - const spawnedExample = spawn(cmd, [file, ...args], { cwd }); + const env = { ...process.env, DEBUG: "golem-js:*" }; + const spawnedExample = spawn(cmd, [file, ...args], { cwd, env }); spawnedExample.stdout?.setEncoding("utf-8"); spawnedExample.stderr?.setEncoding("utf-8"); let error = ""; @@ -37,10 +44,6 @@ async function test(cmd: string, path: string, args: string[] = [], timeout = 36 error = `A critical error occurred during the test. ${logWithoutColors}`; spawnedExample.kill(); } - // for some reason, sometimes the process doesn't exit after Executor shut down - if (logWithoutColors.indexOf("Task Executor has shut down") !== -1) { - spawnedExample.kill("SIGKILL"); - } }; spawnedExample.stdout?.on("data", assertLogs); spawnedExample.stderr?.on("data", assertLogs); diff --git a/tests/fixtures/alpine.gvmi b/tests/fixtures/alpine.gvmi new file mode 100644 index 000000000..7c80f210c Binary files /dev/null and b/tests/fixtures/alpine.gvmi differ diff --git a/examples/blender/cubes.blend b/tests/fixtures/cubes.blend similarity index 100% rename from examples/blender/cubes.blend rename to tests/fixtures/cubes.blend diff --git a/tests/import/import.test.mjs b/tests/import/import.test.mjs new file mode 100644 index 000000000..c47d7f02b --- /dev/null +++ b/tests/import/import.test.mjs @@ -0,0 +1,11 @@ +describe("ESM Import", () => { + test("Import @golem-sdk/golem-js", async () => { + const { YagnaApi } = await import("@golem-sdk/golem-js"); + expect(typeof YagnaApi).toBe("function"); + }); + + test("Import @golem-sdk/golem-js/experimental", async () => { + const { Job } = await import("@golem-sdk/golem-js/experimental"); + expect(typeof Job).toBe("function"); + }); +}); diff --git a/tests/import/jest.config.js b/tests/import/jest.config.js new file mode 100644 index 000000000..ad23c836d --- /dev/null +++ b/tests/import/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + verbose: true, + testEnvironment: "node", + // disable transforming source files because we want to execute the + // es modules directly + transform: {}, + moduleFileExtensions: ["js", "cjs", "mjs"], + testMatch: ["**/?(*.)+(spec|test).mjs", "**/?(*.)+(spec|test).cjs"], +}; diff --git a/tests/import/require.test.cjs b/tests/import/require.test.cjs new file mode 100644 index 000000000..cced7f034 --- /dev/null +++ b/tests/import/require.test.cjs @@ -0,0 +1,11 @@ +describe("CommonJS Import", () => { + test("Require @golem-sdk/golem-js", () => { + const { YagnaApi } = require("@golem-sdk/golem-js"); + expect(typeof YagnaApi).toBe("function"); + }); + + test("Require @golem-sdk/golem-js/experimental", async () => { + const { Job } = require("@golem-sdk/golem-js/experimental"); + expect(typeof Job).toBe("function"); + }); +}); diff --git a/tests/unit/jest.config.json b/tests/jest.config.json similarity index 82% rename from tests/unit/jest.config.json rename to tests/jest.config.json index 8df405883..702e07f36 100644 --- a/tests/unit/jest.config.json +++ b/tests/jest.config.json @@ -1,9 +1,9 @@ { "preset": "ts-jest", "testEnvironment": "node", - "roots": ["../../src", "./"], + "roots": ["../src"], "testMatch": ["**/*.test.ts", "**/*.spec.ts"], - "setupFilesAfterEnv": ["../jest.setup.ts"], + "setupFilesAfterEnv": ["./jest.setup.ts"], "reporters": [ "default", [ diff --git a/tests/mock/activity.mock.ts b/tests/mock/activity.mock.ts deleted file mode 100644 index aeceae178..000000000 --- a/tests/mock/activity.mock.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Activity, ActivityConfig, ActivityStateEnum, Result, ResultState } from "../../src/activity"; -import { Events, nullLogger } from "../../src"; -import { ExeScriptRequest } from "../../src/activity/activity"; -import { Readable } from "stream"; -import { YagnaApi } from "../../src/utils"; -import { Agreement } from "../../src/agreement"; - -export class ActivityMock extends Activity { - private _currentState: ActivityStateEnum = ActivityStateEnum.Ready; - - private results: (Result | Error)[] = []; - - static createResult(props?: Partial): Result { - return new Result({ - result: ResultState.Ok, - index: 1, - eventDate: new Date().toISOString(), - ...props, - }); - } - - constructor(id: string, agreement: Agreement, yagnaApi: YagnaApi, options?: ActivityConfig) { - super(id, agreement, yagnaApi, (options ?? { logger: nullLogger() }) as unknown as ActivityConfig); - } - - async execute(script: ExeScriptRequest, stream?: boolean, timeout?: number): Promise { - // TODO: add more events if needed. - const eventTarget = this.options?.eventTarget; - - const readable = new Readable({ - objectMode: true, - read: () => { - const result = this.results.shift(); - if (result instanceof Error) { - readable.emit("error", result); - } else if (result) { - readable.push(result); - } else { - eventTarget?.dispatchEvent( - new Events.ScriptExecuted({ - activityId: this.id, - agreementId: this.agreement.id, - success: true, - }), - ); - readable.push(null); - } - }, - }); - - return readable; - } - - async stop(): Promise { - return true; - } - - async getState(): Promise { - return this._currentState; - } - - mockCurrentState(state: ActivityStateEnum) { - this._currentState = state; - } - - mockResults(results: Result[]) { - this.results = results; - } - - /** - * Create a new execution result and add it to the list of results. - * @param props - */ - mockResultCreate(props: Partial = {}): Result { - const result = ActivityMock.createResult(props); - this.results.push(result); - return result; - } - - /** - * Create a failure event, once execute will reach it, an exception will be thrown. - * - * This can be used to simulate various failures modes. - */ - mockResultFailure(messageOrError: string | Error): void { - this.results.push(typeof messageOrError === "string" ? new Error(messageOrError) : messageOrError); - } -} diff --git a/tests/mock/entities/agreement.ts b/tests/mock/entities/agreement.ts deleted file mode 100644 index 3d4bd0475..000000000 --- a/tests/mock/entities/agreement.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Agreement } from "../../../src/agreement"; -import { AgreementStateEnum } from "ya-ts-client/dist/ya-market/src/models"; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -export const agreement: Agreement = { - id: "test_agreement_id", - getProviderInfo() { - return { id: "test_provider_id", name: "Test Provider", walletAddress: "test_wallet_address" }; - }, - async confirm(): Promise { - return Promise.resolve(undefined); - }, - async getState(): Promise { - return Promise.resolve(AgreementStateEnum.Approved); - }, - async isFinalState(): Promise { - return Promise.resolve(true); - }, - async refreshDetails(): Promise { - return Promise.resolve(undefined); - }, - async terminate(reason?: { [p: string]: string }): Promise { - return Promise.resolve(undefined); - }, -}; diff --git a/tests/mock/entities/allocation.ts b/tests/mock/entities/allocation.ts deleted file mode 100644 index 99d533d91..000000000 --- a/tests/mock/entities/allocation.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Allocation } from "../../../src/payment/allocation"; -import { MarketDecoration } from "ya-ts-client/dist/ya-payment"; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -export const allocationMock: Allocation = { - timeout: "", - paymentPlatform: "erc20-holesky-tglm", - address: "", - id: "test_id", - timestamp: "", - totalAmount: "", - async getDemandDecoration(): Promise { - return Promise.resolve({ - properties: [ - { - key: "golem.com.payment.platform.erc20-holesky-tglm.address", - value: "0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119", - }, - ], - constraints: ["(golem.com.payment.platform.erc20-holesky-tglm.address=*)"], - }); - }, - async release(): Promise { - return Promise.resolve(undefined); - }, -}; diff --git a/tests/mock/entities/package.ts b/tests/mock/entities/package.ts deleted file mode 100644 index 3ac95071c..000000000 --- a/tests/mock/entities/package.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Package } from "../../../src/package"; -import { MarketDecoration } from "ya-ts-client/dist/ya-payment"; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -export const packageMock: Package = { - async getDemandDecoration(): Promise { - return Promise.resolve({ - constraints: ["(golem.inf.mem.gib>=0.5)", "(golem.inf.storage.gib>=2)", "(golem.runtime.name=vm)"], - properties: [ - { - key: "golem.srv.comp.task_package", - value: - "hash:sha3:9a3b5d67b0b27746283cb5f287c13eab1beaa12d92a9f536b747c7ae:http://girepo.dev.golem.network:8000/local-image-c76719083b.gvmi\n", - }, - { - key: "golem.srv.comp.vm.package_format", - value: "gvmkit-squash", - }, - ], - }); - }, -}; diff --git a/tests/mock/entities/storage_provider.ts b/tests/mock/entities/storage_provider.ts deleted file mode 100644 index 2d62b433c..000000000 --- a/tests/mock/entities/storage_provider.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { StorageProvider } from "../../../src/storage"; -import { Logger, nullLogger } from "../../../src/utils"; -import { StorageProviderDataCallback } from "../../../src/storage/provider"; - -export class StorageProviderMock implements StorageProvider { - private logger: Logger; - - constructor(options?: { logger?: Logger }) { - this.logger = options?.logger || nullLogger(); - } - - receiveFile(path: string): Promise { - this.logger.debug(`File received`, { path }); - return Promise.resolve(""); - } - - receiveData(callback: StorageProviderDataCallback): Promise { - this.logger.debug(`Data received`); - return Promise.resolve(""); - } - - close(): Promise { - this.logger.debug(`Storage provider closed`); - return Promise.resolve(undefined); - } - - init(): Promise { - this.logger.debug(`Storage provider started`); - return Promise.resolve(undefined); - } - - publishFile(src: string): Promise { - this.logger.debug(`File published`, { src }); - return Promise.resolve(""); - } - - publishData(data: Uint8Array): Promise { - this.logger.debug(`Data published`, { data }); - return Promise.resolve(""); - } - - release(urls: string[]): Promise { - this.logger.debug(`Urls released`, { urls }); - return Promise.resolve(); - } -} - -export const storageProviderMock = new StorageProviderMock(); diff --git a/tests/mock/entities/task.ts b/tests/mock/entities/task.ts deleted file mode 100644 index 218062e05..000000000 --- a/tests/mock/entities/task.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { QueueableTask } from "../../../src/task"; - -export enum TaskState { - New, - Retry, - Pending, - Done, -} - -export default class TaskMock implements QueueableTask { - constructor( - private results, - private state: TaskState, - ) {} - - public isQueueable() { - return this.state == TaskState.Retry || this.state == TaskState.New; - } - - public getResults() { - return this.results; - } - - public isRetry() { - return this.state == TaskState.Retry; - } -} diff --git a/tests/mock/fixtures/agreements.ts b/tests/mock/fixtures/agreements.ts deleted file mode 100644 index 783ce65ef..000000000 --- a/tests/mock/fixtures/agreements.ts +++ /dev/null @@ -1,134 +0,0 @@ -export const agreementsApproved = [ - { - agreementId: "b9ef6f5feb7fa9e5eb226a5ca2b658823242451fa28b08df9e063943018cc883", - demand: { - properties: { - "golem.com.payment.chosen-platform": "erc20-rinkeby-tglm", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119", - "golem.node.debug.subnet": "devnet-beta", - "golem.srv.caps.multi-activity": true, - "golem.srv.comp.expiration": 1669632825572, - "golem.srv.comp.task_package": - "hash:sha3:9a3b5d67b0b27746283cb5f287c13eab1beaa12d92a9f536b747c7ae:http://girepo.dev.golem.network:8000/local-image-c76719083b.gvmi\n", - "golem.srv.comp.vm.package_format": "gvmkit-squash", - }, - constraints: - "(&(&(golem.inf.mem.gib>=0.5)\n\t(golem.inf.storage.gib>=2)\n\t(golem.runtime.name=vm))\n\t(golem.com.payment.platform.erc20-rinkeby-tglm.address=*)\n\t(golem.node.debug.subnet=devnet-beta))", - demandId: "f8a7f2ac2e034123b59fdd45993ccb0e-7b25faa576ea7696a27775b6058470fedbfe9b4f86fc808de4a12ddffdef4811", - requestorId: "0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119", - timestamp: "2022-11-28T10:38:57.369763Z", - }, - offer: { - properties: { - "golem.activity.caps.transfer.protocol": ["gftp", "http", "https"], - "golem.com.payment.platform.erc20-holesky-tglm.address": "0x6e7f25ca8ab5a043d91d102116cc97c2654403f0", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0x6e7f25ca8ab5a043d91d102116cc97c2654403f0", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0x6e7f25ca8ab5a043d91d102116cc97c2654403f0", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0x6e7f25ca8ab5a043d91d102116cc97c2654403f0", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.0001, 0.00005, 0], - "golem.com.scheme": "payu", - "golem.com.usage.vector": ["golem.usage.cpu_sec", "golem.usage.duration_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "smx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "hle", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "rtm", - "deprecate_fpu_cs_ds", - "mpx", - "rdseed", - "adx", - "smap", - "clflushopt", - "processor_trace", - "sgx", - ], - "golem.inf.cpu.cores": 4, - "golem.inf.cpu.model": "Stepping 9 Family 6 Model 302", - "golem.inf.cpu.threads": 1, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 4, - "golem.inf.storage.gib": 20, - "golem.node.debug.subnet": "devnet-beta", - "golem.node.id.name": "azathoth-rnd", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994555408)\n (golem.node.debug.subnet=devnet-beta)\n)", - offerId: "7d322a0eb074438b86c80973ac7d0cbf-e03ec4bc0f723f5332c1ddf7ebcc4b689bcf772c346ab608e75611320283993f", - providerId: "0x6e7f25ca8ab5a043d91d102116cc97c2654403f0", - timestamp: "2022-11-28T10:38:57.369763Z", - }, - validTo: "2022-11-28T11:38:57.357Z", - approvedDate: "2022-11-28T10:38:57.517431370Z", - state: "Approved", - timestamp: "2022-11-28T10:38:57.369763Z", - proposedSignature: "NoSignature", - approvedSignature: "NoSignature", - committedSignature: "NoSignature", - }, -]; diff --git a/tests/mock/fixtures/allocations.ts b/tests/mock/fixtures/allocations.ts deleted file mode 100644 index e02852e35..000000000 --- a/tests/mock/fixtures/allocations.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const allocations = [ - { - allocationId: "70000034-020b-40a0-8adc-404be7440beb", - address: "0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119", - paymentPlatform: "erc20-holesky-tglm", - totalAmount: "1", - spentAmount: "0", - remainingAmount: "1", - timestamp: "2022-11-24T08:49:05.480Z", - timeout: "2022-11-24T09:09:05.415Z", - makeDeposit: false, - }, -]; diff --git a/tests/mock/fixtures/cubes.blend b/tests/mock/fixtures/cubes.blend deleted file mode 100644 index 4a6a4cbe9..000000000 Binary files a/tests/mock/fixtures/cubes.blend and /dev/null differ diff --git a/tests/mock/fixtures/debit_notes.ts b/tests/mock/fixtures/debit_notes.ts deleted file mode 100644 index 5057e405a..000000000 --- a/tests/mock/fixtures/debit_notes.ts +++ /dev/null @@ -1,24 +0,0 @@ -export const debitNotesEvents = [ - { - debitNoteId: "39c20b22-9c49-47cf-b7f9-a4d80f67b915", - eventDate: "2022-12-05T08:54:31.833Z", - eventType: "DebitNoteReceivedEvent", - }, -]; - -export const debitNotes = [ - { - debitNoteId: "39c20b22-9c49-47cf-b7f9-a4d80f67b915", - issuerId: "0xd9a4a6ba9e1800e4f61cd88dc23f082527f4ee28", - recipientId: "0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119", - payeeAddr: "0xd9a4a6ba9e1800e4f61cd88dc23f082527f4ee28", - payerAddr: "0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119", - paymentPlatform: "erc20-holesky-tglm", - timestamp: "2022-12-05T08:54:31.831Z", - agreementId: "test_agreement_id", - activityId: "00f36a80a8544260acbf4b09dc46f4fc", - totalAmountDue: "0.000869307866200000", - usageCounterVector: [0.188735, 17.008687324], - status: "RECEIVED", - }, -]; diff --git a/tests/mock/fixtures/eiffel.blend b/tests/mock/fixtures/eiffel.blend deleted file mode 100644 index 34814e532..000000000 Binary files a/tests/mock/fixtures/eiffel.blend and /dev/null differ diff --git a/tests/mock/fixtures/identity.ts b/tests/mock/fixtures/identity.ts deleted file mode 100644 index f322ce835..000000000 --- a/tests/mock/fixtures/identity.ts +++ /dev/null @@ -1 +0,0 @@ -export const TEST_IDENTITY = "0x19ee20228a4c4bf8d4aebc79d9d3af2a01433456"; diff --git a/tests/mock/fixtures/index.ts b/tests/mock/fixtures/index.ts deleted file mode 100644 index a98f70fc8..000000000 --- a/tests/mock/fixtures/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { agreementsApproved } from "./agreements"; -export { allocations } from "./allocations"; -export { - proposalsInitial, - proposalsDraft, - proposalsWrongPaymentPlatform, - proposalsShortDebitNoteTimeout, -} from "./proposals"; -export { TEST_IDENTITY } from "./identity"; -export { invoiceEvents, invoices } from "./invoices"; -export { debitNotes, debitNotesEvents } from "./debit_notes"; diff --git a/tests/mock/fixtures/invoices.ts b/tests/mock/fixtures/invoices.ts deleted file mode 100644 index 772d490bf..000000000 --- a/tests/mock/fixtures/invoices.ts +++ /dev/null @@ -1,24 +0,0 @@ -export const invoiceEvents = [ - { - invoiceId: "f2f5a229-8324-4211-973a-346e45b3d3e2", - eventDate: "2022-12-05T08:54:31.930Z", - eventType: "InvoiceReceivedEvent", - }, -]; - -export const invoices = [ - { - invoiceId: "f2f5a229-8324-4211-973a-346e45b3d3e2", - issuerId: "0xd9a4a6ba9e1800e4f61cd88dc23f082527f4ee28", - recipientId: "0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119", - payeeAddr: "0xd9a4a6ba9e1800e4f61cd88dc23f082527f4ee28", - payerAddr: "0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119", - paymentPlatform: "erc20-holesky-tglm", - timestamp: "2022-12-05T08:54:31.927Z", - agreementId: "test_agreement_id", - activityIds: ["00f36a80a8544260acbf4b09dc46f4fc"], - amount: "0.000869307866200000", - paymentDueDate: "2022-12-06T08:54:31.941822231Z", - status: "RECEIVED", - }, -]; diff --git a/tests/mock/fixtures/proposals.ts b/tests/mock/fixtures/proposals.ts deleted file mode 100644 index 3469551c6..000000000 --- a/tests/mock/fixtures/proposals.ts +++ /dev/null @@ -1,1455 +0,0 @@ -export const proposalsInitial = [ - // 0 - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:21:05.761Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["http", "https", "gftp"], - "golem.com.payment.debit-notes.accept-timeout?": 240, - "golem.com.payment.platform.erc20-holesky-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.00005, 0.0001, 0], - "golem.com.scheme": "payu", - "golem.com.scheme.payu.debit-note.interval-sec?": 120, - "golem.com.scheme.payu.payment-timeout-sec?": 120, - "golem.com.usage.vector": ["golem.usage.duration_sec", "golem.usage.cpu_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "smx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "hle", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "rtm", - "deprecate_fpu_cs_ds", - "mpx", - "rdseed", - "adx", - "smap", - "clflushopt", - "processor_trace", - "sgx", - "sgx_lc", - ], - "golem.inf.cpu.cores": 6, - "golem.inf.cpu.model": "Stepping 10 Family 6 Model 302", - "golem.inf.cpu.threads": 1, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 4, - "golem.inf.storage.gib": 20, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "golem2004", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994561269)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-20a722b5aa370c2ce25fa2621143541ee17fc8654e54ecdd76dd4e76bb83f476", - issuerId: "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - state: "Initial", - timestamp: "2022-11-24T08:21:05.760367Z", - }, - }, - // 1 - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:22:11.470Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["http", "https", "gftp"], - "golem.com.payment.debit-notes.accept-timeout?": 240, - "golem.com.payment.platform.erc20-holesky-tglm.address": "0x79d38971d4a484d07925b5919aaf7cab807696f4", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0x79d38971d4a484d07925b5919aaf7cab807696f4", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0x79d38971d4a484d07925b5919aaf7cab807696f4", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0x79d38971d4a484d07925b5919aaf7cab807696f4", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.00005, 0.0001, 0], - "golem.com.scheme": "payu", - "golem.com.scheme.payu.debit-note.interval-sec?": 120, - "golem.com.scheme.payu.payment-timeout-sec?": 120, - "golem.com.usage.vector": ["golem.usage.duration_sec", "golem.usage.cpu_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "smx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "hle", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "rtm", - "deprecate_fpu_cs_ds", - "mpx", - "rdseed", - "adx", - "smap", - "clflushopt", - "processor_trace", - "sgx", - "sgx_lc", - ], - "golem.inf.cpu.cores": 6, - "golem.inf.cpu.model": "Stepping 10 Family 6 Model 302", - "golem.inf.cpu.threads": 1, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 4, - "golem.inf.storage.gib": 20, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "fbwk2t2", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994556583)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-4301c24faa9776388ccf49837b2c60986a039a54c9310592c74908cf3fcd58a0", - issuerId: "0x79d38971d4a484d07925b5919aaf7cab807696f4", - state: "Initial", - timestamp: "2022-11-24T08:22:11.469747Z", - }, - }, - // 2 - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:21:41.400Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["http", "https", "gftp"], - "golem.com.payment.debit-notes.accept-timeout?": 240, - "golem.com.payment.platform.erc20-holesky-tglm.address": "0x3a8052f782c55f96be7ffbce22587ed917ad98b9", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0x3a8052f782c55f96be7ffbce22587ed917ad98b9", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0x3a8052f782c55f96be7ffbce22587ed917ad98b9", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0x3a8052f782c55f96be7ffbce22587ed917ad98b9", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.0001, 0.00005, 0], - "golem.com.scheme": "payu", - "golem.com.scheme.payu.debit-note.interval-sec?": 120, - "golem.com.scheme.payu.payment-timeout-sec?": 120, - "golem.com.usage.vector": ["golem.usage.cpu_sec", "golem.usage.duration_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "smx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "hle", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "rtm", - "deprecate_fpu_cs_ds", - "mpx", - "rdseed", - "adx", - "smap", - "clflushopt", - "processor_trace", - "sgx", - "sgx_lc", - ], - "golem.inf.cpu.cores": 6, - "golem.inf.cpu.model": "Stepping 10 Family 6 Model 302", - "golem.inf.cpu.threads": 1, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 4, - "golem.inf.storage.gib": 20, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "michal", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994551780)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-f6224492cb90e492135b9720d8ff8ea0928335357bf1a08b0abfe45dee2103fe", - issuerId: "0x3a8052f782c55f96be7ffbce22587ed917ad98b9", - state: "Initial", - timestamp: "2022-11-24T08:21:41.399655Z", - }, - }, - // 3 - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:21:42.270Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["https", "http", "gftp"], - "golem.com.payment.debit-notes.accept-timeout?": 240, - "golem.com.payment.platform.erc20-holesky-tglm.address": "0x44df807cd832393b254378ec33efb65adee837b8", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0x44df807cd832393b254378ec33efb65adee837b8", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0x44df807cd832393b254378ec33efb65adee837b8", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0x44df807cd832393b254378ec33efb65adee837b8", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.00005, 0.0001, 0], - "golem.com.scheme": "payu", - "golem.com.scheme.payu.debit-note.interval-sec?": 120, - "golem.com.scheme.payu.payment-timeout-sec?": 120, - "golem.com.usage.vector": ["golem.usage.duration_sec", "golem.usage.cpu_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Xeon(R) CPU E3-1270 v6 @ 3.80GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "smx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "hle", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "rtm", - "deprecate_fpu_cs_ds", - "mpx", - "rdseed", - "adx", - "smap", - "clflushopt", - "processor_trace", - "sgx", - ], - "golem.inf.cpu.cores": 4, - "golem.inf.cpu.model": "Stepping 9 Family 6 Model 302", - "golem.inf.cpu.threads": 2, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 8, - "golem.inf.storage.gib": 100, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "q53", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994554487)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-96e1f6c59e542dfb5602af5022030f6ce2208f2e702c571888d9aeeabd621fc9", - issuerId: "0x44df807cd832393b254378ec33efb65adee837b8", - state: "Initial", - timestamp: "2022-11-24T08:21:42.270199Z", - }, - }, - // 4 - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:21:46.446Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["https", "http", "gftp"], - "golem.com.payment.debit-notes.accept-timeout?": 240, - "golem.com.payment.platform.erc20-holesky-tglm.address": "0x700e83ffc421d43f95c340774d5816b985fcf804", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0x700e83ffc421d43f95c340774d5816b985fcf804", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0x700e83ffc421d43f95c340774d5816b985fcf804", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0x700e83ffc421d43f95c340774d5816b985fcf804", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.00005, 0.0001, 0], - "golem.com.scheme": "payu", - "golem.com.scheme.payu.debit-note.interval-sec?": 120, - "golem.com.scheme.payu.payment-timeout-sec?": 120, - "golem.com.usage.vector": ["golem.usage.duration_sec", "golem.usage.cpu_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "deprecate_fpu_cs_ds", - ], - "golem.inf.cpu.cores": 4, - "golem.inf.cpu.model": "Stepping 3 Family 6 Model 108", - "golem.inf.cpu.threads": 1, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 4, - "golem.inf.storage.gib": 20, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "sharkoon_378", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994563392)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-b50df314ed6c2c7710047d445ec5ecc256892c5b7515c3a09461de7fdb27f73e", - issuerId: "0x700e83ffc421d43f95c340774d5816b985fcf804", - state: "Initial", - timestamp: "2022-11-24T08:21:46.445758Z", - }, - }, - // 5 - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:22:05.788Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["http", "https", "gftp"], - "golem.com.payment.debit-notes.accept-timeout?": 240, - "golem.com.payment.platform.erc20-holesky-tglm.address": "0x4316e60d7154a99b16d4cd43202017983cdb6bcb", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0x4316e60d7154a99b16d4cd43202017983cdb6bcb", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0x4316e60d7154a99b16d4cd43202017983cdb6bcb", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0x4316e60d7154a99b16d4cd43202017983cdb6bcb", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.0001, 0.00005, 0], - "golem.com.scheme": "payu", - "golem.com.scheme.payu.debit-note.interval-sec?": 120, - "golem.com.scheme.payu.payment-timeout-sec?": 120, - "golem.com.usage.vector": ["golem.usage.cpu_sec", "golem.usage.duration_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Xeon(R) CPU E3-1270 v6 @ 3.80GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "smx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "hle", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "rtm", - "deprecate_fpu_cs_ds", - "mpx", - "rdseed", - "adx", - "smap", - "clflushopt", - "processor_trace", - "sgx", - ], - "golem.inf.cpu.cores": 4, - "golem.inf.cpu.model": "Stepping 9 Family 6 Model 302", - "golem.inf.cpu.threads": 2, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 8, - "golem.inf.storage.gib": 100, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "jiuzhang", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994648654)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-2eea8fd0bb37efee7ac1ab9b8c3c08f69e61fca5c3922a1c5e966773a609bc2d", - issuerId: "0x4316e60d7154a99b16d4cd43202017983cdb6bcb", - state: "Initial", - timestamp: "2022-11-24T08:22:05.787609Z", - }, - }, - // 6 - invalid payment platform - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:22:05.788Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["http", "https", "gftp"], - "golem.com.payment.debit-notes.accept-timeout?": 240, - "golem.com.payment.platform.erc20-invalid1.address": "0x4316e60d7154a99b16d4cd43202017983cdb6bcb", - "golem.com.payment.platform.erc20--invalid2-tglm.address": "0x4316e60d7154a99b16d4cd43202017983cdb6bcb", - "golem.com.payment.platform.erc20--invalid3-tglm.address": "0x4316e60d7154a99b16d4cd43202017983cdb6bcb", - "golem.com.payment.platform.zksync--invalid4-tglm.address": "0x4316e60d7154a99b16d4cd43202017983cdb6bcb", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.0001, 0.00005, 0], - "golem.com.scheme": "payu", - "golem.com.scheme.payu.debit-note.interval-sec?": 120, - "golem.com.scheme.payu.payment-timeout-sec?": 120, - "golem.com.usage.vector": ["golem.usage.cpu_sec", "golem.usage.duration_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Xeon(R) CPU E3-1270 v6 @ 3.80GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "smx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "hle", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "rtm", - "deprecate_fpu_cs_ds", - "mpx", - "rdseed", - "adx", - "smap", - "clflushopt", - "processor_trace", - "sgx", - ], - "golem.inf.cpu.cores": 4, - "golem.inf.cpu.model": "Stepping 9 Family 6 Model 302", - "golem.inf.cpu.threads": 2, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 8, - "golem.inf.storage.gib": 100, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "jiuzhang", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994648654)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-2eea8fd0bb37efee7ac1ab9b8c3c08f69e61fca5c3922a1c5e966773a609bc2d", - issuerId: "0x4316e60d7154a99b16d4cd43202017983cdb6bcb", - state: "Initial", - timestamp: "2022-11-24T08:22:05.787609Z", - }, - }, -]; - -export const proposalsDraft = [ - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:22:40.471Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["http", "https", "gftp"], - "golem.com.payment.platform.erc20-holesky-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.00005, 0.0001, 0], - "golem.com.scheme": "payu", - "golem.com.usage.vector": ["golem.usage.duration_sec", "golem.usage.cpu_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "smx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "hle", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "rtm", - "deprecate_fpu_cs_ds", - "mpx", - "rdseed", - "adx", - "smap", - "clflushopt", - "processor_trace", - "sgx", - "sgx_lc", - ], - "golem.inf.cpu.cores": 6, - "golem.inf.cpu.model": "Stepping 10 Family 6 Model 302", - "golem.inf.cpu.threads": 1, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 4, - "golem.inf.storage.gib": 20, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "golem2004", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994561269)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-818ed14e04477af6ead7aacd7c03ee3bfebadaf5b26924dcb54ae7c2b78274cd", - issuerId: "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - state: "Draft", - timestamp: "2022-11-24T08:22:40.486195451Z", - prevProposalId: "R-49263a0d6627e42a3df6650dd513be7748f680d6a119eab0c06f5b118da170c9", - }, - }, - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:26:09.068Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["https", "http", "gftp"], - "golem.com.payment.platform.erc20-holesky-tglm.address": "0x44df807cd832393b254378ec33efb65adee837b8", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0x44df807cd832393b254378ec33efb65adee837b8", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0x44df807cd832393b254378ec33efb65adee837b8", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0x44df807cd832393b254378ec33efb65adee837b8", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.00005, 0.0001, 0], - "golem.com.scheme": "payu", - "golem.com.usage.vector": ["golem.usage.duration_sec", "golem.usage.cpu_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Xeon(R) CPU E3-1270 v6 @ 3.80GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "smx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "hle", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "rtm", - "deprecate_fpu_cs_ds", - "mpx", - "rdseed", - "adx", - "smap", - "clflushopt", - "processor_trace", - "sgx", - ], - "golem.inf.cpu.cores": 4, - "golem.inf.cpu.model": "Stepping 9 Family 6 Model 302", - "golem.inf.cpu.threads": 2, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 8, - "golem.inf.storage.gib": 100, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "q53", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994554487)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-bc5a613cf03260bb163bdc4343f5c2d783f36ad2ff3eeca07bb7651f7301eec9", - issuerId: "0x44df807cd832393b254378ec33efb65adee837b8", - state: "Draft", - timestamp: "2022-11-24T08:26:09.091332272Z", - prevProposalId: "R-595633882f910f59f9422da6581628657d9a598757a4d1564ed5c1850e1ea7f1", - }, - }, - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:26:09.155Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["https", "http", "gftp"], - "golem.com.payment.platform.erc20-holesky-tglm.address": "0x700e83ffc421d43f95c340774d5816b985fcf804", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0x700e83ffc421d43f95c340774d5816b985fcf804", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0x700e83ffc421d43f95c340774d5816b985fcf804", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0x700e83ffc421d43f95c340774d5816b985fcf804", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.00005, 0.0001, 0], - "golem.com.scheme": "payu", - "golem.com.usage.vector": ["golem.usage.duration_sec", "golem.usage.cpu_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "deprecate_fpu_cs_ds", - ], - "golem.inf.cpu.cores": 4, - "golem.inf.cpu.model": "Stepping 3 Family 6 Model 108", - "golem.inf.cpu.threads": 1, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 4, - "golem.inf.storage.gib": 20, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "sharkoon_378", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994563392)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-de45ac1f188cc45a7800fced225a18bc65168f9c9d0e780c2d75962b834aab38", - issuerId: "0x700e83ffc421d43f95c340774d5816b985fcf804", - state: "Draft", - timestamp: "2022-11-24T08:26:09.139536649Z", - prevProposalId: "R-4e84f800b07564757b6c952946e1019d89633b999c992356efaee23be7aa92a4", - }, - }, - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:26:43.283Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["gftp", "https", "http"], - "golem.com.payment.platform.erc20-holesky-tglm.address": "0xccf5d4e9f6dcfc553e7a4ba6a08b8b3e4fd785a2", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0xccf5d4e9f6dcfc553e7a4ba6a08b8b3e4fd785a2", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0xccf5d4e9f6dcfc553e7a4ba6a08b8b3e4fd785a2", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0xccf5d4e9f6dcfc553e7a4ba6a08b8b3e4fd785a2", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.0001, 0.00005, 0], - "golem.com.scheme": "payu", - "golem.com.usage.vector": ["golem.usage.cpu_sec", "golem.usage.duration_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "deprecate_fpu_cs_ds", - ], - "golem.inf.cpu.cores": 4, - "golem.inf.cpu.model": "Stepping 3 Family 6 Model 108", - "golem.inf.cpu.threads": 1, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 4, - "golem.inf.storage.gib": 20, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "sharkoon_379", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994562764)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-5dcfc5bff6348573ea4512f23125d21275db1c158a6816ac3ed83ae3ba518d92", - issuerId: "0xccf5d4e9f6dcfc553e7a4ba6a08b8b3e4fd785a2", - state: "Draft", - timestamp: "2022-11-24T08:26:43.307984303Z", - prevProposalId: "R-bc46f25fe3cdfd5af890d6dd64d8e664680dcd51e0c479ca4cabd022ce1e5c8f", - }, - }, -]; - -export const proposalsWrongPaymentPlatform = [ - // 0 - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:21:05.761Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["http", "https", "gftp"], - "golem.com.payment.debit-notes.accept-timeout?": 240, - "golem.com.payment.platform.wrong1.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.payment.platform.wrong2.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.00005, 0.0001, 0], - "golem.com.scheme": "payu", - "golem.com.scheme.payu.debit-note.interval-sec?": 120, - "golem.com.scheme.payu.payment-timeout-sec?": 120, - "golem.com.usage.vector": ["golem.usage.duration_sec", "golem.usage.cpu_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "smx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "hle", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "rtm", - "deprecate_fpu_cs_ds", - "mpx", - "rdseed", - "adx", - "smap", - "clflushopt", - "processor_trace", - "sgx", - "sgx_lc", - ], - "golem.inf.cpu.cores": 6, - "golem.inf.cpu.model": "Stepping 10 Family 6 Model 302", - "golem.inf.cpu.threads": 1, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 4, - "golem.inf.storage.gib": 20, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "golem2004", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994561269)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-20a722b5aa370c2ce25fa2621143541ee17fc8654e54ecdd76dd4e76bb83f476", - issuerId: "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - state: "Initial", - timestamp: "2022-11-24T08:21:05.760367Z", - }, - }, -]; - -export const proposalsShortDebitNoteTimeout = [ - // 0 - { - eventType: "ProposalEvent", - eventDate: "2022-11-24T08:21:05.761Z", - proposal: { - properties: { - "golem.activity.caps.transfer.protocol": ["http", "https", "gftp"], - "golem.com.payment.debit-notes.accept-timeout?": 15, - "golem.com.payment.platform.erc20-holesky-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.payment.platform.erc20-mumbai-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.payment.platform.erc20-rinkeby-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.payment.platform.zksync-rinkeby-tglm.address": "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - "golem.com.pricing.model": "linear", - "golem.com.pricing.model.linear.coeffs": [0.00005, 0.0001, 0], - "golem.com.scheme": "payu", - "golem.com.scheme.payu.debit-note.interval-sec?": 120, - "golem.com.scheme.payu.payment-timeout-sec?": 120, - "golem.com.usage.vector": ["golem.usage.duration_sec", "golem.usage.cpu_sec"], - "golem.inf.cpu.architecture": "x86_64", - "golem.inf.cpu.brand": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz", - "golem.inf.cpu.capabilities": [ - "sse3", - "pclmulqdq", - "dtes64", - "monitor", - "dscpl", - "vmx", - "smx", - "eist", - "tm2", - "ssse3", - "fma", - "cmpxchg16b", - "pdcm", - "pcid", - "sse41", - "sse42", - "x2apic", - "movbe", - "popcnt", - "tsc_deadline", - "aesni", - "xsave", - "osxsave", - "avx", - "f16c", - "rdrand", - "fpu", - "vme", - "de", - "pse", - "tsc", - "msr", - "pae", - "mce", - "cx8", - "apic", - "sep", - "mtrr", - "pge", - "mca", - "cmov", - "pat", - "pse36", - "clfsh", - "ds", - "acpi", - "mmx", - "fxsr", - "sse", - "sse2", - "ss", - "htt", - "tm", - "pbe", - "fsgsbase", - "adjust_msr", - "bmi1", - "hle", - "avx2", - "smep", - "bmi2", - "rep_movsb_stosb", - "invpcid", - "rtm", - "deprecate_fpu_cs_ds", - "mpx", - "rdseed", - "adx", - "smap", - "clflushopt", - "processor_trace", - "sgx", - "sgx_lc", - ], - "golem.inf.cpu.cores": 6, - "golem.inf.cpu.model": "Stepping 10 Family 6 Model 302", - "golem.inf.cpu.threads": 1, - "golem.inf.cpu.vendor": "GenuineIntel", - "golem.inf.mem.gib": 4, - "golem.inf.storage.gib": 20, - "golem.node.debug.subnet": "public", - "golem.node.id.name": "golem2004", - "golem.runtime.capabilities": ["vpn"], - "golem.runtime.name": "vm", - "golem.runtime.version": "0.2.11", - "golem.srv.caps.multi-activity": true, - }, - constraints: "(&\n (golem.srv.comp.expiration>1667994561269)\n (golem.node.debug.subnet=public)\n)", - proposalId: "R-20a722b5aa370c2ce25fa2621143541ee17fc8654e54ecdd76dd4e76bb83f476", - issuerId: "0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655", - state: "Initial", - timestamp: "2022-11-24T08:21:05.760367Z", - }, - }, -]; diff --git a/tests/mock/index.ts b/tests/mock/index.ts deleted file mode 100644 index d217f9e79..000000000 --- a/tests/mock/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { allocationMock } from "./entities/allocation"; -export { packageMock } from "./entities/package"; -export { paymentServiceMock } from "./services/payment"; -export { agreementPoolServiceMock } from "./services/agrrement_pool"; -export { networkServiceMock } from "./services/network"; -export { StorageProviderMock } from "./entities/storage_provider"; -export { LoggerMock } from "./utils/logger"; -export { YagnaMock } from "./rest/yagna"; diff --git a/tests/mock/rest/activity.ts b/tests/mock/rest/activity.ts deleted file mode 100644 index 3608321e6..000000000 --- a/tests/mock/rest/activity.ts +++ /dev/null @@ -1,129 +0,0 @@ -/* eslint @typescript-eslint/ban-ts-comment: 0 */ -import { RequestorControlApi } from "ya-ts-client/dist/ya-activity/src/api/requestor-control-api"; -import { - ActivityState, - ActivityStateStateEnum, - CreateActivityRequest, - CreateActivityResult, - ExeScriptCommandResult, - ExeScriptRequest, -} from "ya-ts-client/dist/ya-activity/src/models"; -import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios"; -import { v4 as uuidv4 } from "uuid"; -import { ExeScriptCommandResultResultEnum } from "ya-ts-client/dist/ya-activity/src/models/exe-script-command-result"; -import { RequestorStateApi } from "ya-ts-client/dist/ya-activity/src/api/requestor-state-api"; - -const exampleExeResult = { - index: 0, - eventDate: new Date().toISOString(), - result: "Ok" as ExeScriptCommandResultResultEnum, - stdout: "test_result", - stderr: "", - message: "", - isBatchFinished: true, -}; -global.expectedExeResults = []; -export const setExpectedExeResults = (results) => - results.forEach((result, i) => { - global.expectedExeResults[i] = Object.assign({}, exampleExeResult); - global.expectedExeResults[i] = Object.assign(global.expectedExeResults[i], result); - global.expectedExeResults[i].index = i; - global.expectedExeResults[i].isBatchFinished = i === results.length - 1; - }); - -global.expectedErrors = []; -export const setExpectedErrors = (errors) => (global.expectedErrors = errors); - -global.expectedStates = []; -export const setExpectedStates = (states) => (global.expectedStates = states); - -export const clear = () => { - global.expectedExeResults = []; - global.expectedErrors = []; - global.expectedStates = []; -}; - -export class RequestorControlApiMock extends RequestorControlApi { - constructor() { - super(); - } - // @ts-ignore - async createActivity( - stringCreateActivityRequest: string | CreateActivityRequest, - timeout?: number, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({ data: { activityId: uuidv4() } } as AxiosResponse)); - } - // @ts-ignore - async exec( - activityId: string, - script: ExeScriptRequest, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({ data: uuidv4() } as AxiosResponse)); - } - // @ts-ignore - async getExecBatchResults( - activityId: string, - batchId: string, - commandIndex?: number, - timeout?: number, - options?: AxiosRequestConfig, - ): Promise> { - if (global.expectedErrors.length) { - const mockError = global.expectedErrors.shift(); - const error = new Error(mockError!.message) as AxiosError; - error.response = { - data: { message: mockError!.message }, - status: mockError!.status, - } as AxiosResponse; - throw error; - } - await new Promise((res) => setTimeout(res, 100)); - return new Promise((res) => - res({ - data: global.expectedExeResults?.length ? global.expectedExeResults : [exampleExeResult], - } as AxiosResponse), - ); - } - - // @ts-ignore - async destroyActivity( - activityId: string, - timeout?: number, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({ data: null } as AxiosResponse)); - } -} - -export class RequestorSateApiMock extends RequestorStateApi { - private exampleStates = [ - [ActivityStateStateEnum.Initialized, ActivityStateStateEnum.Ready], - [ActivityStateStateEnum.Ready, ActivityStateStateEnum.Ready], - [ActivityStateStateEnum.Ready, ActivityStateStateEnum.Ready], - [ActivityStateStateEnum.Ready, ActivityStateStateEnum.Ready], - [ActivityStateStateEnum.Ready, ActivityStateStateEnum.Ready], - [ActivityStateStateEnum.Ready, ActivityStateStateEnum.Ready], - [ActivityStateStateEnum.Ready, ActivityStateStateEnum.Ready], - [ActivityStateStateEnum.Ready, ActivityStateStateEnum.Ready], - [ActivityStateStateEnum.Ready, ActivityStateStateEnum.Ready], - ]; - constructor() { - super(); - } - - // @ts-ignore - getActivityState(activityId: string, options?: AxiosRequestConfig): Promise> { - return new Promise((res) => - res({ - data: { - state: global.expectedStates.length ? global.expectedStates : this.exampleStates.shift(), - reason: "test", - errorMessage: "test", - }, - } as AxiosResponse), - ); - } -} diff --git a/tests/mock/rest/gsb.ts b/tests/mock/rest/gsb.ts deleted file mode 100644 index c3ccfe3aa..000000000 --- a/tests/mock/rest/gsb.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { GftpFileInfo, RequestorApi, ServiceModel } from "../../../src/utils/yagna/gsb"; - -export class GsbApiMock extends RequestorApi { - async createService(fileInfo: GftpFileInfo, components: string[]): Promise { - return new Promise((res) => res({ servicesId: "test_id" })); - } - - async deleteService(id: string): Promise { - return new Promise((res) => res()); - } -} diff --git a/tests/mock/rest/identity.ts b/tests/mock/rest/identity.ts deleted file mode 100644 index 760e97e16..000000000 --- a/tests/mock/rest/identity.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TEST_IDENTITY } from "../fixtures"; -import { IdentityModel, RequestorApi } from "../../../src/utils/yagna/identity"; - -export class IdentityApiMock extends RequestorApi { - async getIdentity(): Promise { - return new Promise((res) => res({ identity: TEST_IDENTITY } as IdentityModel)); - } -} diff --git a/tests/mock/rest/market.ts b/tests/mock/rest/market.ts deleted file mode 100644 index bd6fd96f1..000000000 --- a/tests/mock/rest/market.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* eslint @typescript-eslint/ban-ts-comment: 0 */ -import { RequestorApi } from "ya-ts-client/dist/ya-market/src/api/requestor-api"; -import { AgreementProposal } from "ya-ts-client/dist/ya-market/src/models"; -import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios"; -import { v4 as uuidv4 } from "uuid"; -import { DemandOfferBase, Event } from "ya-ts-client/dist/ya-market/src/models"; -import { agreementsApproved, proposalsDraft, proposalsInitial } from "../fixtures"; -import { sleep } from "../../../src/utils"; - -global.expectedProposals = []; -export const setExpectedProposals = (proposals) => (global.expectedProposals = proposals); -global.expectedError; -export const setExpectedError = (error) => (global.expectedError = error); - -export class MarketApiMock extends RequestorApi { - private exampleProposals = [...proposalsInitial, proposalsDraft]; - - // @ts-ignore - async createAgreement( - createAgreementRequest: AgreementProposal, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({ data: uuidv4() } as AxiosResponse)); - } - // @ts-ignore - async getAgreement(agreementId: string, options?: AxiosRequestConfig): Promise> { - const agreementData = agreementsApproved[0]; - return new Promise((res) => res({ data: agreementData } as AxiosResponse)); - } - // @ts-ignore - async confirmAgreement(agreementId: string): Promise> { - return new Promise((res) => res({} as AxiosResponse)); - } - // @ts-ignore - async terminateAgreement(agreementId: string): Promise> { - return new Promise((res) => res({} as AxiosResponse)); - } - // @ts-ignore - async waitForApproval(agreementId: string): Promise> { - await sleep(1); - return new Promise((res) => res({} as AxiosResponse)); - } - - // @ts-ignore - async subscribeDemand( - demandOfferBase: DemandOfferBase, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({ data: uuidv4() } as AxiosResponse)); - } - // @ts-ignore - async collectOffers( - subscriptionId: string, - timeout?: number, - maxEvents?: number, - options?: AxiosRequestConfig, - ): Promise> { - if (global.expectedError) { - const error = new Error(global.expectedError.message) as AxiosError; - error.response = { - data: { message: global.expectedError.message }, - status: global.expectedError.status, - } as AxiosResponse; - throw error; - } - await new Promise((res) => setTimeout(res, 10)); - return new Promise((res) => res({ data: global.expectedProposals || this.exampleProposals } as AxiosResponse)); - } - // @ts-ignore - unsubscribeDemand( - subscriptionId: string, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({ data: true } as AxiosResponse)); - } - // @ts-ignore - rejectProposalOffer( - subscriptionId: string, - proposalId: string, - requestBody?: { - [key: string]: object; - }, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({ data: true } as AxiosResponse)); - } - // @ts-ignore - counterProposalDemand( - subscriptionId: string, - proposalId: string, - demandOfferBase: DemandOfferBase, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({ data: proposalId } as AxiosResponse)); - } -} diff --git a/tests/mock/rest/network.ts b/tests/mock/rest/network.ts deleted file mode 100644 index a186a0cec..000000000 --- a/tests/mock/rest/network.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* eslint @typescript-eslint/ban-ts-comment: 0 */ -import { RequestorApi } from "ya-ts-client/dist/ya-net/src/api/requestor-api"; -import { v4 as uuidv4 } from "uuid"; -import { Address, Network, Node } from "ya-ts-client/dist/ya-net/src/models"; -import { AxiosRequestConfig, AxiosResponse } from "axios"; - -export class NetworkApiMock extends RequestorApi { - error; - constructor() { - super(); - } - setExpectedError(e) { - this.error = e; - } - // @ts-ignore - createNetwork(network: Network, options?: AxiosRequestConfig): Promise> { - return new Promise((res) => - res({ data: { id: uuidv4(), ip: "192.168.0.0", mask: "255.255.255.0" } } as AxiosResponse), - ); - } - // @ts-ignore - removeNetwork(networkId: string, options?: AxiosRequestConfig): Promise> { - if (this.error) return Promise.reject(this.error); - return new Promise((res) => res({ data: undefined } as AxiosResponse)); - } - - // @ts-ignore - removeNode( - networkId: string, - nodeId: string, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({ data: undefined } as AxiosResponse)); - } - - // @ts-ignore - addAddress( - networkId: string, - address: Address, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({ data: undefined } as AxiosResponse)); - } - // @ts-ignore - addNode(networkId: string, node: Node, options?: AxiosRequestConfig): Promise> { - return new Promise((res) => res({ data: undefined } as AxiosResponse)); - } -} diff --git a/tests/mock/rest/payment.ts b/tests/mock/rest/payment.ts deleted file mode 100644 index 3173beb5d..000000000 --- a/tests/mock/rest/payment.ts +++ /dev/null @@ -1,127 +0,0 @@ -/* eslint @typescript-eslint/ban-ts-comment: 0 */ -import { RequestorApi } from "ya-ts-client/dist/ya-payment/src/api/requestor-api"; -import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios"; -import { - Account, - Allocation, - DebitNoteEvent, - InvoiceEvent, - Invoice, - DebitNote, - Acceptance, -} from "ya-ts-client/dist/ya-payment/src/models"; -import { allocations, debitNotesEvents, debitNotes, invoiceEvents, invoices } from "../fixtures"; -import { Rejection } from "ya-ts-client/dist/ya-payment/src/models"; -import { sleep } from "../../../src/utils"; - -global.expectedEvents = []; -global.expectedInvoices = []; -global.expectedDebitNotes = []; - -export const setExpectedEvents = (events) => (global.expectedEvents = events); -export const setExpectedInvoices = (invoices) => (global.expectedInvoices = invoices); -export const setExpectedDebitNotes = (debitNotes) => (global.expectedDebitNotes = debitNotes); - -global.expectedError; -export const setExpectedError = (error) => (global.expectedError = error); - -export const clear = () => { - global.expectedEvents = []; - global.expectedInvoices = []; - global.expectedDebitNotes = []; -}; - -export class PaymentApiMock extends RequestorApi { - constructor() { - super(); - } - - // @ts-ignore - createAllocation( - allocation: Allocation, - afterTimestamp?: string, - maxItems?: number, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({ data: { ...allocations[0], ...allocation } } as AxiosResponse)); - } - // @ts-ignore - releaseAllocation(allocationId: string, options?: AxiosRequestConfig): Promise> { - return Promise.resolve({} as AxiosResponse); - } - - // @ts-ignore - getInvoiceEvents( - timeout?: number, - afterTimestamp?: string, - maxEvents?: number, - appSessionId?: string, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise(async (res) => { - await sleep(100, true); - res({ data: global.expectedEvents } as AxiosResponse); - }); - } - - // @ts-ignore - getInvoice(invoiceId: string, options?: AxiosRequestConfig): Promise> { - return new Promise((res) => res({ data: global.expectedInvoices[0] } as AxiosResponse)); - } - - // @ts-ignore - getDebitNoteEvents( - timeout?: number, - afterTimestamp?: string, - maxEvents?: number, - appSessionId?: string, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise(async (res) => { - await sleep(100, true); - res({ data: global.expectedEvents } as AxiosResponse); - }); - } - - // @ts-ignore - getDebitNote(debitNoteId: string, options?: AxiosRequestConfig): Promise> { - return new Promise((res) => res({ data: global.expectedDebitNotes[0] } as AxiosResponse)); - } - // @ts-ignore - acceptInvoice( - invoiceId: string, - acceptance: Acceptance, - timeout?: number, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({} as AxiosResponse)); - } - // @ts-ignore - acceptDebitNote( - debitNoteId: string, - acceptance: Acceptance, - timeout?: number, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({} as AxiosResponse)); - } - // @ts-ignore - rejectDebitNote( - debitNoteId: string, - rejection: Rejection, - timeout?: number, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({} as AxiosResponse)); - } - - // @ts-ignore - rejectInvoice( - invoiceId: string, - rejection: Rejection, - timeout?: number, - options?: AxiosRequestConfig, - ): Promise> { - return new Promise((res) => res({} as AxiosResponse)); - } -} diff --git a/tests/mock/rest/yagna.ts b/tests/mock/rest/yagna.ts deleted file mode 100644 index 4eaeab447..000000000 --- a/tests/mock/rest/yagna.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* eslint @typescript-eslint/ban-ts-comment: 0 */ -import { Yagna, YagnaApi } from "../../../src/utils"; -import { RequestorControlApiMock, RequestorSateApiMock } from "./activity"; -import { MarketApiMock } from "./market"; -import { PaymentApiMock } from "./payment"; -import { NetworkApiMock } from "./network"; -import { IdentityApiMock } from "./identity"; -import { GsbApiMock } from "./gsb"; - -export class YagnaMock extends Yagna { - constructor() { - super({ apiKey: "test_api_key" }); - } - protected createApi(): YagnaApi { - return { - // @ts-ignore - market: new MarketApiMock(), - activity: { - // @ts-ignore - control: new RequestorControlApiMock(), - // @ts-ignore - state: new RequestorSateApiMock(), - }, - // @ts-ignore - net: new NetworkApiMock(), - - // @ts-ignore - payment: new PaymentApiMock(), - identity: new IdentityApiMock(), - gsb: new GsbApiMock(), - yagnaOptions: { - apiKey: this.apiKey, - basePath: this.apiBaseUrl, - }, - }; - } -} diff --git a/tests/mock/services/agrrement_pool.ts b/tests/mock/services/agrrement_pool.ts deleted file mode 100644 index 4e31299ce..000000000 --- a/tests/mock/services/agrrement_pool.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint @typescript-eslint/ban-ts-comment: 0 */ -import { Agreement, AgreementPoolService } from "../../../src/agreement"; -import { agreementsApproved } from "../fixtures"; -import { AgreementConfig } from "../../../src/agreement"; -import { Proposal } from "../../../src/market"; -import { YagnaMock } from "../rest/yagna"; -import { mock, instance, when } from "@johanblumenberg/ts-mockito"; - -const proposals: Proposal[] = []; -const proposalMock = mock(Proposal); -const testProvider = { id: "test_provider_id", name: "Test Provider", walletAddress: "test_wallet_address" }; -when(proposalMock.provider).thenReturn(testProvider); -const proposal = instance(proposalMock); -const yagnaApi = new YagnaMock().getApi(); -const invalidProviderIds: string[] = []; -// @ts-ignore -export const agreementPoolServiceMock: AgreementPoolService = { - async getAgreement(): Promise { - const agreementData = agreementsApproved[0]; - return new Agreement(agreementData.agreementId, proposal, yagnaApi, new AgreementConfig()); - }, - async addProposal(proposal: Proposal) { - proposals.push(proposal); - }, - async releaseAgreement(agreementId: string, allowReuse: boolean) { - return undefined; - }, - // @ts-ignore - getProposals() { - return proposals; - }, - setInvalidProvider(providerId) { - invalidProviderIds.push(providerId); - }, -}; diff --git a/tests/mock/services/network.ts b/tests/mock/services/network.ts deleted file mode 100644 index 6a962458f..000000000 --- a/tests/mock/services/network.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NetworkNode, NetworkService } from "../../../src/network"; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -export const networkServiceMock: NetworkService = { - async addNode(nodeId: string, ip?: string): Promise { - return Promise.resolve({} as NetworkNode); - }, - async removeNode(nodeId: string): Promise { - return Promise.resolve(undefined); - }, - hasNode(nodeId: string): boolean { - return true; - }, - async end(): Promise { - return Promise.resolve(undefined); - }, - async run(address: string): Promise { - return Promise.resolve(undefined); - }, -}; diff --git a/tests/mock/services/payment.ts b/tests/mock/services/payment.ts deleted file mode 100644 index 92ebf6dea..000000000 --- a/tests/mock/services/payment.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Allocation } from "../../../src/payment"; -import { PaymentService } from "../../../src"; -import { allocationMock } from "../../mock"; -import { Agreement } from "../../../src/agreement"; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -export const paymentServiceMock: PaymentService = { - async createAllocation( - budget?, - payment?: { driver: string; network: string }, - timeout?: number, - ): Promise { - return Promise.resolve(allocationMock); - }, - acceptPayments(agreement: Agreement) { - return true; - }, -}; diff --git a/tests/mock/utils/empty.js b/tests/mock/utils/empty.js deleted file mode 100644 index 1477a4115..000000000 --- a/tests/mock/utils/empty.js +++ /dev/null @@ -1,3 +0,0 @@ -export const GftpStorageProvider = {}; -export const pinoLogger = {}; -export const EventSource = {}; diff --git a/tests/mock/utils/empty_default.js b/tests/mock/utils/empty_default.js deleted file mode 100644 index ff8b4c563..000000000 --- a/tests/mock/utils/empty_default.js +++ /dev/null @@ -1 +0,0 @@ -export default {}; diff --git a/tests/mock/utils/event_source.ts b/tests/mock/utils/event_source.ts deleted file mode 100644 index fc822d13f..000000000 --- a/tests/mock/utils/event_source.ts +++ /dev/null @@ -1,49 +0,0 @@ -global.events = new Map(); -global.errorEvents = new Map(); - -export class EventSourceMock { - private activityId: string; - constructor(url) { - const chunks = url?.split("/"); - this.activityId = chunks[chunks.length - 3]; - } - addEventListener(eventName, callback) { - if (eventName === "runtime") { - const runtimeInterval = setInterval(() => { - const mockEvents = global.events.get(this.activityId) || []; - if (mockEvents.length) { - callback(mockEvents.shift()); - } else { - clearInterval(runtimeInterval); - } - }, 100); - } - if (eventName === "error") { - const errorInterval = setInterval(() => { - const mockEvents = global.errorEvents.get(this.activityId) || []; - if (mockEvents.length) { - callback(mockEvents.shift()); - } else { - clearInterval(errorInterval); - } - }, 100); - } - } - close() { - // empty mock - } -} - -export const setExpectedEvents = (activityId, expectedEvents) => { - global.events.set( - activityId, - expectedEvents.map((e) => JSON.parse(JSON.stringify(e))), - ); -}; - -export const setExpectedErrorEvents = (activityId, expectedErrors) => { - global.errorEvents.set( - activityId, - expectedErrors.map((e) => JSON.parse(JSON.stringify(e))), - ); -}; diff --git a/tests/mock/utils/logger.ts b/tests/mock/utils/logger.ts deleted file mode 100644 index 642b7ca1d..000000000 --- a/tests/mock/utils/logger.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Logger } from "../../../src"; - -function ctxToString(ctx: Record | Error | undefined) { - if (!ctx) return "[no context]"; - if (ctx instanceof Error) return ctx.message; - try { - return JSON.stringify(ctx); - } catch (e) { - return ctx.toString(); - } -} - -export class LoggerMock implements Logger { - private _logs: { msg: string; ctx?: Record | Error }[] = []; - - constructor(private silent = true) {} - - child(): Logger { - return this; - } - - async expectToInclude(msg: string, ctx?: Record | Error, wait?: number) { - if (wait) await new Promise((res) => setTimeout(res, wait)); - - return expect(this._logs).toContainEqual({ msg, ctx }); - } - - async expectToMatch(msg: RegExp, wait?: number) { - if (wait) await new Promise((res) => setTimeout(res, wait)); - - return expect(this.logs).toMatch(msg); - } - - async expectToNotMatch(msg: RegExp, wait?: number) { - if (wait) await new Promise((res) => setTimeout(res, wait)); - return expect(this.logs).not.toMatch(msg); - } - - get logs() { - return this._logs.map(({ ctx, msg }) => `${msg} ${ctxToString(ctx)}`).join("\n"); - } - - clear() { - this._logs = []; - } - - error(msg: string, ctx?: Record | Error) { - this.log(msg, ctx, "error"); - } - - info(msg: string, ctx?: Record | Error) { - this.log(msg, ctx, "info"); - } - - warn(msg: string, ctx?: Record | Error) { - this.log(msg, ctx, "warn"); - } - - debug(msg: string, ctx?: Record | Error) { - this.log(msg, ctx, "debug"); - } - - private log(msg: string, ctx?: Record | Error, level = "info") { - if (!this.silent) - console.log( - `\x1b[32m[test]\x1b[0m \x1b[36m${new Date().toISOString()}\x1b[0m ${this.levelColor( - level, - )} ${msg} ${ctxToString(ctx)}`, - ); - this._logs.push({ - msg, - ctx, - }); - } - - levelColor(level) { - switch (level) { - case "warn": - return `\x1b[33m[${level}]\x1b[0m`; - case "info": - return `\x1b[34m[${level}]\x1b[0m`; - } - } -} diff --git a/tests/unit/activity.test.ts b/tests/unit/activity.test.ts deleted file mode 100644 index da6549f7c..000000000 --- a/tests/unit/activity.test.ts +++ /dev/null @@ -1,420 +0,0 @@ -import * as activityMock from "../mock/rest/activity"; -import { agreement } from "../mock/entities/agreement"; -import { EventSourceMock, setExpectedErrorEvents, setExpectedEvents } from "../mock/utils/event_source"; -import { StorageProviderMock, YagnaMock } from "../mock"; -import { Activity, ActivityStateEnum } from "../../src"; -import { sleep } from "../../src/utils"; -import { Deploy, Start, Run, Terminate, UploadFile, DownloadFile, Script, Capture } from "../../src/script"; -import { GolemWorkError, WorkErrorCode } from "../../src/task/error"; -import { GolemError, GolemTimeoutError } from "../../src/error/golem-error"; - -jest.mock("eventsource", () => EventSourceMock); -describe("Activity", () => { - const yagnaApi = new YagnaMock().getApi(); - beforeEach(() => { - activityMock.clear(); - }); - - describe("Creating", () => { - it("should create activity", async () => { - const activity = await Activity.create(agreement, yagnaApi); - expect(activity).toBeInstanceOf(Activity); - const GUID_REGEX = - /^(?:\{{0,1}(?:[0-9a-fA-F]){8}-(?:[0-9a-fA-F]){4}-(?:[0-9a-fA-F]){4}-(?:[0-9a-fA-F]){4}-(?:[0-9a-fA-F]){12}\}{0,1})$/; - expect(activity.id).toMatch(GUID_REGEX); - }); - }); - - describe("Executing", () => { - it("should execute commands on activity", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const streamResult = await activity.execute(new Deploy().toExeScriptRequest()); - const { value: result } = await streamResult[Symbol.asyncIterator]().next(); - expect(result.result).toEqual("Ok"); - }); - - it("should execute commands and get state", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const streamResult = await activity.execute(new Run("test_command").toExeScriptRequest()); - const { value: result } = await streamResult[Symbol.asyncIterator]().next(); - activityMock.setExpectedStates([ActivityStateEnum.Ready, null]); - const stateAfterRun = await activity.getState(); - expect(result.result).toEqual("Ok"); - expect(stateAfterRun).toEqual(ActivityStateEnum.Ready); - }); - - it("should execute script and get results by iterator", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const command1 = new Deploy(); - const command2 = new Start(); - const command3 = new Run("test_command1"); - const command4 = new Run("test_command2"); - const command5 = new Terminate(); - const script = Script.create([command1, command2, command3, command4, command5]); - activityMock.setExpectedExeResults([ - { stdout: "test" }, - { stdout: "test" }, - { stdout: "stdout_test_command_run_1" }, - { stdout: "stdout_test_command_run_2" }, - { stdout: "test" }, - ]); - const expectedRunStdOuts = ["test", "test", "stdout_test_command_run_1", "stdout_test_command_run_2", "test"]; - await script.before(); - const results = await activity.execute(script.getExeScriptRequest()); - for await (const result of results) { - expect(result.result).toEqual("Ok"); - expect(result.stdout).toEqual(expectedRunStdOuts.shift()); - } - await script.after([]); - await activity.stop(); - }); - - it("should execute script and get results by events", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const command1 = new Deploy(); - const command2 = new Start(); - const command3 = new UploadFile(new StorageProviderMock(), "testSrc", "testDst"); - const command4 = new Run("test_command1"); - const command5 = new DownloadFile(new StorageProviderMock(), "testSrc", "testDst"); - const command6 = new Terminate(); - const script = Script.create([command1, command2, command3, command4, command5, command6]); - activityMock.setExpectedExeResults([ - { stdout: "test" }, - { stdout: "test" }, - { stdout: "stdout_test_command_run_1" }, - { stdout: "stdout_test_command_run_2" }, - { stdout: "test" }, - { stdout: "test" }, - ]); - const expectedRunStdOuts = [ - "test", - "test", - "stdout_test_command_run_1", - "stdout_test_command_run_2", - "test", - "test", - ]; - await script.before(); - const results = await activity.execute(script.getExeScriptRequest()); - let resultCount = 0; - return new Promise((res) => { - results.on("data", (result) => { - expect(result.result).toEqual("Ok"); - expect(result.stdout).toEqual(expectedRunStdOuts.shift()); - ++resultCount; - }); - results.on("end", async () => { - await script.after([]); - await activity.stop(); - expect(resultCount).toEqual(6); - return res(); - }); - }); - }); - - it("should execute script by streaming batch", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const command1 = new Deploy(); - const command2 = new Start(); - const capture: Capture = { - stdout: { stream: { format: "string" } }, - stderr: { stream: { format: "string" } }, - }; - const command3 = new Run("test_command1", null, null, capture); - const command4 = new Terminate(); - const script = Script.create([command1, command2, command3, command4]); - const expectedEvents = [ - { - type: "runtime", - data: '{"batch_id":"04a9b0f49e564db99e6f15ba95c35817","index":0,"timestamp":"2022-06-23T10:42:38.626573153","kind":{"stdout":"{\\"startMode\\":\\"blocking\\",\\"valid\\":{\\"Ok\\":\\"\\"},\\"vols\\":[]}"}}', - }, - { - type: "runtime", - data: '{"batch_id":"04a9b0f49e564db99e6f15ba95c35817","index":0,"timestamp":"2022-06-23T10:42:38.626958777","kind":{"finished":{"return_code":0,"message":null}}}', - }, - { - type: "runtime", - data: '{"batch_id":"04a9b0f49e564db99e6f15ba95c35817","index":1,"timestamp":"2022-06-23T10:42:38.626960850","kind":{"started":{"command":{"start":{"args":[]}}}}}', - }, - { - type: "runtime", - data: '{"batch_id":"04a9b0f49e564db99e6f15ba95c35817","index":1,"timestamp":"2022-06-23T10:42:39.946031527","kind":{"finished":{"return_code":0,"message":null}}}', - }, - { - type: "runtime", - data: '{"batch_id":"04a9b0f49e564db99e6f15ba95c35817","index":2,"timestamp":"2022-06-23T10:42:39.946034161","kind":{"started":{"command":{"run":{"entry_point":"/bin/sh","args":["-c","echo +\\"test\\""],"capture":{"stdout":{"stream":{"format":"str"}},"stderr":{"stream":{"format":"str"}}}}}}}}', - }, - { - type: "runtime", - data: '{"batch_id":"04a9b0f49e564db99e6f15ba95c35817","index":2,"timestamp":"2022-06-23T10:42:39.957927713","kind":{"stdout":"test"}}', - }, - { - type: "runtime", - data: '{"batch_id":"04a9b0f49e564db99e6f15ba95c35817","index":2,"timestamp":"2022-06-23T10:42:39.958238754","kind":{"finished":{"return_code":0,"message":null}}}', - }, - { - type: "runtime", - data: '{"batch_id":"04a9b0f49e564db99e6f15ba95c35817","index":3,"timestamp":"2022-06-23T10:42:39.962014674","kind":{"started":{"command":{"terminate":{}}}}}', - }, - { - type: "runtime", - data: '{"batch_id":"04a9b0f49e564db99e6f15ba95c35817","index":3,"timestamp":"2022-06-23T10:42:40.009603540","kind":{"finished":{"return_code":0,"message":null}}}', - }, - ]; - setExpectedEvents(activity.id, expectedEvents); - await script.before(); - const results = await activity.execute(script.getExeScriptRequest(), true); - let expectedStdout; - for await (const result of results) { - expect(result).toHaveProperty("index"); - if (result.index === 2 && result.stdout) expectedStdout = result.stdout; - } - expect(expectedStdout).toEqual("test"); - await script.after([]); - await activity.stop(); - }); - }); - - describe("Getting state", () => { - it("should get activity state", async () => { - const activity = await Activity.create(agreement, yagnaApi); - activityMock.setExpectedStates([ActivityStateEnum.Ready, ActivityStateEnum.Terminated]); - const state = await activity.getState(); - expect(state).toEqual(ActivityStateEnum.Ready); - }); - }); - - describe("Cancelling", () => { - it("should cancel activity", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const command1 = new Deploy(); - const command2 = new Start(); - const command3 = new Run("test_command1"); - const command4 = new Run("test_command2"); - const command5 = new Run("test_command3"); - const command6 = new Terminate(); - const script = Script.create([command1, command2, command3, command4, command5, command6]); - await script.before(); - const results = await activity.execute(script.getExeScriptRequest(), undefined, undefined); - await activity.stop(); - return new Promise((res) => { - results.on("error", (error) => { - expect(error.toString()).toMatch(/Error: Activity .* has been interrupted/); - return res(); - }); - results.on("data", () => null); - }); - }); - - it("should cancel activity while streaming batch", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const command1 = new Deploy(); - const command2 = new Start(); - const capture: Capture = { - stdout: { stream: { format: "string" } }, - stderr: { stream: { format: "string" } }, - }; - const command3 = new Run("test_command1", null, null, capture); - const command4 = new Terminate(); - const script = Script.create([command1, command2, command3, command4]); - await script.before(); - const results = await activity.execute(script.getExeScriptRequest(), true, undefined); - await activity.stop(); - return new Promise((res) => { - results.on("error", (error) => { - expect(error.toString()).toMatch(/Error: Activity .* has been interrupted/); - return res(); - }); - results.on("data", () => null); - }); - }); - }); - - describe("Error handling", () => { - it("should handle some error", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const command1 = new Deploy(); - const command2 = new Start(); - const command3 = new Run("test_command1"); - const script = Script.create([command1, command2, command3]); - const results = await activity.execute(script.getExeScriptRequest()); - const error = { - message: "Some undefined error", - status: 400, - }; - activityMock.setExpectedErrors([error, error, error, error, error, error]); - return new Promise((res) => { - results.on("error", (error: GolemWorkError) => { - expect(error).toBeInstanceOf(GolemWorkError); - expect(error.code).toEqual(WorkErrorCode.ActivityResultsFetchingFailed); - expect(error.activity).toBeDefined(); - expect(error.agreement).toBeDefined(); - expect(error.provider?.name).toEqual("Test Provider"); - expect(error.previous?.toString()).toEqual( - "Error: Command #0 getExecBatchResults error: Some undefined error", - ); - expect(error.toString()).toEqual( - "Error: Unable to get activity results. Command #0 getExecBatchResults error: Some undefined error", - ); - return res(); - }); - results.on("data", (data) => null); - }); - }); - - it("should handle gsb error", async () => { - const activity = await Activity.create(agreement, yagnaApi, { - activityExeBatchResultPollIntervalSeconds: 10, - }); - const command1 = new Deploy(); - const command2 = new Start(); - const command3 = new Run("test_command1"); - const command4 = new Run("test_command1"); - const command5 = new Run("test_command1"); - const command6 = new Run("test_command1"); - const command7 = new Run("test_command1"); - const script = Script.create([command1, command2, command3, command4, command5, command6, command7]); - const results = await activity.execute(script.getExeScriptRequest()); - const error = { - message: "GSB error: remote service at `test` error: GSB failure: Bad request: endpoint address not found", - status: 500, - }; - activityMock.setExpectedErrors([error, error, error, error, error, error]); - return new Promise((res) => { - results.on("error", (error: GolemWorkError) => { - expect(error).toBeInstanceOf(GolemWorkError); - expect(error.code).toEqual(WorkErrorCode.ActivityResultsFetchingFailed); - expect(error.activity).toBeDefined(); - expect(error.agreement).toBeDefined(); - expect(error.provider?.name).toEqual("Test Provider"); - expect(error.previous?.toString()).toEqual( - "Error: Command #0 getExecBatchResults error: GSB error: remote service at `test` error: GSB failure: Bad request: endpoint address not found", - ); - expect(error.toString()).toEqual( - "Error: Unable to get activity results. Command #0 getExecBatchResults error: GSB error: remote service at `test` error: GSB failure: Bad request: endpoint address not found", - ); - return res(); - }); - results.on("data", () => null); - }); - }); - - it("should handle termination error", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const command1 = new Deploy(); - const command2 = new Start(); - const command3 = new Run("test_command1"); - const script = Script.create([command1, command2, command3]); - const results = await activity.execute(script.getExeScriptRequest()); - const error = { - message: "GSB error: endpoint address not found. Terminated.", - status: 500, - }; - activityMock.setExpectedErrors([error, error, error]); - activityMock.setExpectedStates([ActivityStateEnum.Terminated, ActivityStateEnum.Terminated]); - return new Promise((res) => { - results.on("error", (error: GolemWorkError) => { - expect(error).toBeInstanceOf(GolemWorkError); - expect(error.code).toEqual(WorkErrorCode.ActivityResultsFetchingFailed); - expect(error.activity).toBeDefined(); - expect(error.agreement).toBeDefined(); - expect(error.provider?.name).toEqual("Test Provider"); - expect(error.previous?.toString()).toEqual("Error: GSB error: endpoint address not found. Terminated."); - expect(error.toString()).toEqual( - "Error: Unable to get activity results. GSB error: endpoint address not found. Terminated.", - ); - return res(); - }); - results.on("data", () => null); - }); - }); - - it("should handle timeout error", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const command1 = new Deploy(); - const command2 = new Start(); - const command3 = new Run("test_command1"); - const command4 = new Run("test_command2"); - const command5 = new Run("test_command3"); - const script = Script.create([command1, command2, command3, command4, command5]); - const results = await activity.execute(script.getExeScriptRequest(), false, 1); - await sleep(10, true); - return new Promise((res) => { - results.on("error", (error: GolemWorkError) => { - expect(error).toBeInstanceOf(GolemTimeoutError); - expect(error.toString()).toMatch(/Error: Activity .* timeout/); - return res(); - }); - // results.on("end", () => rej()); - results.on("data", () => null); - }); - }); - - it("should handle timeout error while streaming batch", async () => { - const activity = await Activity.create(agreement, yagnaApi, { activityExecuteTimeout: 1 }); - const command1 = new Deploy(); - const command2 = new Start(); - const capture: Capture = { - stdout: { stream: { format: "string" } }, - stderr: { stream: { format: "string" } }, - }; - const command3 = new Run("test_command1", null, null, capture); - const command4 = new Terminate(); - const script = Script.create([command1, command2, command3, command4]); - await script.before(); - const results = await activity.execute(script.getExeScriptRequest(), true, 800); - return new Promise((res, rej) => { - results.on("error", (error: GolemError) => { - expect(error).toBeInstanceOf(GolemTimeoutError); - expect(error.toString()).toMatch(/Error: Activity .* timeout/); - return res(); - }); - results.on("end", () => rej()); - results.on("data", () => null); - }); - }); - - it("should handle some error while streaming batch", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const command1 = new Deploy(); - const command2 = new Start(); - const capture: Capture = { - stdout: { stream: { format: "string" } }, - stderr: { stream: { format: "string" } }, - }; - const command3 = new Run("test_command1", null, null, capture); - const command4 = new Terminate(); - const script = Script.create([command1, command2, command3, command4]); - const expectedErrors: Partial[] = [ - { - data: { - type: "error", - message: "Some undefined error", - }, - }, - ]; - setExpectedErrorEvents(activity.id, expectedErrors); - await script.before(); - const results = await activity.execute(script.getExeScriptRequest(), true); - return new Promise((res) => { - results.on("error", (error: GolemWorkError) => { - expect(error).toBeInstanceOf(GolemWorkError); - expect(error.code).toEqual(WorkErrorCode.ActivityResultsFetchingFailed); - expect(error.activity).toBeDefined(); - expect(error.agreement).toBeDefined(); - expect(error.provider?.name).toEqual("Test Provider"); - expect(error.toString()).toEqual('Error: Unable to get activity results. ["Some undefined error"]'); - return res(); - }); - results.on("data", () => null); - }); - }); - }); - - describe("Destroying", () => { - it("should stop activity", async () => { - const activity = await Activity.create(agreement, yagnaApi); - expect(await activity.stop()).toEqual(true); - }); - }); -}); diff --git a/tests/unit/agreement.test.ts b/tests/unit/agreement.test.ts deleted file mode 100644 index c0089ee8b..000000000 --- a/tests/unit/agreement.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { LoggerMock, YagnaMock } from "../mock"; -import { Agreement } from "../../src/agreement"; -import { AgreementStateEnum } from "ya-ts-client/dist/ya-market/src/models/agreement"; -import { instance, mock, when } from "@johanblumenberg/ts-mockito"; -import { Proposal } from "../../src/market"; - -const logger = new LoggerMock(); -const yagnaApi = new YagnaMock().getApi(); - -const proposalMock = mock(Proposal); -const testProvider = { id: "test_provider_id", name: "Test Provider", walletAddress: "test_wallet_address" }; -when(proposalMock.provider).thenReturn(testProvider); -const proposal = instance(proposalMock); - -describe("Agreement", () => { - beforeEach(() => logger.clear()); - describe("create()", () => { - it("should create agreement for given proposal Id", async () => { - const agreement = await Agreement.create(proposal, yagnaApi, { logger }); - expect(agreement).toBeInstanceOf(Agreement); - expect(agreement.id).toBeDefined(); - await logger.expectToInclude("Agreement created", { id: agreement.id }); - }); - }); - - describe("provider", () => { - it("should be a instance ProviderInfo with provider details", async () => { - const agreement = await Agreement.create(proposal, yagnaApi, { logger }); - expect(agreement).toBeInstanceOf(Agreement); - expect(agreement.getProviderInfo().id).toEqual(expect.any(String)); - expect(agreement.getProviderInfo().name).toEqual(expect.any(String)); - }); - }); - - describe("getState()", () => { - it("should return state of agreement", async () => { - const agreement = await Agreement.create(proposal, yagnaApi, { logger }); - expect(await agreement.getState()).toEqual(AgreementStateEnum.Approved); - }); - }); - - describe("terminate()", () => { - it("should terminate agreement", async () => { - const agreement = await Agreement.create(proposal, yagnaApi, { logger }); - await agreement.terminate(); - await logger.expectToInclude("Agreement terminated", { id: agreement.id }); - }); - }); - - describe("confirm()", () => { - it("should confirm agreement", async () => { - const agreement = await Agreement.create(proposal, yagnaApi, { logger }); - await agreement.confirm(); - await logger.expectToInclude("Agreement approved", { id: agreement.id }); - }); - }); -}); diff --git a/tests/unit/agreement_pool_service.test.ts b/tests/unit/agreement_pool_service.test.ts deleted file mode 100644 index c9c538e62..000000000 --- a/tests/unit/agreement_pool_service.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { instance, mock, when } from "@johanblumenberg/ts-mockito"; - -jest.mock("ya-ts-client/dist/ya-market/api"); - -import { LoggerMock, YagnaMock } from "../mock"; -import { Agreement, AgreementPoolService } from "../../src/agreement"; -import { RequestorApi } from "ya-ts-client/dist/ya-market/api"; -import { Proposal as ProposalModel } from "ya-ts-client/dist/ya-market/src/models/proposal"; -import { ProposalAllOfStateEnum } from "ya-ts-client/dist/ya-market"; -import { Demand, Proposal } from "../../src/market"; -import { Allocation } from "../../src/payment"; - -const logger = new LoggerMock(); -const mockAPI = new RequestorApi(); -const mockSetCounteringProposalReference = jest.fn(); -const yagnaApi = new YagnaMock().getApi(); - -const createProposal = (id) => { - const allocationMock = mock(Allocation); - when(allocationMock.paymentPlatform).thenReturn("test-payment-platform"); - const demandMock = mock(Demand); - when(demandMock.id).thenReturn(id); - when(demandMock.allocation).thenReturn(instance(allocationMock)); - const testDemand = instance(demandMock); - const model: ProposalModel = { - constraints: "", - issuerId: "", - proposalId: "", - state: ProposalAllOfStateEnum.Initial, - timestamp: "", - properties: { - "golem.activity.caps.transfer.protocol": "protocol", - "golem.inf.cpu.brand": "cpu_brand", - "golem.inf.cpu.capabilities": "cpu_capabilities", - "golem.inf.cpu.cores": "cpu_cores", - "golem.inf.cpu.threads": "cpu_threads", - "golem.inf.mem.gib": "mem_gib", - "golem.inf.storage.gib": "storage_gib", - "golem.node.id.name": "node_id_name", - "golem.node.net.is-public": true, - "golem.runtime.capabilities": ["a", "b", "c"], - "golem.runtime.name": "runtime_name", - "golem.com.usage.vector": ["golem.usage.duration_sec", "golem.usage.cpu_sec"], - "golem.com.pricing.model.linear.coeffs": [0.1, 0.2, 0.0], - }, - }; - - return new Proposal(testDemand, null, mockSetCounteringProposalReference, mockAPI, model); -}; - -describe("Agreement Pool Service", () => { - beforeEach(() => { - logger.clear(); - }); - - describe("run()", () => { - it("should start service", async () => { - const agreementService = new AgreementPoolService(yagnaApi, { logger }); - await agreementService.run(); - expect(logger.logs).toContain("Agreement Pool Service has started"); - await agreementService.end(); - }); - }); - describe("end()", () => { - it("should stop service", async () => { - const agreementService = new AgreementPoolService(yagnaApi, { logger }); - await agreementService.run(); - await agreementService.end(); - expect(logger.logs).toContain("Agreement Pool Service has been stopped"); - }); - }); - describe("getAvailableAgreement()", () => { - it("should create and return agreement from available proposal pool", async () => { - const agreementService = new AgreementPoolService(yagnaApi, { logger }); - await agreementService.run(); - - await agreementService.addProposal(createProposal("proposal-id")); - const agreement = await agreementService.getAgreement(); - expect(agreement).toBeInstanceOf(Agreement); - }); - it("should return agreement if is available in the pool", async () => { - const agreementService = new AgreementPoolService(yagnaApi, { logger }); - await agreementService.run(); - await agreementService.addProposal(createProposal("proposal-id")); - const agreement1 = await agreementService.getAgreement(); - await agreementService.releaseAgreement(agreement1.id, true); - const agreement2 = await agreementService.getAgreement(); - expect(agreement1).toEqual(agreement2); - }); - }); - describe("releaseAgreement()", () => { - it("should return agreement to the pool if flag reuse is on", async () => { - const agreementService = new AgreementPoolService(yagnaApi, { logger }); - await agreementService.run(); - await agreementService.addProposal(createProposal("proposal-id")); - const agreement = await agreementService.getAgreement(); - await agreementService.releaseAgreement(agreement.id, true); - await logger.expectToInclude(`Agreement has been released for reuse`, { id: agreement.id }); - }); - - it("should terminate agreement if flag reuse is off", async () => { - const agreementService = new AgreementPoolService(yagnaApi, { logger }); - await agreementService.run(); - await agreementService.addProposal(createProposal("proposal-id")); - const agreement = await agreementService.getAgreement(); - await agreementService.releaseAgreement(agreement.id, false); - await logger.expectToInclude(`Agreement has been released and will be terminated`, { id: agreement.id }); - await logger.expectToInclude(`Agreement terminated`, { id: agreement.id }); - }); - - it("should warn if there is no agreement with given id", async () => { - const agreementService = new AgreementPoolService(yagnaApi, { logger }); - await agreementService.run(); - await agreementService.addProposal(createProposal("proposal-id")); - const agreement = await agreementService.getAgreement(); - await agreementService.releaseAgreement("not-known-id", true); - await logger.expectToInclude("Agreement not found in the pool", { id: "not-known-id" }); - }); - - it("should terminate agreement if pool is full", async () => { - const agreementService = new AgreementPoolService(yagnaApi, { logger, agreementMaxPoolSize: 1 }); - await agreementService.run(); - await agreementService.addProposal(createProposal("proposal-id-1")); - await agreementService.addProposal(createProposal("proposal-id-2")); - const agreement1 = await agreementService.getAgreement(); - const agreement2 = await agreementService.getAgreement(); - await agreementService.releaseAgreement(agreement1.id, true); - await agreementService.releaseAgreement(agreement2.id, true); - await logger.expectToInclude(`Agreement has been released for reuse`, { id: agreement1.id }); - await logger.expectToInclude(`Agreement cannot return to the pool because the pool is already full`, { - id: agreement2.id, - }); - await logger.expectToInclude(`Agreement has been released and will be terminated`, { id: agreement2.id }); - }); - }); - - describe("addProposal()", () => { - it("should add proposal to pool", async () => { - const agreementService = new AgreementPoolService(yagnaApi, { logger }); - await agreementService.run(); - - await agreementService.addProposal(createProposal("proposal-id")); - expect(logger.logs).toMatch(/New proposal added to pool .*/); - - await agreementService.end(); - }); - }); -}); diff --git a/tests/unit/allocation.test.ts b/tests/unit/allocation.test.ts deleted file mode 100644 index b0636979e..000000000 --- a/tests/unit/allocation.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { LoggerMock, YagnaMock } from "../mock"; -import { Allocation, GolemPaymentError, PaymentErrorCode } from "../../src/payment"; -import { GolemConfigError, GolemUserError } from "../../src/error/golem-error"; -import { AllocationOptions } from "../../src/payment/allocation"; - -const logger = new LoggerMock(); -const account = { address: "test_address", platform: "test_platform" }; -const yagnaApi = new YagnaMock().getApi(); - -describe("Allocation", () => { - beforeEach(() => logger.clear()); - - describe("Creating", () => { - it("should create allocation", async () => { - const allocation = await Allocation.create(yagnaApi, { account }); - expect(allocation).toBeInstanceOf(Allocation); - }); - - it("should not create allocation without account options", async () => { - const expectedPreviousError = new GolemConfigError("Account option is required"); - await expect(Allocation.create(yagnaApi, {} as AllocationOptions)).rejects.toMatchError( - new GolemPaymentError( - `Could not create new allocation. ${expectedPreviousError}`, - PaymentErrorCode.AllocationCreationFailed, - undefined, - undefined, - expectedPreviousError, - ), - ); - }); - - it("should not create allocation with empty account parameters", async () => { - const expectedPreviousError = new GolemConfigError("Account address and payment platform are required"); - await expect(Allocation.create(yagnaApi, { account: { address: "", platform: "" } })).rejects.toMatchError( - new GolemPaymentError( - `Could not create new allocation. ${expectedPreviousError}`, - PaymentErrorCode.AllocationCreationFailed, - undefined, - undefined, - expectedPreviousError, - ), - ); - }); - }); -}); diff --git a/tests/unit/decorations_builder.test.ts b/tests/unit/decorations_builder.test.ts deleted file mode 100644 index da27299ed..000000000 --- a/tests/unit/decorations_builder.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { ComparisonOperator, DecorationsBuilder } from "../../src/market/builder"; -import { GolemInternalError } from "../../src/error/golem-error"; - -describe("#DecorationsBuilder()", () => { - describe("addProperty()", () => { - it("should allow to add property", () => { - const decorationsBuilder = new DecorationsBuilder(); - decorationsBuilder.addProperty("key", "value"); - expect(decorationsBuilder.getDecorations().properties.length).toEqual(1); - }); - it("should replace already existing property", () => { - const decorationsBuilder = new DecorationsBuilder(); - decorationsBuilder.addProperty("key", "value").addProperty("key", "value2"); - expect(decorationsBuilder.getDecorations().properties.length).toEqual(1); - expect(decorationsBuilder.getDecorations().properties[0].value).toEqual("value2"); - }); - it("should provide fluent API", () => { - const decorationsBuilder = new DecorationsBuilder(); - const flAPI = decorationsBuilder.addProperty("key", "value"); - expect(flAPI).toBeInstanceOf(DecorationsBuilder); - }); - }); - describe("addConstraint()", () => { - it("should allow to add constrain", () => { - const decorationsBuilder = new DecorationsBuilder(); - decorationsBuilder.addConstraint("key", "value"); - expect(decorationsBuilder.getDecorations().constraints.length).toEqual(1); - }); - it("should allow to add constrain with >=", () => { - const decorationsBuilder = new DecorationsBuilder(); - decorationsBuilder.addConstraint("key", "value", ComparisonOperator.GtEq); - expect(decorationsBuilder.getDecorations().constraints.length).toEqual(1); - }); - it("should allow to add constrain with <=", () => { - const decorationsBuilder = new DecorationsBuilder(); - decorationsBuilder.addConstraint("key", "value", ComparisonOperator.LtEq); - expect(decorationsBuilder.getDecorations().constraints.length).toEqual(1); - }); - it("should allow to add constrain with >", () => { - const decorationsBuilder = new DecorationsBuilder(); - decorationsBuilder.addConstraint("key", "value", ComparisonOperator.Gt); - expect(decorationsBuilder.getDecorations().constraints.length).toEqual(1); - }); - it("should allow to add constrain with <", () => { - const decorationsBuilder = new DecorationsBuilder(); - decorationsBuilder.addConstraint("key", "value", ComparisonOperator.Lt); - expect(decorationsBuilder.getDecorations().constraints.length).toEqual(1); - }); - it("should allow to add constrain with =", () => { - const decorationsBuilder = new DecorationsBuilder(); - decorationsBuilder.addConstraint("key", "value", ComparisonOperator.Eq); - expect(decorationsBuilder.getDecorations().constraints.length).toEqual(1); - }); - it("should provide fluent API", () => { - const decorationsBuilder = new DecorationsBuilder(); - const flAPI = decorationsBuilder.addConstraint("key", "value"); - expect(flAPI).toBeInstanceOf(DecorationsBuilder); - }); - }); - describe("addDecorations()", () => { - it("should allow to parse constrain with =, >=, <=, >, <", async () => { - const decoration = { - constraints: [ - "some_constraint=some_value", - "some_constraint>=some_value", - "some_constraint<=some_value", - "some_constraint>some_value", - "some_constraint { - const decoration = { - properties: [{ key: "prop_key", value: "value" }], - constraints: ["some_constraint=some_value"], - }; - const decorationsBuilder = new DecorationsBuilder(); - decorationsBuilder.addDecoration(decoration); - expect(decorationsBuilder.getDecorations().constraints.length).toEqual(1); - expect(decorationsBuilder.getDecorations().properties.length).toEqual(1); - }); - it("should provide fluent API", () => { - const decoration = { - properties: [{ key: "prop_key", value: "value" }], - constraints: ["some_constraint=some_value"], - }; - const decorationsBuilder = new DecorationsBuilder(); - const flAPI = decorationsBuilder.addDecoration(decoration); - expect(flAPI).toBeInstanceOf(DecorationsBuilder); - }); - - it("should not allow to add invalid decorations", () => { - const decoration = { - properties: [{ key: "prop_key", value: "value" }], - constraints: ["some_invalid_constraint"], - }; - const decorationsBuilder = new DecorationsBuilder(); - expect(() => decorationsBuilder.addDecoration(decoration)).toThrow( - new GolemInternalError('Unable to parse constraint "some_invalid_constraint"'), - ); - }); - }); - describe("getDecorations()", () => { - it("should return correct decoration", () => { - const decorationsBuilder = new DecorationsBuilder(); - decorationsBuilder - .addConstraint("key", "value", ComparisonOperator.Eq) - .addConstraint("key", "value", ComparisonOperator.GtEq) - .addConstraint("key", "value", ComparisonOperator.LtEq) - .addConstraint("key", "value", ComparisonOperator.Gt) - .addConstraint("key", "value", ComparisonOperator.Lt) - .addProperty("key", "value") - .addProperty("key2", "value"); - - expect(decorationsBuilder.getDecorations().constraints.length).toEqual(5); - expect(decorationsBuilder.getDecorations().properties.length).toEqual(2); - - expect(decorationsBuilder.getDecorations().constraints).toEqual([ - "(key=value)", - "(key>=value)", - "(key<=value)", - "(key>value)", - "(key { - describe("Creating", () => { - it("should create and publish demand", async () => { - const demand = await Demand.create(packageMock, allocationMock, yagnaApi, { subnetTag, logger }); - expect(demand).toBeInstanceOf(Demand); - expect(logger.logs).toContain("Demand published on the market"); - await demand.unsubscribe(); - }); - }); - describe("Processing", () => { - it("should get proposal after publish demand", async () => { - const demand = await Demand.create(packageMock, allocationMock, yagnaApi, { subnetTag }); - setExpectedProposals(proposalsInitial); - const event: DemandEvent = await new Promise((res) => - demand.addEventListener(DEMAND_EVENT_TYPE, (e) => res(e as DemandEvent)), - ); - expect(event.proposal).toBeInstanceOf(Proposal); - await demand.unsubscribe(); - }); - }); - describe("Error handling", () => { - it("should throw market error if demand cannot be created", async () => { - const spySubscribe = spy(yagnaApi.market); - const testError = new Error("Test error"); - when(spySubscribe.subscribeDemand(anything())).thenThrow(testError); - await expect(Demand.create(packageMock, allocationMock, yagnaApi)).rejects.toMatchError( - new GolemMarketError( - `Could not publish demand on the market. Error: Test error`, - MarketErrorCode.SubscriptionFailed, - undefined, - testError, - ), - ); - }); - it("should throw user error if expiration option is invalid", async () => { - await expect(Demand.create(packageMock, allocationMock, yagnaApi, { expirationSec: -3 })).rejects.toMatchError( - new GolemConfigError("The demand expiration time has to be a positive integer"), - ); - }); - it("should throw user error if debitNotesAcceptanceTimeoutSec option is invalid", async () => { - await expect( - Demand.create(packageMock, allocationMock, yagnaApi, { debitNotesAcceptanceTimeoutSec: -3 }), - ).rejects.toMatchError( - new GolemConfigError("The debit note acceptance timeout time has to be a positive integer"), - ); - }); - it("should throw user error if midAgreementDebitNoteIntervalSec option is invalid", async () => { - await expect( - Demand.create(packageMock, allocationMock, yagnaApi, { midAgreementDebitNoteIntervalSec: -3 }), - ).rejects.toMatchError(new GolemConfigError("The debit note interval time has to be a positive integer")); - }); - it("should throw user error if midAgreementPaymentTimeoutSec option is invalid", async () => { - await expect( - Demand.create(packageMock, allocationMock, yagnaApi, { midAgreementPaymentTimeoutSec: -3 }), - ).rejects.toMatchError( - new GolemConfigError("The mid-agreement payment timeout time has to be a positive integer"), - ); - }); - }); -}); diff --git a/tests/unit/market_service.test.ts b/tests/unit/market_service.test.ts deleted file mode 100644 index ab7ccceb5..000000000 --- a/tests/unit/market_service.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { setExpectedProposals } from "../mock/rest/market"; -import { MarketService, ProposalFilterFactory } from "../../src/market"; -import { agreementPoolServiceMock, packageMock, LoggerMock, allocationMock, YagnaMock } from "../mock"; -import { - proposalsInitial, - proposalsDraft, - proposalsWrongPaymentPlatform, - proposalsShortDebitNoteTimeout, -} from "../mock/fixtures"; - -const logger = new LoggerMock(); -const yagnaApi = new YagnaMock().getApi(); - -describe("Market Service", () => { - beforeEach(() => { - logger.clear(); - }); - - it("should start service and publish demand", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger }); - await marketService.run(packageMock, allocationMock); - expect(logger.logs).toContain("Market Service has started"); - expect(logger.logs).toContain("Demand published on the market"); - await marketService.end(); - expect(logger.logs).toContain("Market Service has been stopped"); - }); - - it("should respond initial proposal", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { - logger, - minProposalsBatchSize: 1, - proposalsBatchReleaseTimeoutMs: 10, - }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals(proposalsInitial); - await logger.expectToInclude("Proposal has been responded", { id: expect.anything() }, 10); - await marketService.end(); - }); - - it("should add draft proposal to agreement pool", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals(proposalsDraft); - await logger.expectToInclude("Proposal has been confirmed and added to agreement pool", expect.anything(), 10); - const addedProposalsIds = agreementPoolServiceMock["getProposals"]().map((p) => p.id); - expect(addedProposalsIds).toEqual(expect.arrayContaining(proposalsDraft.map((p) => p.proposal.proposalId))); - await marketService.end(); - }); - - it("should reject initial proposal without common payment platform", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { - logger, - minProposalsBatchSize: 1, - proposalsBatchReleaseTimeoutMs: 10, - }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals([proposalsInitial[6]]); - await logger.expectToInclude( - "Proposal has been rejected", - { - reason: "No common payment platform", - id: expect.anything(), - }, - 10, - ); - await marketService.end(); - }); - - it("should reject when no common payment platform", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { - logger, - minProposalsBatchSize: 1, - proposalsBatchReleaseTimeoutMs: 10, - }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals(proposalsWrongPaymentPlatform); - await logger.expectToMatch(/No common payment platform/, 10); - await marketService.end(); - }); - it("should reject initial proposal when debit note acceptance timeout too short", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { - logger, - minProposalsBatchSize: 1, - proposalsBatchReleaseTimeoutMs: 10, - }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals(proposalsShortDebitNoteTimeout); - await logger.expectToMatch(/Debit note acceptance timeout too short/, 10); - await marketService.end(); - }); - it("should reject when proposal rejected by Proposal Filter", async () => { - const proposalAlwaysBanFilter = () => false; - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { - logger, - proposalFilter: proposalAlwaysBanFilter, - minProposalsBatchSize: 1, - proposalsBatchReleaseTimeoutMs: 10, - }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals(proposalsInitial); - await logger.expectToMatch(/Proposal rejected by Proposal Filter/, 10); - await marketService.end(); - }); - it("should reject when proposal rejected by BlackListIds Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { - logger, - proposalFilter: ProposalFilterFactory.disallowProvidersById(["0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655"]), - minProposalsBatchSize: 1, - proposalsBatchReleaseTimeoutMs: 10, - }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals(proposalsInitial); - await logger.expectToMatch(/Proposal rejected by Proposal Filter/, 10); - await marketService.end(); - }); - it("should reject when proposal rejected by BlackListNames Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { - logger, - proposalFilter: ProposalFilterFactory.disallowProvidersByNameRegex(/golem2004/), - minProposalsBatchSize: 1, - proposalsBatchReleaseTimeoutMs: 10, - }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals(proposalsInitial); - await logger.expectToMatch(/Proposal rejected by Proposal Filter/, 10); - await marketService.end(); - }); - it("should reject when proposal rejected by WhiteListIds Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { - logger, - proposalFilter: ProposalFilterFactory.allowProvidersById(["0x123455"]), - minProposalsBatchSize: 1, - proposalsBatchReleaseTimeoutMs: 10, - }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals(proposalsInitial); - await logger.expectToMatch(/Proposal rejected by Proposal Filter/, 10); - await marketService.end(); - }); - it("should reject when proposal rejected by WhiteListNames Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { - logger, - proposalFilter: ProposalFilterFactory.allowProvidersByNameRegex(/abcdefg/), - minProposalsBatchSize: 1, - proposalsBatchReleaseTimeoutMs: 10, - }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals(proposalsInitial); - await logger.expectToMatch(/Proposal rejected by Proposal Filter/, 10); - await marketService.end(); - }); - it("should respond when provider id is whitelisted by WhiteListIds Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { - logger, - proposalFilter: ProposalFilterFactory.allowProvidersById(["0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655"]), - minProposalsBatchSize: 1, - proposalsBatchReleaseTimeoutMs: 10, - }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals(proposalsInitial); - await logger.expectToMatch(/Proposal has been responded/, 10); - await marketService.end(); - }); - it("should respond when provider name is whitelisted by WhiteListNames Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { - logger, - proposalFilter: ProposalFilterFactory.allowProvidersByNameRegex(/golem2004/), - minProposalsBatchSize: 1, - proposalsBatchReleaseTimeoutMs: 10, - }); - await marketService.run(packageMock, allocationMock); - setExpectedProposals(proposalsInitial); - await logger.expectToMatch(/Proposal has been responded/, 10); - await marketService.end(); - }); -}); diff --git a/tests/unit/network.test.ts b/tests/unit/network.test.ts deleted file mode 100644 index 96bbc422b..000000000 --- a/tests/unit/network.test.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { GolemNetworkError, Network } from "../../src/network"; -import { YagnaMock } from "../mock"; -import { NetworkErrorCode } from "../../src/network/error"; - -const yagnaApi = new YagnaMock().getApi(); - -describe("Network", () => { - describe("Creating", () => { - it("should create network", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "test_owner_id" }); - const { ip, mask, nodes } = network.getNetworkInfo(); - expect(nodes["192.168.0.1"]).toEqual("test_owner_id"); - expect(Object.keys(nodes).length).toEqual(1); - expect(ip).toEqual("192.168.0.0"); - expect(mask).toEqual("255.255.255.0"); - }); - - it("should create network with 16 bit mask", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.7.0/16" }); - const { ip, mask } = network.getNetworkInfo(); - expect({ ip, mask }).toEqual({ ip: "192.168.0.0", mask: "255.255.0.0" }); - }); - - it("should create network with 24 bit mask", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.7.0/24" }); - const { ip, mask } = network.getNetworkInfo(); - expect({ ip, mask }).toEqual({ ip: "192.168.7.0", mask: "255.255.255.0" }); - }); - - it("should create network with 8 bit mask", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.7.0/8" }); - const { ip, mask } = network.getNetworkInfo(); - expect({ ip, mask }).toEqual({ ip: "192.0.0.0", mask: "255.0.0.0" }); - }); - - it("should not create network with invalid ip", async () => { - const shouldFail = Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "123.1.2" }); - await expect(shouldFail).rejects.toMatchError( - new GolemNetworkError( - "Unable to create network. Error: Cidr notation should be in the form [ip number]/[range]", - NetworkErrorCode.NetworkCreationFailed, - undefined, - new Error("Cidr notation should be in the form [ip number]/[range]"), - ), - ); - }); - - it("should not create network without mask", async () => { - const shouldFail = Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "1.1.1.1" }); - await expect(shouldFail).rejects.toMatchError( - new GolemNetworkError( - "Unable to create network. Error: Cidr notation should be in the form [ip number]/[range]", - NetworkErrorCode.NetworkCreationFailed, - undefined, - new Error("Cidr notation should be in the form [ip number]/[range]"), - ), - ); - }); - - it("should create network with custom options", async () => { - const network = await Network.create(yagnaApi, { - networkIp: "192.168.0.1", - networkOwnerId: "owner_1", - networkOwnerIp: "192.168.0.7", - networkMask: "27", - networkGateway: "192.168.0.2", - }); - const { ip, mask, nodes } = network.getNetworkInfo(); - expect({ ip, mask }).toEqual({ ip: "192.168.0.0", mask: "255.255.255.224" }); - expect(nodes["192.168.0.7"]).toEqual("owner_1"); - }); - }); - - describe("Nodes", () => { - it("should add node", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - const { id, ip } = await network.addNode("7", "192.168.0.7"); - expect({ id, ip: ip.toString() }).toEqual({ id: "7", ip: "192.168.0.7" }); - }); - - it("should add a few nodes", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - const node2 = await network.addNode("2", "192.168.0.3"); - const node3 = await network.addNode("3"); - const node4 = await network.addNode("4"); - expect({ id: node2.id, ip: node2.ip.toString() }).toEqual({ id: "2", ip: "192.168.0.3" }); - expect({ id: node3.id, ip: node3.ip.toString() }).toEqual({ id: "3", ip: "192.168.0.2" }); - expect({ id: node4.id, ip: node4.ip.toString() }).toEqual({ id: "4", ip: "192.168.0.4" }); - }); - - it("should not add node with an existing ID", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - await expect(network.addNode("1")).rejects.toMatchError( - new GolemNetworkError( - "Network ID '1' has already been assigned in this network.", - NetworkErrorCode.AddressAlreadyAssigned, - network.getNetworkInfo(), - ), - ); - }); - - it("should not add node with an existing IP", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - await network.addNode("2", "192.168.0.3"); - await expect(network.addNode("3", "192.168.0.3")).rejects.toMatchError( - new GolemNetworkError( - "IP '192.168.0.3' has already been assigned in this network.", - NetworkErrorCode.AddressAlreadyAssigned, - network.getNetworkInfo(), - ), - ); - }); - - it("should not add node with address outside the network range", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - await expect(network.addNode("2", "192.168.2.2")).rejects.toMatchError( - new GolemNetworkError( - "The given IP ('192.168.2.2') address must belong to the network ('192.168.0.0/24').", - NetworkErrorCode.AddressOutOfRange, - network.getNetworkInfo(), - ), - ); - }); - - it("should not add too many nodes", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/30" }); - await network.addNode("2"); - await network.addNode("3"); - await expect(network.addNode("4")).rejects.toMatchError( - new GolemNetworkError( - "No more addresses available in 192.168.0.0/30", - NetworkErrorCode.NoAddressesAvailable, - network.getNetworkInfo(), - ), - ); - }); - - it("should throw an error when there are no free IPs available", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/30" }); - await network.addNode("2"); - await network.addNode("3"); - await network.removeNode("2"); - await expect(network.addNode("4")).rejects.toMatchError( - new GolemNetworkError( - "No more addresses available in 192.168.0.0/30", - NetworkErrorCode.NoAddressesAvailable, - network.getNetworkInfo(), - ), - ); - }); - - it("should return true if node belongs to the network", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/30" }); - await network.addNode("2"); - expect(network.hasNode("2")).toEqual(true); - }); - - it("should return false if node does not belong to the network", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/30" }); - await network.addNode("2"); - expect(network.hasNode("77")).toEqual(false); - }); - - it("should get node network config", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - const node = await network.addNode("2"); - expect(node.getNetworkConfig()).toEqual({ - net: [ - { - id: network.id, - ip: "192.168.0.0", - mask: "255.255.255.0", - nodeIp: "192.168.0.2", - nodes: { - "192.168.0.1": "1", - "192.168.0.2": "2", - }, - }, - ], - }); - }); - - it("should get node websocket uri", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - const node = await network.addNode("2"); - expect(node.getWebsocketUri(22)).toEqual( - `ws://${process.env?.YAGNA_API_URL?.substring(7) || "localhost"}/net-api/v1/net/${ - network.id - }/tcp/192.168.0.2/22`, - ); - }); - - it("should remove node from the network", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - const node = await network.addNode("7"); - const removeNetworkApiSpy = jest.spyOn(yagnaApi.net, "removeNode"); - await network.removeNode(node.id); - expect(removeNetworkApiSpy).toHaveBeenCalledWith(network.id, node.id); - }); - - it("should not remove node from the network if it does not exist", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - await network.addNode("7"); - await expect(network.removeNode("88")).rejects.toMatchError( - new GolemNetworkError( - "Unable to remove node 88. There is no such node in the network", - NetworkErrorCode.NodeRemovalFailed, - network.getNetworkInfo(), - ), - ); - }); - }); - - describe("Removing", () => { - it("should remove network", async () => { - const spyRemove = jest.spyOn(yagnaApi.net, "removeNetwork"); - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - await network.remove(); - expect(spyRemove).toHaveBeenCalled(); - }); - - it("should not remove network that doesn't exist", async () => { - const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - network["yagnaApi"]["net"]["setExpectedError"](new Error("404")); - await expect(network.remove()).rejects.toMatchError( - new GolemNetworkError( - `Unable to remove network. Error: 404`, - NetworkErrorCode.NetworkRemovalFailed, - network.getNetworkInfo(), - new Error("404"), - ), - ); - }); - }); -}); diff --git a/tests/unit/network_service.test.ts b/tests/unit/network_service.test.ts deleted file mode 100644 index 26fd7feab..000000000 --- a/tests/unit/network_service.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { LoggerMock, YagnaMock } from "../mock"; -import { GolemNetworkError, NetworkService } from "../../src/network"; -import { NetworkErrorCode } from "../../src/network/error"; - -const logger = new LoggerMock(); -const yagnaApi = new YagnaMock().getApi(); -describe("Network Service", () => { - beforeEach(() => { - logger.clear(); - }); - - describe("Creating", () => { - it("should start service and create network", async () => { - const networkService = new NetworkService(yagnaApi, { logger }); - await networkService.run("test_owner_id"); - await logger.expectToInclude( - "Network created", - { - id: expect.anything(), - ip: "192.168.0.0", - mask: "255.255.255.0", - }, - 10, - ); - await logger.expectToInclude("Network Service has started"); - await networkService.end(); - }); - }); - - describe("Nodes", () => { - describe("adding", () => { - it("should add node to network", async () => { - const networkService = new NetworkService(yagnaApi, { logger }); - await networkService.run("test_owner_id"); - await networkService.addNode("provider_2"); - await logger.expectToInclude( - "Node has added to the network.", - { - id: "provider_2", - ip: "192.168.0.2", - }, - 10, - ); - await networkService.end(); - }); - - it("should not add node if the service is not started", async () => { - const networkService = new NetworkService(yagnaApi, { logger }); - const result = networkService.addNode("provider_2"); - await expect(result).rejects.toMatchError( - new GolemNetworkError( - "The service is not started and the network does not exist", - NetworkErrorCode.NetworkSetupMissing, - ), - ); - }); - describe("removing", () => { - it("should remove node from the network", async () => { - const networkService = new NetworkService(yagnaApi, { logger }); - await networkService.run("test_owner_id"); - await networkService.addNode("provider_2"); - const removeNetworkApiSpy = jest.spyOn(yagnaApi.net, "removeNode"); - await networkService.removeNode("provider_2"); - expect(removeNetworkApiSpy).toHaveBeenCalled(); - await networkService.end(); - }); - it("should not remove node from the network", async () => { - const networkService = new NetworkService(yagnaApi, { logger }); - await networkService.run("test_owner_id"); - await networkService.addNode("provider_2"); - await expect(networkService.removeNode("provider_777")).rejects.toMatchError( - new GolemNetworkError( - "Unable to remove node provider_777. There is no such node in the network", - NetworkErrorCode.NodeRemovalFailed, - networkService["network"]?.getNetworkInfo(), - ), - ); - await networkService.end(); - }); - }); - }); - - describe("Removing", () => { - it("should end service and remove network", async () => { - const networkService = new NetworkService(yagnaApi, { logger }); - await networkService.run("test_owner_id"); - await networkService.end(); - await logger.expectToInclude( - "Network has removed:", - { - id: expect.anything(), - ip: expect.anything(), - }, - 60, - ); - await logger.expectToInclude("Network Service has been stopped"); - await networkService.end(); - }); - }); - }); -}); diff --git a/tests/unit/package.test.ts b/tests/unit/package.test.ts deleted file mode 100644 index d7c245d69..000000000 --- a/tests/unit/package.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { LoggerMock } from "../mock"; -import { Package } from "../../src"; -const logger = new LoggerMock(); - -describe("Package", () => { - describe("create()", () => { - it("should create package", async () => { - const p = await Package.create({ imageHash: "image_hash", logger }); - expect(p).toBeInstanceOf(Package); - }); - it("should return decorators with task_package and package_format", async () => { - // Due to missing mocking and DI approach this tests is not mocked - // and makes real request to the registry - // ? Shouldnt we avoid this - - const p = await Package.create({ - imageHash: "529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - }); - - const decorations = await p.getDemandDecoration(); - - expect(decorations.properties).toEqual( - expect.arrayContaining([ - { - key: "golem.srv.comp.task_package", - value: - "hash:sha3:529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4:http://registry.golem.network/download/529f7fdaf1cf46ce3126eb6bbcd3b213c314fe8fe884914f5d1106d4", - }, - { key: "golem.srv.comp.vm.package_format", value: "gvmkit-squash" }, - ]), - ); - }); - it("should create package with manifest decorations", async () => { - const manifest = "XNBdCI6ICIyMTAwLTAxLTAxVDAwOjAxOjAwLjAwMDAwMFoiLAogICJtZXRhZGF0YSI6IHsKICAgICJuYW1lI="; - const manifestSig = "GzbdJDaW6FTajVYCKKZZvwpwVNBK3o40r/okna87wV9CVWW0+WUFwe="; - const manifestCert = "HCkExVUVDZ3dOUjI5c1pXMGdSbUZqZEc5eWVURW1NQ1FHQTFVRUF3d2RSMjlzWl="; - const manifestSigAlgorithm = "sha256"; - const capabilities = ["inet", "manifest-support"]; - const p = await Package.create({ - manifest, - manifestSig, - manifestCert, - manifestSigAlgorithm, - capabilities, - }); - const decorations = await p.getDemandDecoration(); - expect(decorations.properties).toEqual( - expect.arrayContaining([ - { key: "golem.srv.comp.payload", value: manifest }, - { key: "golem.srv.comp.payload.sig", value: manifestSig }, - { key: "golem.srv.comp.payload.cert", value: manifestCert }, - { key: "golem.srv.comp.payload.sig.algorithm", value: manifestSigAlgorithm }, - { key: "golem.srv.comp.vm.package_format", value: "gvmkit-squash" }, - ]), - ); - expect(decorations.constraints).toEqual([ - "(golem.inf.mem.gib>=0.5)", - "(golem.inf.storage.gib>=2)", - "(golem.runtime.name=vm)", - "(golem.inf.cpu.cores>=1)", - "(golem.inf.cpu.threads>=1)", - "(golem.runtime.capabilities=inet)", - "(golem.runtime.capabilities=manifest-support)", - ]); - }); - }); -}); diff --git a/tests/unit/payment_service.test.ts b/tests/unit/payment_service.test.ts deleted file mode 100644 index e39222175..000000000 --- a/tests/unit/payment_service.test.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { clear, setExpectedDebitNotes, setExpectedEvents, setExpectedInvoices } from "../mock/rest/payment"; -import { LoggerMock, YagnaMock } from "../mock"; -import { Allocation, GolemPaymentError, PaymentErrorCode, PaymentFilters, PaymentService } from "../../src/payment"; -import { agreement } from "../mock/entities/agreement"; -import { debitNotes, debitNotesEvents, invoiceEvents, invoices } from "../mock/fixtures"; -import { anything, reset, spy, when } from "@johanblumenberg/ts-mockito"; -import { GolemUserError } from "../../src"; - -const logger = new LoggerMock(); -const yagnaApi = new YagnaMock().getApi(); - -/** - * service.end() waits for invoices to be paid, in unit-tests that should be below 5s - */ -const TEST_PAYMENT_TIMEOUT_MS = 1000; - -describe("Payment Service", () => { - beforeEach(() => { - logger.clear(); - clear(); - }); - - describe("Allocations", () => { - it("should create allocation", async () => { - const paymentService = new PaymentService(yagnaApi); - const allocation = await paymentService.createAllocation(); - expect(allocation).toBeInstanceOf(Allocation); - await paymentService.end(); - }); - - it("should release created allocation when service stopped", async () => { - const paymentService = new PaymentService(yagnaApi, { logger }); - const allocation = await paymentService.createAllocation(); - const releaseSpy = jest.spyOn(allocation, "release"); - await paymentService.end(); - expect(releaseSpy).toHaveBeenCalled(); - }); - - it("should throw GolemPaymentError if allocation cannot be created", async () => { - const paymentApiSpy = spy(yagnaApi.payment); - const errorYagnaApiMock = new Error("test error"); - when(paymentApiSpy.createAllocation(anything())).thenReject(errorYagnaApiMock); - const paymentService = new PaymentService(yagnaApi, { logger }); - await expect(paymentService.createAllocation()).rejects.toMatchError( - new GolemPaymentError( - `Could not create new allocation. ${errorYagnaApiMock}`, - PaymentErrorCode.AllocationCreationFailed, - undefined, - undefined, - errorYagnaApiMock, - ), - ); - reset(paymentApiSpy); - }); - }); - - describe("Processing payments", () => { - it("should accept and process invoice for agreement", async () => { - const paymentService = new PaymentService(yagnaApi, { - logger, - paymentTimeout: TEST_PAYMENT_TIMEOUT_MS, - }); - setExpectedEvents(invoiceEvents); - setExpectedInvoices(invoices); - await paymentService.createAllocation(); - await paymentService.run(); - paymentService.acceptPayments(agreement); - - await logger.expectToInclude( - `Invoice has been accepted`, - { - invoiceId: invoices[0].invoiceId, - agreementId: agreement.id, - providerName: agreement.getProviderInfo().name, - }, - 1_000, - ); - await paymentService.end(); - }); - - it("should accept and process debit note for agreement", async () => { - const paymentService = new PaymentService(yagnaApi, { - logger, - paymentTimeout: TEST_PAYMENT_TIMEOUT_MS, - }); - setExpectedEvents(debitNotesEvents); - setExpectedDebitNotes(debitNotes); - await paymentService.createAllocation(); - paymentService.acceptPayments(agreement); - await paymentService.run(); - await logger.expectToInclude( - `DebitNote accepted`, - { - debitNoteId: debitNotes[0].debitNoteId, - agreementId: agreement.id, - }, - 1_000, - ); - await paymentService.end(); - }); - - it("should reject when debit note rejected by DebitNote Filter", async () => { - const alwaysRejectDebitNoteFilter = async () => false; - const paymentService = new PaymentService(yagnaApi, { - logger, - debitNotesFilter: alwaysRejectDebitNoteFilter, - paymentTimeout: TEST_PAYMENT_TIMEOUT_MS, - }); - setExpectedEvents(debitNotesEvents); - setExpectedDebitNotes(debitNotes); - await paymentService.createAllocation(); - paymentService.acceptPayments(agreement); - await paymentService.run(); - await logger.expectToInclude( - `DebitNote rejected`, - { - reason: `DebitNote ${debitNotes[0].debitNoteId} for agreement ${agreement.id} rejected by DebitNote Filter`, - }, - 100, - ); - await paymentService.end(); - }); - - it("should reject a debit note when the agreement is already covered with a final invoice", async () => { - const paymentService = new PaymentService(yagnaApi, { - logger, - }); - - setExpectedEvents([...invoiceEvents, ...debitNotesEvents]); - setExpectedDebitNotes(debitNotes); - setExpectedInvoices(invoices); - await paymentService.createAllocation(); - paymentService.acceptPayments(agreement); - await paymentService.run(); - await logger.expectToInclude( - `DebitNote rejected`, - { - reason: `DebitNote ${debitNotes[0].debitNoteId} rejected because the agreement ${agreement.id} is already covered with a final invoice that should be paid instead of the debit note`, - }, - 100, - ); - await paymentService.end(); - }); - - it("should reject when invoice rejected by Invoice Filter", async () => { - const alwaysRejectInvoiceFilter = async () => false; - const paymentService = new PaymentService(yagnaApi, { - logger, - invoiceFilter: alwaysRejectInvoiceFilter, - paymentTimeout: TEST_PAYMENT_TIMEOUT_MS, - }); - setExpectedEvents(invoiceEvents); - setExpectedInvoices(invoices); - await paymentService.createAllocation(); - paymentService.acceptPayments(agreement); - await paymentService.run(); - await logger.expectToInclude( - `Invoice rejected`, - { reason: `Invoice ${invoices[0].invoiceId} for agreement ${agreement.id} rejected by Invoice Filter` }, - 1_000, - ); - await paymentService.end(); - }); - - it("should reject when debit note rejected by DebitNoteMaxAmount Filter", async () => { - const paymentService = new PaymentService(yagnaApi, { - logger, - debitNotesFilter: PaymentFilters.acceptMaxAmountDebitNoteFilter(0.00001), - paymentTimeout: TEST_PAYMENT_TIMEOUT_MS, - }); - setExpectedEvents(debitNotesEvents); - setExpectedDebitNotes(debitNotes); - await paymentService.createAllocation(); - paymentService.acceptPayments(agreement); - await paymentService.run(); - await logger.expectToInclude( - `DebitNote rejected`, - { - reason: `DebitNote ${debitNotes[0].debitNoteId} for agreement ${agreement.id} rejected by DebitNote Filter`, - }, - 100, - ); - await paymentService.end(); - }); - - it("should reject when invoice rejected by MaxAmountInvoice Filter", async () => { - const paymentService = new PaymentService(yagnaApi, { - logger, - invoiceFilter: PaymentFilters.acceptMaxAmountInvoiceFilter(0.00001), - paymentTimeout: TEST_PAYMENT_TIMEOUT_MS, - }); - setExpectedEvents(invoiceEvents); - setExpectedInvoices(invoices); - await paymentService.createAllocation(); - paymentService.acceptPayments(agreement); - await paymentService.run(); - - await logger.expectToInclude( - `Invoice rejected`, - { - reason: `Invoice ${invoices[0].invoiceId} for agreement ${agreement.id} rejected by Invoice Filter`, - }, - 100, - ); - await paymentService.end(); - }); - - it("should accept when debit note filtered by DebitNoteMaxAmount Filter", async () => { - const paymentService = new PaymentService(yagnaApi, { - logger, - debitNotesFilter: PaymentFilters.acceptMaxAmountDebitNoteFilter(7), - paymentTimeout: TEST_PAYMENT_TIMEOUT_MS, - }); - setExpectedEvents(debitNotesEvents); - setExpectedDebitNotes(debitNotes); - await paymentService.createAllocation(); - paymentService.acceptPayments(agreement); - await paymentService.run(); - await logger.expectToInclude( - `DebitNote accepted`, - { - debitNoteId: debitNotes[0].debitNoteId, - agreementId: agreement.id, - }, - 1_000, - ); - await paymentService.end(); - }); - - it("should accept when invoice filtered by MaxAmountInvoice Filter", async () => { - const paymentService = new PaymentService(yagnaApi, { - logger, - invoiceFilter: PaymentFilters.acceptMaxAmountInvoiceFilter(7), - }); - setExpectedEvents(invoiceEvents); - setExpectedInvoices(invoices); - await paymentService.createAllocation(); - await paymentService.run(); - paymentService.acceptPayments(agreement); - - await logger.expectToInclude( - `Invoice has been accepted`, - { - invoiceId: invoices[0].invoiceId, - agreementId: agreement.id, - providerName: agreement.getProviderInfo().name, - }, - 1_000, - ); - await paymentService.end(); - }); - - describe("emitting 'error' event", () => { - it("should emit when there's an issue with processing the debit note", async () => { - // Given - const error = new Error("Broken debit note filter"); - - const paymentService = new PaymentService(yagnaApi, { - logger, - debitNotesFilter: () => { - throw error; - }, - paymentTimeout: TEST_PAYMENT_TIMEOUT_MS, - }); - - setExpectedEvents(debitNotesEvents); - setExpectedDebitNotes(debitNotes); - - const handler = jest.fn(); - paymentService.events.once("error", handler); - - // When - await paymentService.createAllocation(); - paymentService.acceptPayments(agreement); - await paymentService.run(); - await paymentService.end(); - - // Then - expect(handler).toHaveBeenCalledWith(new GolemUserError("An error occurred in the debit note filter", error)); - }); - - it("should emit an error event when there's an issue with processing the invoice", async () => { - // Given - const error = new Error("Broken invoice filter"); - - const paymentService = new PaymentService(yagnaApi, { - logger, - invoiceFilter: () => { - throw error; - }, - paymentTimeout: TEST_PAYMENT_TIMEOUT_MS, - }); - - setExpectedEvents(invoiceEvents); - setExpectedInvoices(invoices); - - const handler = jest.fn(); - paymentService.events.once("error", handler); - - // When - await paymentService.createAllocation(); - paymentService.acceptPayments(agreement); - await paymentService.run(); - await paymentService.end(); - - // Then - expect(handler).toHaveBeenCalledWith(new GolemUserError("An error occurred in the invoice filter", error)); - }); - }); - - it("should throw GolemPaymentError if allocation is not created", async () => { - const paymentService = new PaymentService(yagnaApi, { - logger, - invoiceFilter: PaymentFilters.acceptMaxAmountInvoiceFilter(7), - }); - setExpectedEvents(invoiceEvents); - setExpectedInvoices(invoices); - await paymentService.run(); - expect(() => paymentService.acceptPayments(agreement)).toThrow( - new GolemPaymentError( - "You need to create an allocation before starting any payment processes", - PaymentErrorCode.MissingAllocation, - undefined, - agreement.getProviderInfo(), - ), - ); - await paymentService.end(); - }); - }); -}); diff --git a/tests/unit/stats.test.ts b/tests/unit/stats.test.ts deleted file mode 100644 index 03d48569b..000000000 --- a/tests/unit/stats.test.ts +++ /dev/null @@ -1,422 +0,0 @@ -import { AbstractAggregator } from "../../src/stats/abstract_aggregator"; -import { Activities } from "../../src/stats/activities"; -import { Agreements, AgreementStatusEnum } from "../../src/stats/agreements"; -import { Allocations } from "../../src/stats/allocations"; -import { Invoices } from "../../src/stats/invoices"; -import { Payments } from "../../src/stats/payments"; -import { Proposals } from "../../src/stats/proposals"; -import { Providers } from "../../src/stats/providers"; -import { Tasks, TaskStatusEnum } from "../../src/stats/tasks"; -import { Collection } from "collect.js"; - -const testProvider = { - id: "testId", - name: "test name", - walletAddress: "testWalletAddress", -}; - -const testProvider2 = { - id: "testId2", - name: "test name2", - walletAddress: "testWalletAddress2", -}; - -const testProvider3 = { - id: "testId3", - name: "test name3", - walletAddress: "testWalletAddress3", -}; - -describe("Stats Module", () => { - describe("Abstract Aggregator", () => { - it("should add() add items", async () => { - const tests = new Dummy(); - tests.add({ id: "id", parentId: "parentId" }); - expect(tests.getAll().count()).toEqual(1); - }); - it("should getById() return ItemInfo", async () => { - const tests = new Dummy(); - tests.add({ id: "id", parentId: "parentId" }); - expect(tests.getById("id")).toEqual({ id: "id", parentId: "parentId" }); - }); - it("should getAll() return Collection of ItemInfo", async () => { - const tests = new Dummy(); - tests.add({ id: "id", parentId: "parentId" }); - tests.add({ id: "id2", parentId: "parentId" }); - expect(tests.getAll()).toBeInstanceOf(Collection); - expect(tests.getAll().count()).toEqual(2); - }); - it("should getByField() return filtered Collection of ItemInfo", async () => { - const tests = new Dummy(); - tests.add({ id: "id", parentId: "parentId" }); - tests.add({ id: "id2", parentId: "parentId2" }); - tests.add({ id: "id3", parentId: "parentId3" }); - expect(tests.getByParentId("parentId2")).toBeInstanceOf(Collection); - expect(tests.getByParentId("parentId2").count()).toEqual(1); - }); - - it("should getByField() return empty Collection if there is no existing key", async () => { - const tests = new Dummy(); - tests.add({ id: "id", parentId: "parentId" }); - tests.add({ id: "id2", parentId: "parentId2" }); - tests.add({ id: "id3", parentId: "parentId3" }); - expect(tests.getByNotExistingKey()).toBeInstanceOf(Collection); - expect(tests.getByNotExistingKey().count()).toEqual(0); - }); - }); - describe("Activities", () => { - it("should beforeAdd() converts payload to ActivityInfo", async () => { - const tests = new Activities(); - tests.add({ id: "id", taskId: "taskId", agreementId: "agreementId" }); - expect(tests.getAll()).toEqual(new Collection([{ id: "id", taskId: "taskId", agreementId: "agreementId" }])); - }); - it("should getByAgreementId() return Collection of ActivityInfo", async () => { - const tests = new Activities(); - tests.add({ id: "id", taskId: "taskId", agreementId: "agreementId" }); - tests.add({ id: "id2", taskId: "taskId2", agreementId: "agreementId2" }); - tests.add({ id: "id3", taskId: "taskId3", agreementId: "agreementId3" }); - expect(tests.getByAgreementId("agreementId").count()).toEqual(1); - }); - it("should getByTaskId() return Collection of ActivityInfo", async () => { - const tests = new Activities(); - tests.add({ id: "id", taskId: "taskId", agreementId: "agreementId" }); - tests.add({ id: "id2", taskId: "taskId2", agreementId: "agreementId2" }); - tests.add({ id: "id3", taskId: "taskId3", agreementId: "agreementId3" }); - expect(tests.getByTaskId("taskId").count()).toEqual(1); - }); - }); - describe("Agreements", () => { - it("should beforeAdd() converts payload to AgreementInfo", async () => { - const tests = new Agreements(); - tests.add({ id: "id", provider: testProvider, proposalId: "proposalId" }); - expect(tests.getAll()).toEqual( - new Collection([ - { id: "id", provider: testProvider, proposalId: "proposalId", status: AgreementStatusEnum.Pending }, - ]), - ); - }); - it("should confirm() flag AgreementInfo.status as confirmed ", async () => { - const tests = new Agreements(); - tests.add({ id: "id", provider: testProvider, proposalId: "proposalId" }); - tests.confirm("id"); - expect(tests.getAll()).toEqual( - new Collection([ - { - id: "id", - provider: testProvider, - proposalId: "proposalId", - status: AgreementStatusEnum.Confirmed, - }, - ]), - ); - }); - it("should reject() flag AgreementInfo.status as rejected ", async () => { - const tests = new Agreements(); - tests.add({ id: "id", provider: testProvider, proposalId: "proposalId" }); - tests.reject("id"); - expect(tests.getAll()).toEqual( - new Collection([ - { id: "id", provider: testProvider, proposalId: "proposalId", status: AgreementStatusEnum.Rejected }, - ]), - ); - }); - it("should getByProviderId() return filtered Collection of AgreementInfo", async () => { - const tests = new Agreements(); - tests.add({ id: "id", provider: testProvider, proposalId: "proposalId" }); - tests.add({ id: "id2", provider: testProvider2, proposalId: "proposalId" }); - tests.add({ id: "id3", provider: testProvider, proposalId: "proposalId" }); - expect(tests.getByProviderId(testProvider.id).count()).toEqual(2); - }); - it("should getByStatus() return filtered Collection of AgreementInfo", async () => { - const tests = new Agreements(); - tests.add({ id: "id", provider: testProvider, proposalId: "proposalId" }); - tests.reject("id"); - tests.add({ id: "id2", provider: testProvider2, proposalId: "proposalId" }); - tests.confirm("id2"); - tests.add({ id: "id3", provider: testProvider, proposalId: "proposalId" }); - expect(tests.getByStatus(AgreementStatusEnum.Rejected).count()).toEqual(1); - expect(tests.getByStatus(AgreementStatusEnum.Confirmed).count()).toEqual(1); - expect(tests.getByStatus(AgreementStatusEnum.Pending).count()).toEqual(1); - }); - }); - describe("Allocations", () => { - it("should beforeAdd() converts payload to AllocationInfo", async () => { - const tests = new Allocations(); - tests.add({ id: "id", amount: 100, platform: "platform" }); - expect(tests.getAll()).toEqual(new Collection([{ id: "id", amount: 100, platform: "platform" }])); - }); - }); - describe("Invoices", () => { - it("should beforeAdd() converts payload to InvoiceInfo", async () => { - const tests = new Invoices(); - tests.add({ - id: "id", - amount: "100", - provider: testProvider, - agreementId: "agreementId", - }); - expect(tests.getAll()).toEqual( - new Collection([ - { - id: "id", - amount: "100", - provider: testProvider, - agreementId: "agreementId", - }, - ]), - ); - }); - it("should getByProviderId() return filtered Collection of InvoiceInfo", async () => { - const tests = new Invoices(); - tests.add({ - id: "id", - amount: "100", - provider: testProvider, - agreementId: "agreementId", - }); - tests.add({ - id: "id2", - amount: "100", - provider: testProvider2, - agreementId: "agreementId2", - }); - tests.add({ - id: "id3", - amount: "100", - provider: testProvider, - agreementId: "agreementId3", - }); - expect(tests.getByProviderId(testProvider.id).count()).toEqual(2); - }); - it("should getByAgreementId() return filtered Collection of InvoiceInfo", async () => { - const tests = new Invoices(); - tests.add({ - id: "id", - amount: "100", - provider: testProvider, - agreementId: "agreementId", - }); - tests.add({ - id: "id2", - amount: "100", - provider: testProvider2, - agreementId: "agreementId2", - }); - tests.add({ - id: "id3", - amount: "100", - provider: testProvider3, - agreementId: "agreementId", - }); - expect(tests.getByAgreementId("agreementId").count()).toEqual(2); - }); - }); - describe("Payments", () => { - it("should beforeAdd() converts payload to PaymentInfo", async () => { - const tests = new Payments(); - tests.add({ - id: "id", - amount: "100", - provider: testProvider, - agreementId: "agreementId", - }); - expect(tests.getAll()).toEqual( - new Collection([ - { - id: "id", - amount: "100", - provider: testProvider, - agreementId: "agreementId", - }, - ]), - ); - }); - it("should getByProviderId() return filtered Collection of PaymentInfo", async () => { - const tests = new Payments(); - tests.add({ - id: "id", - amount: "100", - provider: testProvider, - agreementId: "agreementId", - }); - tests.add({ - id: "id2", - amount: "100", - provider: testProvider2, - agreementId: "agreementId2", - }); - tests.add({ - id: "id3", - amount: "100", - provider: testProvider, - agreementId: "agreementId3", - }); - expect(tests.getByProviderId(testProvider.id).count()).toEqual(2); - }); - it("should getByAgreementId() return filtered Collection of PaymentInfo", async () => { - const tests = new Payments(); - tests.add({ - id: "id", - amount: "100", - provider: testProvider, - agreementId: "agreementId", - }); - tests.add({ - id: "id2", - amount: "100", - provider: testProvider2, - agreementId: "agreementId2", - }); - tests.add({ - id: "id3", - amount: "100", - provider: testProvider3, - agreementId: "agreementId", - }); - expect(tests.getByAgreementId("agreementId").count()).toEqual(2); - }); - }); - describe("Proposals", () => { - it("should beforeAdd() converts payload to ProposalInfo", async () => { - const tests = new Proposals(); - tests.add({ id: "id", providerId: testProvider.id }); - expect(tests.getAll()).toEqual( - new Collection([ - { - id: "id", - providerId: testProvider.id, - }, - ]), - ); - }); - - it("should getByProviderId() return filtered Collection of ProposalInfo", async () => { - const tests = new Proposals(); - tests.add({ id: "id", providerId: testProvider.id }); - tests.add({ id: "id2", providerId: testProvider2.id }); - tests.add({ id: "id3", providerId: testProvider.id }); - expect(tests.getByProviderId(testProvider.id)).toBeInstanceOf(Collection); - expect(tests.getByProviderId(testProvider.id).count()).toEqual(2); - }); - }); - describe("Providers", () => { - it("should beforeAdd() converts payload to ProviderInfo", async () => { - const tests = new Providers(); - tests.add(testProvider); - expect(tests.getAll()).toEqual(new Collection([testProvider])); - }); - - it("should beforeAdd() should setup providerName as unknown by default", async () => { - const tests = new Providers(); - tests.add(testProvider); - expect(tests.getAll()).toEqual(new Collection([testProvider])); - }); - - it("should beforeAdd() should update providerName if provided", async () => { - const tests = new Providers(); - tests.add(testProvider); - expect(tests.getAll()).toEqual(new Collection([testProvider])); - tests.add(testProvider); - expect(tests.getAll()).toEqual(new Collection([testProvider])); - }); - it("should beforeAdd() should use previous providerName if is not provided", async () => { - const tests = new Providers(); - tests.add(testProvider); - expect(tests.getAll()).toEqual(new Collection([testProvider])); - tests.add(testProvider); - expect(tests.getAll()).toEqual(new Collection([testProvider])); - }); - }); - describe("Tasks", () => { - it("should beforeAdd() converts payload to TaskInfo", async () => { - const tests = new Tasks(); - tests.add({ agreementId: "test_id", id: "id", startTime: 100 }); - expect(tests.getAll()).toEqual( - new Collection([ - { - id: "id", - agreementId: "test_id", - startTime: 100, - stopTime: 0, - retriesCount: 0, - status: TaskStatusEnum.Pending, - }, - ]), - ); - }); - it("should retry() should setup TaskInfo.retriesCount", async () => { - const tests = new Tasks(); - tests.add({ agreementId: "test_id", id: "id", startTime: 100 }); - tests.retry("id", 1); - expect(tests.getAll()).toEqual( - new Collection([ - { - agreementId: "test_id", - id: "id", - startTime: 100, - stopTime: 0, - retriesCount: 1, - status: TaskStatusEnum.Pending, - }, - ]), - ); - }); - it("should reject() should setup TaskInfo.status as Rejected, stopTime and reason", async () => { - const tests = new Tasks(); - tests.add({ agreementId: "test_id", id: "id", startTime: 100 }); - tests.reject("id", 200, "reason"); - expect(tests.getAll()).toEqual( - new Collection([ - { - id: "id", - agreementId: "test_id", - startTime: 100, - stopTime: 200, - retriesCount: 0, - status: TaskStatusEnum.Rejected, - reason: "reason", - }, - ]), - ); - }); - it("should finish() should setup TaskInfo.status as Finished, and stopTime", async () => { - const tests = new Tasks(); - tests.add({ agreementId: "test_id", id: "id", startTime: 100 }); - tests.finish("id", 200); - expect(tests.getAll()).toEqual( - new Collection([ - { - id: "id", - agreementId: "test_id", - startTime: 100, - stopTime: 200, - retriesCount: 0, - status: TaskStatusEnum.Finished, - }, - ]), - ); - }); - }); -}); - -export interface DummyInfo { - id: string; - parentId: string; -} - -interface Payload { - id: string; - parentId: string; -} - -export class Dummy extends AbstractAggregator { - beforeAdd(payload): DummyInfo { - return payload; - } - getByParentId(parentId: string) { - return this.getByField("parentId", parentId); - } - getByNotExistingKey() { - return this.getByField("key_doesnt_exists", 0); - } -} diff --git a/tests/unit/stats_service.test.ts b/tests/unit/stats_service.test.ts deleted file mode 100644 index 65a2f83a7..000000000 --- a/tests/unit/stats_service.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { Events } from "../../src/events"; -import { LoggerMock } from "../mock"; -import { StatsService } from "../../src/stats/service"; -import { setMaxListeners } from "events"; -import { ProposalDetails } from "../../src/market"; -const logger = new LoggerMock(); -const eventTarget = new EventTarget(); -const statServiceOptions = { logger, eventTarget }; -setMaxListeners(20); - -const testProvider = { - id: "testId", - name: "test name", - walletAddress: "testWalletAddress", -}; -describe("Stats Service", () => { - let statsService: StatsService; - beforeAll(async () => { - statsService = statsService = new StatsService(statServiceOptions); - await statsService.run(); - }); - afterAll(async () => { - await statsService.end(); - }); - describe("Creating", () => { - it("should start service", async () => { - expect(logger.logs).toContain("Stats service has started"); - }); - it("should end service", async () => { - await statsService.end(); - expect(logger.logs).toContain("Stats service has stopped"); - }); - }); - describe("Handling Events", () => { - //Tasks - it("should handle TaskStarted and call Tasks.add()", async () => { - const spy = jest.spyOn(statsService["tasks"], "add"); - const event = new Events.TaskStarted({ - id: "taskId", - agreementId: "agreementId", - activityId: "activityId", - provider: testProvider, - }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith({ - id: "taskId", - startTime: event.timeStamp, - agreementId: "agreementId", - }); - }); - it("should handle TaskStarted and call Activities.add()", async () => { - const spy = jest.spyOn(statsService["activities"], "add"); - const event = new Events.TaskStarted({ - id: "taskId", - agreementId: "agreementId", - activityId: "activityId", - provider: testProvider, - }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith({ id: "activityId", taskId: "taskId", agreementId: "agreementId" }); - }); - it("should handle TaskRedone and call Tasks.retry()", async () => { - const spy = jest.spyOn(statsService["tasks"], "retry"); - const event = new Events.TaskRedone({ - id: "id", - agreementId: "agreementId", - retriesCount: 1, - activityId: "activityId", - provider: testProvider, - reason: "reason", - }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith("id", 1); - }); - it("should handle TaskRejected and call Tasks.reject()", async () => { - const spy = jest.spyOn(statsService["tasks"], "reject"); - const event = new Events.TaskRejected({ - id: "id", - agreementId: "agreementId", - activityId: "activityId", - provider: testProvider, - reason: "reason", - }); - - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith("id", event.timeStamp, "reason"); - }); - it("should handle TaskFinished and call Tasks.finish()", async () => { - const spy = jest.spyOn(statsService["tasks"], "finish"); - const event = new Events.TaskFinished({ id: "id" }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith("id", event.timeStamp); - }); - - // Allocations - it("should handle AllocationCreated and call Allocations.add()", async () => { - const spy = jest.spyOn(statsService["allocations"], "add"); - const event = new Events.AllocationCreated({ id: "id", amount: 100, platform: "platform" }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith({ id: "id", amount: 100, platform: "platform" }); - }); - // Proposals - it("should handle ProposalReceived and call Proposals.add()", async () => { - const spy = jest.spyOn(statsService["proposals"], "add"); - const event = new Events.ProposalReceived({ - id: "id", - provider: testProvider, - parentId: null, - details: {} as ProposalDetails, - }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith({ id: "id", providerId: testProvider.id }); - }); - it("should handle ProposalReceived and call Provider.add()", async () => { - const spy = jest.spyOn(statsService["providers"], "add"); - const event = new Events.ProposalReceived({ - id: "id", - provider: testProvider, - parentId: null, - details: {} as ProposalDetails, - }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith(testProvider); - }); - // Invoices - it("should handle InvoiceReceived and call Invoice.add()", async () => { - const spy = jest.spyOn(statsService["invoices"], "add"); - const event = new Events.InvoiceReceived({ - id: "id", - provider: testProvider, - agreementId: "agreementId", - amount: 100, - amountPrecise: "100", - }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith({ - id: "id", - provider: testProvider, - agreementId: "agreementId", - amount: "100", - }); - }); - // Payments - it("should handle PaymentAccepted and call Payments.add()", async () => { - const spy = jest.spyOn(statsService["payments"], "add"); - const event = new Events.PaymentAccepted({ - id: "id", - provider: testProvider, - agreementId: "agreementId", - amount: 100, - amountPrecise: "100", - }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith({ - id: "id", - provider: testProvider, - agreementId: "agreementId", - amount: "100", - }); - }); - // Providers - it("should handle AgreementCreated and call Providers.add()", async () => { - const spy = jest.spyOn(statsService["providers"], "add"); - const event = new Events.AgreementCreated({ - id: "id", - provider: testProvider, - proposalId: "proposalId", - }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith(testProvider); - }); - // Agreements - it("should handle AgreementCreated and call Agreements.add()", async () => { - const spy = jest.spyOn(statsService["agreements"], "add"); - const event = new Events.AgreementCreated({ - id: "id", - provider: testProvider, - proposalId: "proposalId", - }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith({ id: "id", provider: testProvider, proposalId: "proposalId" }); - }); - it("should handle AgreementConfirmed and call Agreements.confirm()", async () => { - const spy = jest.spyOn(statsService["agreements"], "confirm"); - const event = new Events.AgreementConfirmed({ - id: "id", - provider: testProvider, - }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith("id"); - }); - it("should handle AgreementRejected and call Agreements.reject()", async () => { - const spy = jest.spyOn(statsService["agreements"], "reject"); - const event = new Events.AgreementRejected({ - id: "id", - provider: testProvider, - }); - eventTarget.dispatchEvent(event); - expect(spy).toHaveBeenCalledWith("id"); - }); - }); -}); diff --git a/tests/unit/task_queue.test.ts b/tests/unit/task_queue.test.ts deleted file mode 100644 index 060a2953f..000000000 --- a/tests/unit/task_queue.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { TaskQueue } from "../../src/task"; -import TaskMock, { TaskState } from "../mock/entities/task"; - -describe("Task Queue", function () { - let test_queue: TaskQueue; - beforeEach(function () { - test_queue = new TaskQueue(); - }); - describe("Adding", () => { - it("should allow to add Task to the queue", () => { - const task = new TaskMock("taskA", TaskState.New); - test_queue.addToEnd(task); - expect(test_queue.size).toEqual(1); - }); - it("should add new task on the end of the queue", () => { - const tasksToAdd = ["A", "B", "C"].map((t) => new TaskMock(`task${t}`, TaskState.New)); - // Add tree different tasks to the queue - tasksToAdd.forEach((task) => test_queue.addToEnd(task)); - // Check if the order is the same - tasksToAdd.forEach((task) => { - const returned_task = test_queue.get(); - expect(returned_task).toEqual(task); - }); - }); - it("should add task on the beginning of the queue", () => { - const tasksToAdd = ["A", "B", "C"].map((t) => new TaskMock(`task${t}`, TaskState.Retry)); - // Add tree different tasks to the queue - tasksToAdd.forEach((task) => test_queue.addToBegin(task)); - // Reverse expectation and check - tasksToAdd.reverse().forEach((task) => { - const returned_task = test_queue.get(); - expect(returned_task).toEqual(task); - }); - }); - it("should throws error if adding pending task", () => { - const task = new TaskMock("taskA", TaskState.Pending); - expect(() => test_queue.addToEnd(task)).toThrow("You cannot add a task that is not in the correct state"); - }); - it("should throws error if adding done task", () => { - const task = new TaskMock("taskA", TaskState.Done); - expect(() => test_queue.addToEnd(task)).toThrow(Error); - }); - }); - - describe("Getting", () => { - it("should remove task form the queue", () => { - const task = new TaskMock("taskA", TaskState.New); - test_queue.addToEnd(task); - expect(test_queue.size).toEqual(1); - test_queue.get(); - expect(test_queue.size).toEqual(0); - }); - - it('should return "undefined" when the queue is empty', () => { - new TaskMock("taskA", TaskState.New); - expect(test_queue.size).toEqual(0); - expect(test_queue.get()).toBeUndefined(); - }); - - it("should return correct number of items in the queue ", () => { - // Add 3 tasks to the queue - test_queue.addToEnd(new TaskMock(`task`, TaskState.New)); - test_queue.addToEnd(new TaskMock(`task`, TaskState.New)); - test_queue.addToEnd(new TaskMock(`task`, TaskState.New)); - // Check if is eq 3 - expect(test_queue.size).toEqual(3); - // Get one - test_queue.get(); - // Check if is eq 2 - expect(test_queue.size).toEqual(2); - // Get next two - test_queue.get(); - test_queue.get(); - // Check if is eq 0 - expect(test_queue.size).toEqual(0); - // get another one (not existing) - test_queue.get(); - // Check if still is eq 0 - expect(test_queue.size).toEqual(0); - }); - }); -}); diff --git a/tests/unit/tasks_service.test.ts b/tests/unit/tasks_service.test.ts deleted file mode 100644 index 93b486431..000000000 --- a/tests/unit/tasks_service.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -import * as activityMock from "../mock/rest/activity"; -import { Task, TaskQueue, TaskService, WorkContext, Worker } from "../../src/task"; -import { agreementPoolServiceMock, paymentServiceMock, networkServiceMock, LoggerMock, YagnaMock } from "../mock"; - -let queue: TaskQueue; -const logger = new LoggerMock(); - -describe("Task Service", () => { - beforeEach(() => { - logger.clear(); - activityMock.clear(); - queue = new TaskQueue(); - }); - it("should process new task in queue", async () => { - const worker = async (ctx: WorkContext) => ctx.run("some_shell_command"); - const task = new Task("1", worker); - queue.addToEnd(task); - activityMock.setExpectedExeResults([{ stdout: "some_shell_results" }]); - const service = new TaskService( - new YagnaMock().getApi(), - queue, - agreementPoolServiceMock, - paymentServiceMock, - networkServiceMock, - { - logger, - taskRunningInterval: 10, - activityStateCheckingInterval: 10, - }, - ); - service.run().catch((e) => console.error(e)); - await logger.expectToInclude("Activity created", { id: expect.anything() }, 500); - expect(task.isFinished()).toEqual(true); - expect(task.getResults()?.stdout).toEqual("some_shell_results"); - await service.end(); - await logger.expectToInclude("Activity destroyed", { id: expect.anything() }, 1); - await logger.expectToInclude("Task Service has been stopped", undefined, 1); - }); - - it("process only allowed number of tasks simultaneously", async () => { - const worker = async (ctx: WorkContext) => ctx.run("some_shell_command"); - const task1 = new Task("1", worker); - const task2 = new Task("2", worker); - const task3 = new Task("3", worker); - queue.addToEnd(task1); - queue.addToEnd(task2); - queue.addToEnd(task3); - const service = new TaskService( - new YagnaMock().getApi(), - queue, - agreementPoolServiceMock, - paymentServiceMock, - networkServiceMock, - { - logger, - maxParallelTasks: 2, - }, - ); - service.run().catch((e) => console.error(e)); - expect(task1.isQueued()).toEqual(true); - expect(task2.isQueued()).toEqual(true); - expect(task3.isQueued()).toEqual(false); - expect(task3.isNew()).toEqual(true); - await service.end(); - }); - - it("should retry task if it failed", async () => { - const worker = async (ctx: WorkContext) => ctx.run("some_shell_command"); - const task = new Task("1", worker); - queue.addToEnd(task); - activityMock.setExpectedErrors([new Error(), new Error(), new Error(), new Error(), new Error()]); - const service = new TaskService( - new YagnaMock().getApi(), - queue, - agreementPoolServiceMock, - paymentServiceMock, - networkServiceMock, - { - logger, - taskRunningInterval: 100, - activityStateCheckingInterval: 100, - }, - ); - service.run().catch((e) => console.error(e)); - await logger.expectToInclude( - "Task execution failed. Trying to redo the task.", - { - taskId: task.id, - attempt: 1, - reason: expect.anything(), - }, - 700, - ); - await service.end(); - }); - - it("should not retry task if it failed and maxRetries is zero", async () => { - const worker = async (ctx: WorkContext) => ctx.run("some_shell_command"); - const task = new Task("1", worker, { maxRetries: 0 }); - queue.addToEnd(task); - activityMock.setExpectedErrors([new Error(), new Error(), new Error(), new Error(), new Error()]); - const service = new TaskService( - new YagnaMock().getApi(), - queue, - agreementPoolServiceMock, - paymentServiceMock, - networkServiceMock, - { - logger, - taskRunningInterval: 100, - activityStateCheckingInterval: 100, - }, - ); - service.run().catch((e) => console.error(e)); - await logger.expectToNotMatch(/Trying to redo the task/, 100); - await logger.expectToInclude( - "Task has been rejected", - { taskId: task.id, reason: expect.anything(), retries: 0, providerName: expect.anything() }, - 100, - ); - await service.end(); - }); - - it("should throw an error if maxRetries is less then zero", async () => { - const worker = async (ctx: WorkContext) => Promise.resolve(true); - expect(() => new Task("1", worker, { maxRetries: -1 })).toThrow( - "The maxRetries parameter cannot be less than zero", - ); - }); - - it("should reject task if it failed max attempts", async () => { - const worker = async (ctx: WorkContext) => ctx.run("some_shell_command"); - const task = new Task("1", worker, { maxRetries: 1 }); - queue.addToEnd(task); - activityMock.setExpectedErrors(new Array(20).fill(new Error())); - const service = new TaskService( - new YagnaMock().getApi(), - queue, - agreementPoolServiceMock, - paymentServiceMock, - networkServiceMock, - { - logger, - taskRunningInterval: 10, - activityStateCheckingInterval: 10, - }, - ); - service.run().catch((e) => console.error(e)); - await logger.expectToInclude( - "Task has been rejected", - { - taskId: task.id, - reason: expect.anything(), - retries: 1, - providerName: expect.anything(), - }, - 1800, - ); - expect(task.isRejected()).toEqual(true); - await service.end(); - }); - - it("should run setup functions on each activity", async () => { - const setupFunctions = [async (ctx: WorkContext) => ctx.run("init_shell_command")]; - const worker = async (ctx: WorkContext) => ctx.run("some_shell_command"); - const task1 = new Task("1", worker, { activityReadySetupFunctions: setupFunctions }); - const task2 = new Task("2", worker, { activityReadySetupFunctions: setupFunctions }); - const task3 = new Task("3", worker, { activityReadySetupFunctions: setupFunctions }); - queue.addToEnd(task1); - queue.addToEnd(task2); - queue.addToEnd(task3); - const service = new TaskService( - new YagnaMock().getApi(), - queue, - agreementPoolServiceMock, - paymentServiceMock, - networkServiceMock, - { - logger, - taskRunningInterval: 10, - activityStateCheckingInterval: 10, - maxParallelTasks: 2, - }, - ); - service.run().catch((e) => console.error(e)); - - await new Promise((res) => setTimeout(res, 1000)); - - const setupsCompleted = logger.logs.split("\n").filter((log) => log.includes("Activity setup completed")).length; - expect(setupsCompleted).toEqual(2); - - expect(task1.isFinished()).toEqual(true); - expect(task2.isFinished()).toEqual(true); - expect(task3.isFinished()).toEqual(true); - await service.end(); - }); -}); diff --git a/tests/unit/work.test.ts b/tests/unit/work.test.ts deleted file mode 100644 index dfa35126f..000000000 --- a/tests/unit/work.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import * as activityMock from "../mock/rest/activity"; -import { Activity, GolemModuleError, GolemWorkError, WorkContext, WorkErrorCode } from "../../src"; -import { LoggerMock, StorageProviderMock, YagnaMock } from "../mock"; -import { agreement } from "../mock/entities/agreement"; - -const logger = new LoggerMock(); -const yagnaApi = new YagnaMock().getApi(); -const storageProviderMock = new StorageProviderMock({ logger }); - -describe("Work Context", () => { - beforeEach(() => { - logger.clear(); - activityMock.clear(); - }); - - const commonWorkOptions = { - yagnaOptions: yagnaApi.yagnaOptions, - logger, - activityStateCheckingInterval: 10, - storageProvider: storageProviderMock, - }; - - describe("Executing", () => { - it("should execute run command", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const worker = async (ctx: WorkContext) => ctx.run("some_shell_command"); - const ctx = new WorkContext(activity, commonWorkOptions); - await ctx.before(); - const results = await worker(ctx); - expect(results?.stdout).toEqual("test_result"); - }); - - it("should execute upload file command", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const worker = async (ctx: WorkContext) => ctx.uploadFile("./file.txt", "/golem/file.txt"); - const ctx = new WorkContext(activity, commonWorkOptions); - await ctx.before(); - const results = await worker(ctx); - expect(results?.stdout).toEqual("test_result"); - await logger.expectToInclude("File published", { src: "./file.txt" }); - }); - - it("should execute upload json command", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const worker = async (ctx: WorkContext) => ctx.uploadJson({ test: true }, "/golem/file.txt"); - const ctx = new WorkContext(activity, commonWorkOptions); - await ctx.before(); - const results = await worker(ctx); - expect(results?.stdout).toEqual("test_result"); - await logger.expectToInclude("Data published", { data: expect.anything() }); - }); - - it("should execute download file command", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const worker = async (ctx: WorkContext) => ctx.downloadFile("/golem/file.txt", "./file.txt"); - const ctx = new WorkContext(activity, commonWorkOptions); - await ctx.before(); - const results = await worker(ctx); - expect(results?.stdout).toEqual("test_result"); - await logger.expectToInclude("File received", { path: "./file.txt" }); - }); - }); - describe("Batch", () => { - it("should execute batch as promise", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const worker = async (ctx: WorkContext) => { - return ctx - .beginBatch() - .run("some_shell_command") - .uploadFile("./file.txt", "/golem/file.txt") - .uploadJson({ test: true }, "/golem/file.txt") - .downloadFile("/golem/file.txt", "./file.txt") - .end(); - }; - const ctx = new WorkContext(activity, commonWorkOptions); - const expectedStdout = [ - { stdout: "ok_run" }, - { stdout: "ok_upload_file" }, - { stdout: "ok_upload_json" }, - { stdout: "ok_download_file" }, - ]; - activityMock.setExpectedExeResults(expectedStdout); - const results = await worker(ctx); - expect(results?.map((r) => r.stdout)).toEqual(expectedStdout.map((s) => s.stdout)); - await logger.expectToInclude("File published", { src: "./file.txt" }); - await logger.expectToInclude("Data published", { data: expect.anything() }); - await logger.expectToInclude("File received", { path: "./file.txt" }); - }); - - it("should execute batch as stream", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const worker = async (ctx: WorkContext) => { - return ctx - .beginBatch() - .run("some_shell_command") - .uploadFile("./file.txt", "/golem/file.txt") - .uploadJson({ test: true }, "/golem/file.txt") - .downloadFile("/golem/file.txt", "./file.txt") - .endStream(); - }; - const ctx = new WorkContext(activity, commonWorkOptions); - const expectedStdout = [ - { stdout: "ok_run" }, - { stdout: "ok_upload_file" }, - { stdout: "ok_upload_json" }, - { stdout: "ok_download_file" }, - ]; - activityMock.setExpectedExeResults(expectedStdout); - const results = await worker(ctx); - await new Promise((res, rej) => { - results?.on("data", (result) => { - try { - expect(result.stdout).toEqual(expectedStdout?.shift()?.stdout); - } catch (e) { - rej(e); - } - }); - results?.on("end", res); - }); - await logger.expectToInclude("File published", { src: "./file.txt" }); - await logger.expectToInclude("Data published", { data: expect.anything() }); - await logger.expectToInclude("File received", { path: "./file.txt" }); - }); - }); - describe("Error handling", () => { - it("should return a result with error in case the command to execute is invalid", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const worker = async (ctx: WorkContext) => ctx.beginBatch().run("invalid_shell_command").end(); - const ctx = new WorkContext(activity, commonWorkOptions); - const expectedStdout = [{ result: "Error", stderr: "error", message: "Some error occurred" }]; - activityMock.setExpectedExeResults(expectedStdout); - - const [result] = await worker(ctx); - - expect(result.result).toEqual("Error"); - expect(result.message).toEqual("Some error occurred"); - }); - - it("should catch error while executing batch as stream with invalid command", async () => { - const activity = await Activity.create(agreement, yagnaApi); - const worker = async (ctx: WorkContext) => ctx.beginBatch().run("invalid_shell_command").endStream(); - const ctx = new WorkContext(activity, commonWorkOptions); - const expectedStdout = [{ result: "Error", stderr: "error", message: "Some error occurred" }]; - activityMock.setExpectedExeResults(expectedStdout); - const results = await worker(ctx); - - await new Promise((res) => - results.once("error", (error: GolemModuleError) => { - expect(error.message).toEqual("Some error occurred. Stdout: test_result. Stderr: error"); - expect(error).toBeInstanceOf(GolemWorkError); - expect(error.code).toEqual(WorkErrorCode.ScriptExecutionFailed); - res(true); - }), - ); - }); - }); -}); diff --git a/tests/utils/helpers.ts b/tests/utils/helpers.ts new file mode 100644 index 000000000..2cf24e028 --- /dev/null +++ b/tests/utils/helpers.ts @@ -0,0 +1,51 @@ +import { map, of, throwError } from "rxjs"; +import { Result, ResultData } from "../../src/activity/results"; + +/** + * Helper function that makes it easy to prepare successful exec results creation + */ +export const buildExeScriptSuccessResult = (stdout: string): Result => + new Result({ + index: 0, + eventDate: new Date().toISOString(), + result: "Ok", + stdout: stdout, + stderr: "", + message: "", + isBatchFinished: true, + }); + +/** + * Helper function that makes preparing error exec results creation + */ +export const buildExeScriptErrorResult = (stderr: string, message: string, stdout = ""): Result => + new Result({ + index: 0, + eventDate: new Date().toISOString(), + result: "Error", + stdout: stdout, + stderr: stderr, + message: message, + isBatchFinished: true, + }); + +/** + * Use it to simulate responses from a "long-polled" API endpoint + * + * @param response The response to return after "polling time" + * @param pollingTimeSec The time to wait before returning the response + */ +export const simulateLongPoll = (response: T, pollingTimeMs: number = 10) => + new Promise((resolve) => { + setTimeout(() => resolve(response), pollingTimeMs); + }); + +/** + * Helper function that makes preparing activity result returned by Activity.execute function + */ +export const buildExecutorResults = (successResults?: ResultData[], errorResults?: ResultData[], error?: Error) => { + if (error) { + return throwError(() => error); + } + return of(...(successResults ?? []), ...(errorResults ?? [])).pipe(map((resultData) => new Result(resultData))); +}; diff --git a/tsconfig.json b/tsconfig.json index d536a3ea4..912fd7af5 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,12 +19,12 @@ }, "typedocOptions": { "entryPoints": ["src"], - "exclude": ["**/*.spec.ts", "src/stats/*.ts"], + "exclude": ["**/*.spec.ts", "**/*.test.ts"], "entryPointStrategy": "expand", "out": "docs", "sort": "static-first", "excludePrivate": true, - "name": "JavaScript API reference", + "name": "Golem-JS API reference", "categorizeByGroup": false, "excludeExternals": true, "excludeInternal": true