From 9edc4fcc829a5b5f71f0947ada904d3eaf617c11 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Sat, 9 Nov 2024 08:28:04 +0000 Subject: [PATCH 1/8] feat: add cli --- .github/release-please-config.json | 1 + .github/release-please-manifest.json | 1 + .github/workflows/cli.yml | 43 + .github/workflows/release.yml | 1 + .github/workflows/w3up-client.yml | 2 +- packages/cli/.gitattributes | 2 + packages/cli/.gitignore | 1 + packages/cli/LICENSE.md | 232 +++ packages/cli/README.md | 302 ++++ packages/cli/account.js | 58 + packages/cli/api.js | 1 + packages/cli/api.ts | 1 + packages/cli/bin.js | 350 ++++ packages/cli/bridge.js | 66 + packages/cli/can.js | 219 +++ packages/cli/coupon.js | 55 + packages/cli/dialog.js | 148 ++ packages/cli/index.js | 704 ++++++++ packages/cli/lib.js | 298 ++++ packages/cli/package.json | 100 ++ packages/cli/space.js | 414 +++++ packages/cli/test/bin.spec.js | 1430 +++++++++++++++ packages/cli/test/fixtures/empty.car | 1 + packages/cli/test/fixtures/pinpie.car | Bin 0 -> 47972 bytes packages/cli/test/fixtures/pinpie.jpg | Bin 0 -> 47874 bytes packages/cli/test/helpers/context.js | 127 ++ packages/cli/test/helpers/env.js | 19 + packages/cli/test/helpers/http-server.js | 61 + packages/cli/test/helpers/process.js | 178 ++ packages/cli/test/helpers/random.js | 61 + .../cli/test/helpers/receipt-http-server.js | 80 + packages/cli/test/helpers/stream.js | 489 +++++ packages/cli/test/helpers/util.js | 19 + packages/cli/test/lib.spec.js | 98 + packages/cli/tsconfig.json | 29 + packages/w3up-client/src/service.js | 27 +- pnpm-lock.yaml | 1581 ++++++++++++----- 37 files changed, 6736 insertions(+), 463 deletions(-) create mode 100644 .github/workflows/cli.yml create mode 100644 packages/cli/.gitattributes create mode 100644 packages/cli/.gitignore create mode 100644 packages/cli/LICENSE.md create mode 100644 packages/cli/README.md create mode 100644 packages/cli/account.js create mode 100644 packages/cli/api.js create mode 100644 packages/cli/api.ts create mode 100755 packages/cli/bin.js create mode 100644 packages/cli/bridge.js create mode 100644 packages/cli/can.js create mode 100644 packages/cli/coupon.js create mode 100644 packages/cli/dialog.js create mode 100644 packages/cli/index.js create mode 100644 packages/cli/lib.js create mode 100644 packages/cli/package.json create mode 100644 packages/cli/space.js create mode 100644 packages/cli/test/bin.spec.js create mode 100644 packages/cli/test/fixtures/empty.car create mode 100644 packages/cli/test/fixtures/pinpie.car create mode 100644 packages/cli/test/fixtures/pinpie.jpg create mode 100644 packages/cli/test/helpers/context.js create mode 100644 packages/cli/test/helpers/env.js create mode 100644 packages/cli/test/helpers/http-server.js create mode 100644 packages/cli/test/helpers/process.js create mode 100644 packages/cli/test/helpers/random.js create mode 100644 packages/cli/test/helpers/receipt-http-server.js create mode 100644 packages/cli/test/helpers/stream.js create mode 100644 packages/cli/test/helpers/util.js create mode 100644 packages/cli/test/lib.spec.js create mode 100644 packages/cli/tsconfig.json diff --git a/.github/release-please-config.json b/.github/release-please-config.json index 0ebba676e..74b22a0c1 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -9,6 +9,7 @@ "packages": { "packages/access-client": {}, "packages/blob-index": {}, + "packages/cli": {}, "packages/filecoin-api": {}, "packages/filecoin-client": {}, "packages/capabilities": {}, diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json index f707fe251..02ef57700 100644 --- a/.github/release-please-manifest.json +++ b/.github/release-please-manifest.json @@ -1,6 +1,7 @@ { "packages/access-client": "0.0.0", "packages/blob-index": "0.0.0", + "packages/cli": "0.0.0", "packages/filecoin-api": "0.0.0", "packages/filecoin-client": "0.0.0", "packages/capabilities": "1.0.0", diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml new file mode 100644 index 000000000..4e662157c --- /dev/null +++ b/.github/workflows/cli.yml @@ -0,0 +1,43 @@ +name: cli +on: + push: + branches: + - main + paths: + - 'packages/cli/**' + - 'packages/eslint-config-w3up/**' + - '.github/workflows/cli.yml' + - 'pnpm-lock.yaml' + pull_request: + paths: + - 'packages/cli/**' + - 'packages/eslint-config-w3up/**' + - '.github/workflows/cli.yml' + - 'pnpm-lock.yaml' +jobs: + test: + name: Test + runs-on: ubuntu-latest + strategy: + matrix: + node_version: + - 18 + - 20 + defaults: + run: + working-directory: ./packages/cli + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install + uses: pnpm/action-setup@v4 + - name: Setup + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node_version }} + registry-url: https://registry.npmjs.org/ + cache: 'pnpm' + - run: pnpm install + - run: pnpm -r --filter @storacha/cli run lint + - run: pnpm -r --filter @storacha/cli run check + - run: pnpm -r --filter @storacha/cli run test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5828f2116..9abe5652a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,7 @@ jobs: contains(fromJson(needs.release.outputs.paths_released), 'packages/access-client') || contains(fromJson(needs.release.outputs.paths_released), 'packages/blob-index') || contains(fromJson(needs.release.outputs.paths_released), 'packages/capabilities') || + contains(fromJson(needs.release.outputs.paths_released), 'packages/cli') || contains(fromJson(needs.release.outputs.paths_released), 'packages/did-mailto') || contains(fromJson(needs.release.outputs.paths_released), 'packages/upload-client') || contains(fromJson(needs.release.outputs.paths_released), 'packages/upload-api') || diff --git a/.github/workflows/w3up-client.yml b/.github/workflows/w3up-client.yml index 23ce08fa5..5d11703d6 100644 --- a/.github/workflows/w3up-client.yml +++ b/.github/workflows/w3up-client.yml @@ -1,4 +1,4 @@ -name: w3up-client +name: client on: push: branches: diff --git a/packages/cli/.gitattributes b/packages/cli/.gitattributes new file mode 100644 index 000000000..55b172c5d --- /dev/null +++ b/packages/cli/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.car -text diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore new file mode 100644 index 000000000..b512c09d4 --- /dev/null +++ b/packages/cli/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/packages/cli/LICENSE.md b/packages/cli/LICENSE.md new file mode 100644 index 000000000..5662b2641 --- /dev/null +++ b/packages/cli/LICENSE.md @@ -0,0 +1,232 @@ +The contents of this repository are Copyright (c) corresponding authors and +contributors, licensed under the `Permissive License Stack` meaning either of: + +- Apache-2.0 Software License: https://www.apache.org/licenses/LICENSE-2.0 + ([...4tr2kfsq](https://dweb.link/ipfs/bafkreiankqxazcae4onkp436wag2lj3ccso4nawxqkkfckd6cg4tr2kfsq)) + +- MIT Software License: https://opensource.org/licenses/MIT + ([...vljevcba](https://dweb.link/ipfs/bafkreiepofszg4gfe2gzuhojmksgemsub2h4uy2gewdnr35kswvljevcba)) + +You may not use the contents of this repository except in compliance +with one of the listed Licenses. For an extended clarification of the +intent behind the choice of Licensing please refer to +https://protocol.ai/blog/announcing-the-permissive-license-stack/ + +Unless required by applicable law or agreed to in writing, software +distributed under the terms listed in this notice is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +either express or implied. See each License for the specific language +governing permissions and limitations under that License. + + + +`SPDX-License-Identifier: Apache-2.0 OR MIT` + +Verbatim copies of both licenses are included below: + +
Apache-2.0 Software License + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +``` + +
+ +
MIT Software License + +``` +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` + +
\ No newline at end of file diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 000000000..4002a3be7 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,302 @@ +# CLI + +πŸ’Ύ the `storacha` command line interface. + +## Getting started + +Install the CLI from npm (**requires Node 18 or higher**): + +```console +npm install -g @storacha/cli +``` + +Login with this agent to act on behalf of the account associated with your email address: + +```console +storacha login alice@example.com +``` + +Create a new Space for storing your data and register it: + +```console +storacha space create Documents # pick a good name! +``` + +If you'd like to learn more about what is going on under the hood with w3up and its use of Spaces, [UCANs](https://ucan.xyz/), and more, check out the `client` README [here](https://github.com/storacha/upload-service/tree/main/packages/w3up-client#usage). + +Upload a file or directory: + +```console +storacha up recipies.txt +``` + +> βš οΈβ— **Public Data** 🌎: All data uploaded to storacha is available to anyone who requests it using the correct CID. Do not store any private or sensitive information in an unencrypted form using storacha. + +> βš οΈβ— **Permanent Data** ♾️: Removing files from storacha will remove them from the file listing for your account, but that doesn’t prevent nodes on the decentralized storage network from retaining copies of the data indefinitely. Do not use storacha for data that may need to be permanently deleted in the future. + +## Commands + +- Basics + - [`storacha login`](#storacha-login-email) + - [`storacha up`](#storacha-up-path-path) + - [`storacha ls`](#storacha-ls) + - [`storacha rm`](#storacha-rm-root-cid) + - [`storacha open`](#storacha-open-cid) + - [`storacha whoami`](#storacha-whoami) +- Space management + - [`storacha space add`](#storacha-space-add-proofucan) + - [`storacha space create`](#storacha-space-create-name) + - [`storacha space ls`](#storacha-space-ls) + - [`storacha space use`](#storacha-space-use-did) + - [`storacha space info`](#storacha-space-info) +- Capability management + - [`storacha delegation create`](#storacha-delegation-create-audience-did) + - [`storacha delegation ls`](#storacha-delegation-ls) + - [`storacha delegation revoke`](#storacha-delegation-revoke-delegation-cid) + - [`storacha proof add`](#storacha-proof-add-proofucan) + - [`storacha proof ls`](#storacha-proof-ls) +- Key management + - [`storacha key create`](#storacha-key-create) +- UCAN-HTTP Bridge + - [`storacha bridge generate-tokens`](#storacha-bridge-generate-tokens) +- Advanced usage + - [`storacha can blob add`](#storacha-can-blob-add-path) + - [`storacha can blob ls`](#storacha-can-blob-ls) + - [`storacha can blob rm`](#storacha-can-blob-rm-multihash) + - [`storacha can index add`](#storacha-can-index-add-cid) + - [`storacha can space info`](#storacha-can-space-info-did) coming soon! + - [`storacha can space recover`](#storacha-can-space-recover-email) coming soon! + - [`storacha can upload add`](#storacha-can-upload-add-root-cid-shard-cid-shard-cid) + - [`storacha can upload ls`](#storacha-can-upload-ls) + - [`storacha can upload rm`](#storacha-can-upload-rm-root-cid) + +--- + +### `storacha login ` + +Authenticate this agent with your email address to get access to all capabilities that had been delegated to it. + +### `storacha up [path...]` + +Upload file(s) to web3.storage. The IPFS Content ID (CID) for your files is calculated on your machine, and sent up along with your files. web3.storage makes your content available on the IPFS network + +- `--no-wrap` Don't wrap input files with a directory. +- `-H, --hidden` Include paths that start with ".". +- `-c, --car` File is a CAR file. +- `--shard-size` Shard uploads into CAR files of approximately this size in bytes. +- `--concurrent-requests` Send up to this many CAR shards concurrently. + +### `storacha ls` + +List all the uploads registered in the current space. + +- `--json` Format as newline delimited JSON +- `--shards` Pretty print with shards in output + +### `storacha rm ` + +Remove an upload from the uploads listing. Note that this command does not remove the data from the IPFS network, nor does it remove it from space storage (by default). + +- `--shards` Also remove all shards referenced by the upload from the store. Use with caution and ensure other uploads do not reference the same shards. + +### `storacha open ` + +Open a CID on https://w3s.link in your browser. You can also pass a CID and a path. + +```bash +# opens a browser to https://w3s.link/ipfs/bafybeidluj5ub7okodgg5v6l4x3nytpivvcouuxgzuioa6vodg3xt2uqle +storacha open bafybeidluj5ub7okodgg5v6l4x3nytpivvcouuxgzuioa6vodg3xt2uqle + +# opens a browser to https://w3s.link/ipfs/bafybeidluj5ub7okodgg5v6l4x3nytpivvcouuxgzuioa6vodg3xt2uqle/olizilla.png +storacha open bafybeidluj5ub7okodgg5v6l4x3nytpivvcouuxgzuioa6vodg3xt2uqle/olizilla.png +``` + +### `storacha whoami` + +Print information about the current agent. + +### `storacha space add ` + +Add a space to the agent. The proof is a CAR encoded UCAN delegating capabilities over a space to _this_ agent. + +`proof` is a filesystem path to a CAR encoded UCAN, as generated by `storacha delegation create` _or_ a base64 identity CID string as created by `storacha delegation create --base64`. + +### `storacha space create [name]` + +Create a new space with an optional name. + +### `storacha space ls` + +List spaces known to the agent. + +### `storacha space use ` + +Set the current space in use by the agent. + +### `storacha space info` + +Get information about a space (by default the current space) from the service, including +which providers the space is currently registered with. + +- `--space` The space to get information about. Defaults to the current space. +- `--json` Format as newline delimited JSON + +### `storacha delegation create ` + +Create a delegation to the passed audience for the given abilities with the _current_ space as the resource. + +- `--can` A capability to delegate. To specify more than one capability, use this option more than once. +- `--name` Human readable name for the audience receiving the delegation. +- `--type` Type of the audience receiving the delegation, one of: device, app, service. +- `--output` Path of file to write the exported delegation data to. +- `--base64` Format as base64 identity CID string. Useful when saving it as an environment variable. + +```bash +# delegate space/info to did:key:z6M..., output as a CAR +storacha delegation create did:key:z6M... --can space/info --output ./info.ucan + +# delegate admin capabilities to did:key:z6M..., output as a string +storacha delegation create did:key:z6M... --can 'space/*' --can 'upload/*' --can 'filecoin/*' --base64 + +# delegate write (not remove) capabilities to did:key:z6M..., output as a string +storacha delegation create did:key:z6M... \ + --can 'space/blob/add' \ + --can 'space/index/add' \ + --can 'upload/add' \ + --can 'filecoin/offer' \ + --base64 +``` + +### `storacha delegation ls` + +List delegations created by this agent for others. + +- `--json` Format as newline delimited JSON + +### `storacha delegation revoke ` + +Revoke a delegation by CID. + +- `--proof` Name of a file containing the delegation and any additional proofs needed to prove authority to revoke + +### `storacha proof add ` + +Add a proof delegated to this agent. The proof is a CAR encoded delegation to _this_ agent. Note: you probably want to use `storacha space add` unless you know the delegation you received targets a resource _other_ than a space. + +### `storacha proof ls` + +List proofs of delegated capabilities. Proofs are delegations with an audience matching the agent DID. + +- `--json` Format as newline delimited JSON + +### `storacha key create` + +Print a new key pair. Does not change your current signing key + +- `--json` Export as dag-json + +### `storacha bridge generate-tokens` + +Generate tokens that can be used as the `X-Auth-Secret` and `Authorization` headers required to use the UCAN-HTTP bridge. + +See the [UCAN Bridge specification](https://github.com/storacha/specs/blob/main/w3-ucan-bridge.md) for more information +on how these are expected to be used. + +- `--can` One or more abilities to delegate. +- `--expiration` Unix timestamp (in seconds) when the delegation is no longer valid. Zero indicates no expiration. +- `--json` If set, output JSON suitable to splat into the `headers` field of a `fetch` request. + +### `storacha can blob add [path]` + +Store a blob file to the service. + +### `storacha can blob ls` + +List blobs in the current space. + +- `--json` Format as newline delimited JSON +- `--size` The desired number of results to return +- `--cursor` An opaque string included in a prior upload/list response that allows the service to provide the next "page" of results + +### `storacha can blob rm ` + +Remove a blob from the store by base58btc encoded multihash. + +### `storacha can space info ` + +### `storacha can space recover ` + +### `storacha can upload add [shard-cid...]` + +Register an upload - a DAG with the given root data CID that is stored in the given shard(s), identified by CID. + +### `storacha can upload ls` + +List uploads in the current space. + +- `--json` Format as newline delimited JSON +- `--shards` Pretty print with shards in output +- `--size` The desired number of results to return +- `--cursor` An opaque string included in a prior upload/list response that allows the service to provide the next "page" of results +- `--pre` If true, return the page of results preceding the cursor + +### `storacha can upload rm ` + +Remove an upload from the current space's upload list. Does not remove blobs from the store. + +## Environment Variables + +### `STORACHA_PRINCIPAL` + +Set the key `storacha` CLI should use to sign ucan invocations. By default `storacha` CLI will generate a new Ed25519 key on first run and store it. Set it along with a custom `STORACHA_STORE_NAME` to manage multiple custom keys and profiles. Trying to use an existing store with different keys will fail. + +You can generate Ed25519 keys with `storacha key create`. + +**Usage** + +```bash +STORACHA_PRINCIPAL=$(storacha key create --json | jq -r .key) STORACHA_STORE_NAME="other" storacha whoami +did:key:z6Mkf7bvSNgoXk67Ubhie8QMurN9E4yaCCGBzXow78zxnmuB +``` + +Default _unset_, a random Ed25519 key is generated. + +### `STORACHA_STORE_NAME` + +Allows you to use the `storacha` CLI with different profiles. You could use it to log in with different emails and keep the delegations separate. + +`storacha` CLI stores state to disk using the [`conf`](https://github.com/sindresorhus/conf) module. `STORACHA_STORE_NAME` sets the conf [`configName`](https://github.com/sindresorhus/conf#configname) option. + +Default `storacha-cli` + +### `STORACHA_SERVICE_URL` + +`storacha` CLI will use the w3up service at https://upload.storacha.network. If you would like +to use a different w3up-compatible service, set `STORACHA_SERVICE_DID` and `STORACHA_SERVICE_URL` environment variables to set the service DID and URL endpoint. + +Default `https://upload.storacha.network` + +### `STORACHA_SERVICE_DID` + +`storacha` CLI will use the w3up `did:web:upload.storacha.network` as the service did. If you would like +to use a different w3up-compatible service, set `STORACHA_SERVICE_DID` and `STORACHA_SERVICE_URL` environment variables to set the service DID and URL endpoint. + +Default `did:web:upload.storacha.network` + +## FAQ + +### Where are my keys and delegations stored? + +In the system default user config directory: + +- macOS: `~/Library/Preferences/storacha` +- Windows: `%APPDATA%\storacha\Config` (for example, `C:\Users\USERNAME\AppData\Roaming\storacha\Config`) +- Linux: `~/.config/storacha` (or `$XDG_CONFIG_HOME/storacha`) + +## Contributing + +Feel free to join in. All welcome. Please read our [contributing guidelines](https://github.com/storacha/upload-service/blob/main/CONTRIBUTING.md) and/or [open an issue](https://github.com/storacha/upload-service/issues)! + +## License + +Dual-licensed under [MIT + Apache 2.0](https://github.com/storacha/upload-service/blob/main/LICENSE.md) diff --git a/packages/cli/account.js b/packages/cli/account.js new file mode 100644 index 000000000..2f19848b0 --- /dev/null +++ b/packages/cli/account.js @@ -0,0 +1,58 @@ +import * as Account from '@storacha/client/account' +import * as Result from '@storacha/client/result' +import * as DidMailto from '@storacha/did-mailto' +import { getClient } from './lib.js' +import ora from 'ora' + +/** + * @typedef {Awaited>['ok']&{}} View + */ + +/** + * @param {DidMailto.EmailAddress} email + */ +export const login = async (email) => loginWithClient(email, await getClient()) + +/** + * @param {DidMailto.EmailAddress} email + * @param {import('@storacha/client').Client} client + * @returns {Promise} + */ +export const loginWithClient = async (email, client) => { + /** @type {import('ora').Ora|undefined} */ + let spinner + const timeout = setTimeout(() => { + spinner = ora( + `πŸ”— please click the link sent to ${email} to authorize this agent` + ).start() + }, 1000) + try { + const account = Result.try(await Account.login(client, email)) + + Result.try(await account.save()) + + if (spinner) spinner.stop() + console.log(`⁂ Agent was authorized by ${account.did()}`) + return account + } catch (err) { + if (spinner) spinner.stop() + console.error(err) + process.exit(1) + } finally { + clearTimeout(timeout) + } +} + +export const list = async () => { + const client = await getClient() + const accounts = Object.values(Account.list(client)) + for (const account of accounts) { + console.log(account.did()) + } + + if (accounts.length === 0) { + console.log( + '⁂ Agent has not been authorized yet. Try `storacha login` to authorize this agent with your account.' + ) + } +} diff --git a/packages/cli/api.js b/packages/cli/api.js new file mode 100644 index 000000000..336ce12bb --- /dev/null +++ b/packages/cli/api.js @@ -0,0 +1 @@ +export {} diff --git a/packages/cli/api.ts b/packages/cli/api.ts new file mode 100644 index 000000000..d83f73948 --- /dev/null +++ b/packages/cli/api.ts @@ -0,0 +1 @@ +export * from '@ucanto/interface' diff --git a/packages/cli/bin.js b/packages/cli/bin.js new file mode 100755 index 000000000..b40254216 --- /dev/null +++ b/packages/cli/bin.js @@ -0,0 +1,350 @@ +#!/usr/bin/env node + +import sade from 'sade' +import open from 'open' +import updateNotifier from 'update-notifier' +import { getPkg } from './lib.js' +import { + Account, + Space, + Coupon, + Bridge, + accessClaim, + addSpace, + listSpaces, + useSpace, + spaceInfo, + createDelegation, + listDelegations, + revokeDelegation, + addProof, + listProofs, + upload, + remove, + list, + whoami, + usageReport, + getPlan, + createKey, + reset, +} from './index.js' +import { + blobAdd, + blobList, + blobRemove, + indexAdd, + uploadAdd, + uploadList, + uploadRemove, + filecoinInfo, +} from './can.js' + +const pkg = getPkg() + +updateNotifier({ pkg }).notify({ isGlobal: true }) + +const cli = sade('storacha') + +cli + .version(pkg.version) + .example('login user@example.com') + .example('up path/to/files') + +cli + .command('login ') + .example('login user@example.com') + .describe( + 'Authenticate this agent with your email address to gain access to all capabilities that have been delegated to it.' + ) + .action(Account.login) + +cli + .command('plan get [email]') + .example('plan get user@example.com') + .describe('Displays plan given account is on') + .action(getPlan) + +cli + .command('account ls') + .alias('account list') + .describe('List accounts this agent has been authorized to act on behalf of.') + .action(Account.list) + +cli + .command('up [file]') + .alias('upload', 'put') + .describe('Store a file(s) to the service and register an upload.') + .option('-H, --hidden', 'Include paths that start with ".".', false) + .option('-c, --car', 'File is a CAR file.', false) + .option('--wrap', "Wrap single input file in a directory. Has no effect on directory or CAR uploads. Pass --no-wrap to disable.", true) + .option('--json', 'Format as newline delimited JSON', false) + .option('--verbose', 'Output more details.', false) + .option( + '--shard-size', + 'Shard uploads into CAR files of approximately this size in bytes.' + ) + .option( + '--concurrent-requests', + 'Send up to this many CAR shards concurrently.' + ) + .action(upload) + +cli + .command('open ') + .describe('Open CID on https://w3s.link') + .action((cid) => open(`https://w3s.link/ipfs/${cid}`)) + +cli + .command('ls') + .alias('list') + .describe('List uploads in the current space') + .option('--json', 'Format as newline delimited JSON') + .option('--shards', 'Pretty print with shards in output') + .action(list) + +cli + .command('rm ') + .example('rm bafy...') + .describe( + 'Remove an upload from the uploads listing. Pass --shards to delete the actual data if you are sure no other uploads need them' + ) + .option( + '--shards', + 'Remove all shards referenced by the upload from the store. Use with caution and ensure other uploads do not reference the same shards.' + ) + .action(remove) + +cli + .command('whoami') + .describe('Print information about the current agent.') + .action(whoami) + +cli + .command('space create [name]') + .describe('Create a new storacha space') + .option('-nr, --no-recovery', 'Skips recovery key setup') + .option('-n, --no-caution', 'Prints out recovery key without confirmation') + .option('-nc, --no-customer', 'Skip billing setup') + .option('-c, --customer ', 'Billing account email') + .option('-na, --no-account', 'Skip account setup') + .option('-a, --account ', 'Managing account email') + .action(Space.create) + +cli + .command('space provision [name]') + .describe('Associating space with a billing account') + .option('-c, --customer', 'The email address of the billing account') + .option('--coupon', 'Coupon URL to provision space with') + .option('-p, -password', 'Coupon password') + .option( + '-p, --provider', + 'The storage provider to associate with this space.' + ) + .action(Space.provision) + +cli + .command('space add ') + .describe( + 'Import a space from a proof: a CAR encoded UCAN delegating capabilities to this agent. proof is a filesystem path, or a base64 encoded cid string.' + ) + .action(addSpace) + +cli + .command('space ls') + .describe('List spaces known to the agent') + .action(listSpaces) + +cli + .command('space info') + .describe('Show information about a space. Defaults to the current space.') + .option('-s, --space', 'The space to print information about.') + .option('--json', 'Format as newline delimited JSON') + .action(spaceInfo) + +cli + .command('space use ') + .describe('Set the current space in use by the agent') + .action(useSpace) + +cli + .command('coupon create ') + .option('--password', 'Password for created coupon.') + .option('-c, --can', 'One or more abilities to delegate.') + .option( + '-e, --expiration', + 'Unix timestamp when the delegation is no longer valid. Zero indicates no expiration.', + 0 + ) + .option( + '-o, --output', + 'Path of file to write the exported delegation data to.' + ) + .action(Coupon.issue) + + cli + .command('bridge generate-tokens ') + .option('-c, --can', 'One or more abilities to delegate.') + .option( + '-e, --expiration', + 'Unix timestamp (in seconds) when the delegation is no longer valid. Zero indicates no expiration.', + 0 + ) + .option( + '-j, --json', + 'If set, output JSON suitable to spread into the `headers` field of a `fetch` request.' + ) + .action(Bridge.generateTokens) + + +cli + .command('delegation create ') + .describe( + 'Output a CAR encoded UCAN that delegates capabilities to the audience for the current space.' + ) + .option('-c, --can', 'One or more abilities to delegate.') + .option( + '-n, --name', + 'Human readable name for the audience receiving the delegation.' + ) + .option( + '-t, --type', + 'Type of the audience receiving the delegation, one of: device, app, service.' + ) + .option( + '-e, --expiration', + 'Unix timestamp when the delegation is no longer valid. Zero indicates no expiration.', + 0 + ) + .option( + '-o, --output', + 'Path of file to write the exported delegation data to.' + ) + .option( + '--base64', + 'Format as base64 identity CID string. Useful when saving it as an environment variable.' + ) + .action(createDelegation) + +cli + .command('delegation ls') + .describe('List delegations created by this agent for others.') + .option('--json', 'Format as newline delimited JSON') + .action(listDelegations) + +cli + .command('delegation revoke ') + .describe('Revoke a delegation by CID.') + .option( + '-p, --proof', + 'Name of a file containing the delegation and any additional proofs needed to prove authority to revoke' + ) + .action(revokeDelegation) + +cli + .command('proof add ') + .describe('Add a proof delegated to this agent.') + .option('--json', 'Format as newline delimited JSON') + .option('--dry-run', 'Decode and view the proof but do not add it') + .action(addProof) + +cli + .command('proof ls') + .describe('List proofs of capabilities delegated to this agent.') + .option('--json', 'Format as newline delimited JSON') + .action(listProofs) + +cli + .command('usage report') + .describe('Display report of current space usage in bytes.') + .option('--human', 'Format human readable values.', false) + .option('--json', 'Format as newline delimited JSON', false) + .action(usageReport) + +cli + .command('can access claim') + .describe('Claim delegated capabilities for the authorized account.') + .action(accessClaim) + +cli + .command('can blob add [data-path]') + .describe('Store a blob with the service.') + .action(blobAdd) + +cli + .command('can blob ls') + .describe('List blobs in the current space.') + .option('--json', 'Format as newline delimited JSON') + .option('--size', 'The desired number of results to return') + .option( + '--cursor', + 'An opaque string included in a prior blob/list response that allows the service to provide the next "page" of results' + ) + .action(blobList) + +cli + .command('can blob rm ') + .describe('Remove a blob from the store by base58btc encoded multihash.') + .action(blobRemove) + +cli + .command('can index add ') + .describe('Register an "index" with the service.') + .action(indexAdd) + +cli + .command('can upload add ') + .describe( + 'Register an upload - a DAG with the given root data CID that is stored in the given CAR shard(s), identified by CAR CIDs.' + ) + .action(uploadAdd) + +cli + .command('can upload ls') + .describe('List uploads in the current space.') + .option('--json', 'Format as newline delimited JSON') + .option('--shards', 'Pretty print with shards in output') + .option('--size', 'The desired number of results to return') + .option( + '--cursor', + 'An opaque string included in a prior upload/list response that allows the service to provide the next "page" of results' + ) + .option('--pre', 'If true, return the page of results preceding the cursor') + .action(uploadList) + +cli + .command('can upload rm ') + .describe('Remove an upload from the uploads listing.') + .action(uploadRemove) + +cli + .command('can filecoin info ') + .describe('Get filecoin information for given PieceCid.') + .action(filecoinInfo) + +cli + .command('key create') + .describe('Generate and print a new ed25519 key pair. Does not change your current signing key.') + .option('--json', 'output as json') + .action(createKey) + +cli + .command('reset') + .describe('Remove all proofs/delegations from the store but retain the agent DID.') + .action(reset) + +// show help text if no command provided +cli.command('help [cmd]', 'Show help text', { default: true }).action((cmd) => { + try { + cli.help(cmd) + } catch (err) { + console.log(` +ERROR + Invalid command: ${cmd} + +Run \`$ storacha --help\` for more info. +`) + process.exit(1) + } +}) + +cli.parse(process.argv) diff --git a/packages/cli/bridge.js b/packages/cli/bridge.js new file mode 100644 index 000000000..ad6d0d7cb --- /dev/null +++ b/packages/cli/bridge.js @@ -0,0 +1,66 @@ +import * as DID from '@ipld/dag-ucan/did' +import * as Account from './account.js' +import * as Space from './space.js' +import { getClient } from './lib.js' +import * as ucanto from '@ucanto/core' +import { base64url } from 'multiformats/bases/base64' +import cryptoRandomString from 'crypto-random-string' + +export { Account, Space } + +/** + * @typedef {object} BridgeGenerateTokensOptions + * @property {string} resource + * @property {string[]|string} [can] + * @property {number} [expiration] + * @property {boolean} [json] + * + * @param {string} resource + * @param {BridgeGenerateTokensOptions} options + */ +export const generateTokens = async ( + resource, + { can = ['store/add', 'upload/add'], expiration, json } +) => { + const client = await getClient() + + const resourceDID = DID.parse(resource) + const abilities = can ? [can].flat() : [] + if (!abilities.length) { + console.error('Error: missing capabilities for delegation') + process.exit(1) + } + + const capabilities = /** @type {ucanto.API.Capabilities} */ ( + abilities.map((can) => ({ can, with: resourceDID.did() })) + ) + + const password = cryptoRandomString({ length: 32 }) + + const coupon = await client.coupon.issue({ + capabilities, + expiration: expiration === 0 ? Infinity : expiration, + password, + }) + + const { ok: bytes, error } = await coupon.archive() + if (!bytes) { + console.error(error) + return process.exit(1) + } + const xAuthSecret = base64url.encode(new TextEncoder().encode(password)) + const authorization = base64url.encode(bytes) + + if (json) { + console.log(JSON.stringify({ + "X-Auth-Secret": xAuthSecret, + "Authorization": authorization + })) + } else { + console.log(` +X-Auth-Secret header: ${xAuthSecret} + +Authorization header: ${authorization} + `) + } +} diff --git a/packages/cli/can.js b/packages/cli/can.js new file mode 100644 index 000000000..6e6a42ab5 --- /dev/null +++ b/packages/cli/can.js @@ -0,0 +1,219 @@ +/* eslint-env browser */ +import fs from 'node:fs' +import { Readable } from 'node:stream' +import * as Link from 'multiformats/link' +import * as raw from 'multiformats/codecs/raw' +import { base58btc } from 'multiformats/bases/base58' +import * as Digest from 'multiformats/hashes/digest' +import { Piece } from '@web3-storage/data-segment' +import ora from 'ora' +import { + getClient, + uploadListResponseToString, + filecoinInfoToString, + parseCarLink, + streamToBlob, + blobListResponseToString, +} from './lib.js' + +/** + * @param {string} [blobPath] + */ +export async function blobAdd(blobPath) { + const client = await getClient() + + const spinner = ora('Reading data').start() + /** @type {Blob} */ + let blob + try { + blob = await streamToBlob( + /** @type {ReadableStream} */ + (Readable.toWeb(blobPath ? fs.createReadStream(blobPath) : process.stdin)) + ) + } catch (/** @type {any} */ err) { + spinner.fail(`Error: failed to read data: ${err.message}`) + process.exit(1) + } + + spinner.start('Storing') + const { digest } = await client.capability.blob.add(blob, { + receiptsEndpoint: client._receiptsEndpoint.toString() + }) + const cid = Link.create(raw.code, digest) + spinner.stopAndPersist({ symbol: '⁂', text: `Stored ${base58btc.encode(digest.bytes)} (${cid})` }) +} + +/** + * Print out all the blobs in the current space. + * + * @param {object} opts + * @param {boolean} [opts.json] + * @param {string} [opts.cursor] + * @param {number} [opts.size] + */ +export async function blobList(opts = {}) { + const client = await getClient() + const listOptions = {} + if (opts.size) { + listOptions.size = parseInt(String(opts.size)) + } + if (opts.cursor) { + listOptions.cursor = opts.cursor + } + + const spinner = ora('Listing Blobs').start() + const res = await client.capability.blob.list(listOptions) + spinner.stop() + console.log(blobListResponseToString(res, opts)) +} + +/** + * @param {string} digestStr + */ +export async function blobRemove(digestStr) { + const spinner = ora(`Removing ${digestStr}`).start() + let digest + try { + digest = Digest.decode(base58btc.decode(digestStr)) + } catch { + spinner.fail(`Error: "${digestStr}" is not a base58btc encoded multihash`) + process.exit(1) + } + const client = await getClient() + try { + await client.capability.blob.remove(digest) + spinner.stopAndPersist({ symbol: '⁂', text: `Removed ${digestStr}` }) + } catch (/** @type {any} */ err) { + spinner.fail(`Error: blob remove failed: ${err.message ?? err}`) + console.error(err) + process.exit(1) + } +} + +/** + * @param {string} cidStr + */ +export async function indexAdd(cidStr) { + const client = await getClient() + + const spinner = ora('Adding').start() + const cid = parseCarLink(cidStr) + if (!cid) { + spinner.fail(`Error: "${cidStr}" is not a valid index CID`) + process.exit(1) + } + await client.capability.index.add(cid) + spinner.stopAndPersist({ symbol: '⁂', text: `Added index ${cid}` }) +} + +/** + * @param {string} root + * @param {string} shard + * @param {object} opts + * @param {string[]} opts._ + */ +export async function uploadAdd(root, shard, opts) { + const client = await getClient() + + let rootCID + try { + rootCID = Link.parse(root) + } catch (/** @type {any} */ err) { + console.error(`Error: failed to parse root CID: ${root}: ${err.message}`) + process.exit(1) + } + + /** @type {import('@storacha/client/types').CARLink[]} */ + const shards = [] + for (const str of [shard, ...opts._]) { + try { + shards.push(Link.parse(str)) + } catch (/** @type {any} */ err) { + console.error(`Error: failed to parse shard CID: ${str}: ${err.message}`) + process.exit(1) + } + } + + const spinner = ora('Adding upload').start() + await client.capability.upload.add(rootCID, shards) + spinner.stopAndPersist({ symbol: '⁂', text: `Upload added ${rootCID}` }) +} + +/** + * Print out all the uploads in the current space. + * + * @param {object} opts + * @param {boolean} [opts.json] + * @param {boolean} [opts.shards] + * @param {string} [opts.cursor] + * @param {number} [opts.size] + * @param {boolean} [opts.pre] + */ +export async function uploadList(opts = {}) { + const client = await getClient() + const listOptions = {} + if (opts.size) { + listOptions.size = parseInt(String(opts.size)) + } + if (opts.cursor) { + listOptions.cursor = opts.cursor + } + if (opts.pre) { + listOptions.pre = opts.pre + } + + const spinner = ora('Listing uploads').start() + const res = await client.capability.upload.list(listOptions) + spinner.stop() + console.log(uploadListResponseToString(res, opts)) +} + +/** + * Remove the upload from the upload list. + * + * @param {string} rootCid + */ +export async function uploadRemove(rootCid) { + let root + try { + root = Link.parse(rootCid.trim()) + } catch (/** @type {any} */ err) { + console.error(`Error: ${rootCid} is not a CID`) + process.exit(1) + } + const client = await getClient() + try { + await client.capability.upload.remove(root) + } catch (/** @type {any} */ err) { + console.error(`Upload remove failed: ${err.message ?? err}`) + console.error(err) + process.exit(1) + } +} + +/** + * Get filecoin information for given PieceCid. + * + * @param {string} pieceCid + * @param {object} opts + * @param {boolean} [opts.json] + * @param {boolean} [opts.raw] + */ +export async function filecoinInfo(pieceCid, opts) { + let pieceInfo + try { + pieceInfo = Piece.fromString(pieceCid) + } catch (/** @type {any} */ err) { + console.error(`Error: ${pieceCid} is not a Link`) + process.exit(1) + } + const spinner = ora('Getting filecoin info').start() + const client = await getClient() + const info = await client.capability.filecoin.info(pieceInfo.link) + if (info.out.error) { + spinner.fail(`Error: failed to get filecoin info: ${info.out.error.message}`) + process.exit(1) + } + spinner.stop() + console.log(filecoinInfoToString(info.out.ok, opts)) +} diff --git a/packages/cli/coupon.js b/packages/cli/coupon.js new file mode 100644 index 000000000..0be7032e6 --- /dev/null +++ b/packages/cli/coupon.js @@ -0,0 +1,55 @@ +import fs from 'node:fs/promises' +import * as DID from '@ipld/dag-ucan/did' +import * as Account from './account.js' +import * as Space from './space.js' +import { getClient } from './lib.js' +import * as ucanto from '@ucanto/core' + +export { Account, Space } + +/** + * @typedef {object} CouponIssueOptions + * @property {string} customer + * @property {string[]|string} [can] + * @property {string} [password] + * @property {number} [expiration] + * @property {string} [output] + * + * @param {string} customer + * @param {CouponIssueOptions} options + */ +export const issue = async ( + customer, + { can = 'provider/add', expiration, password, output } +) => { + const client = await getClient() + + const audience = DID.parse(customer) + const abilities = can ? [can].flat() : [] + if (!abilities.length) { + console.error('Error: missing capabilities for delegation') + process.exit(1) + } + + const capabilities = /** @type {ucanto.API.Capabilities} */ ( + abilities.map((can) => ({ can, with: audience.did() })) + ) + + const coupon = await client.coupon.issue({ + capabilities, + expiration: expiration === 0 ? Infinity : expiration, + password, + }) + + const { ok: bytes, error } = await coupon.archive() + if (!bytes) { + console.error(error) + return process.exit(1) + } + + if (output) { + await fs.writeFile(output, bytes) + } else { + process.stdout.write(bytes) + } +} diff --git a/packages/cli/dialog.js b/packages/cli/dialog.js new file mode 100644 index 000000000..b4502e20b --- /dev/null +++ b/packages/cli/dialog.js @@ -0,0 +1,148 @@ +import { useState, useKeypress, createPrompt, isEnterKey } from '@inquirer/core' +import chalk from 'chalk' +import ansiEscapes from 'ansi-escapes' +/** + * @typedef {'concealed'|'revealed'|'validating'|'done'} Status + * @typedef {object} MnemonicOptions + * @property {string[]} secret + * @property {string} message + * @property {string} [prefix] + * @property {string} [revealMessage] + * @property {string} [submitMessage] + * @property {string} [validateMessage] + * @property {string} [exitMessage] + */ +export const mnemonic = createPrompt( + /** + * @param {MnemonicOptions} config + * @param {(answer: unknown) => void} done + */ + ( + { + prefix = 'πŸ”‘', + message, + secret, + revealMessage = 'When ready, hit enter to reveal the key', + submitMessage = 'Please save the key and then hit enter to continue', + validateMessage = 'Please type or paste key to ensure it is correct', + exitMessage = 'Key matched!', + }, + done + ) => { + const [status, setStatus] = useState(/** @type {Status} */ ('concealed')) + const [input, setInput] = useState('') + + useKeypress((key, io) => { + switch (status) { + case 'concealed': + if (isEnterKey(key)) { + setStatus('revealed') + } + return + case 'revealed': + if (isEnterKey(key)) { + setStatus('validating') + } + return + case 'validating': { + // if line break is pasted or typed we want interpret it as + // a space character, this is why we write current input back + // to the terminal with a trailing space. That way user will + // still be able to edit the input afterwards. + if (isEnterKey(key)) { + io.write(`${input} `) + } else { + // If current input matches the secret we are done. + const input = parseInput(io.line) + setInput(io.line) + if (input.join('') === secret.join('')) { + setStatus('done') + done({}) + } + } + return + } + default: + return done({}) + } + }) + + switch (status) { + case 'concealed': + return show({ + prefix, + message, + key: conceal(secret), + hint: revealMessage, + }) + case 'revealed': + return show({ prefix, message, key: secret, hint: submitMessage }) + case 'validating': + return show({ + prefix, + message, + key: diff(parseInput(input), secret), + hint: validateMessage, + }) + case 'done': + return show({ + prefix, + message, + key: conceal(secret, CORRECT), + hint: exitMessage, + }) + } + } +) + +/** + * @param {string} input + */ +const parseInput = (input) => input.trim().split(/[\n\s]+/) + +/** + * @param {string[]} input + * @param {string[]} key + */ +const diff = (input, key) => { + const source = input.join('') + let offset = 0 + const output = [] + for (const word of key) { + let delta = [] + for (const expect of word) { + const actual = source[offset] + if (actual === expect) { + delta.push(CORRECT) + } else if (actual != undefined) { + delta.push(chalk.inverse.strikethrough.red(actual)) + } else { + delta.push(CONCEAL) + } + offset++ + } + output.push(delta.join('')) + } + + return output +} + +/** + * @param {object} state + * @param {string} state.prefix + * @param {string} state.message + * @param {string[]} state.key + * @param {string} state.hint + */ +const show = ({ prefix, message, key, hint }) => + `${prefix} ${message}\n\n${key.join(' ')}\n\n${hint}${ansiEscapes.cursorHide}` + +/** + * @param {string[]} key + * @param {string} [char] + */ +const conceal = (key, char = CONCEAL) => + key.map((word) => char.repeat(word.length)) + +const CONCEAL = 'β–ˆ' +const CORRECT = chalk.inverse('β€’') diff --git a/packages/cli/index.js b/packages/cli/index.js new file mode 100644 index 000000000..43ed81623 --- /dev/null +++ b/packages/cli/index.js @@ -0,0 +1,704 @@ +import fs from 'node:fs' +import { pipeline } from 'node:stream/promises' +import { Readable } from 'node:stream' +import ora from 'ora' +import { CID } from 'multiformats/cid' +import { base64 } from 'multiformats/bases/base64' +import { identity } from 'multiformats/hashes/identity' +import * as Digest from 'multiformats/hashes/digest' +import * as DID from '@ipld/dag-ucan/did' +import * as dagJSON from '@ipld/dag-json' +import { CarWriter } from '@ipld/car' +import { filesFromPaths } from 'files-from-path' +import * as PieceHasher from 'fr32-sha2-256-trunc254-padded-binary-tree-multihash' +import * as Account from './account.js' + +import { spaceAccess } from '@storacha/client/capability/access' +import { AgentData } from '@storacha/access' +import * as Space from './space.js' +import { + getClient, + getStore, + checkPathsExist, + filesize, + filesizeMB, + readProof, + readProofFromBytes, + uploadListResponseToString, + startOfLastMonth, +} from './lib.js' +import * as ucanto from '@ucanto/core' +import { ed25519 } from '@ucanto/principal' +import chalk from 'chalk' +export * as Coupon from './coupon.js' +export * as Bridge from './bridge.js' +export { Account, Space } +import ago from 's-ago' + +/** + * + */ +export async function accessClaim() { + const client = await getClient() + await client.capability.access.claim() +} + +/** + * @param {string} email + */ +export const getPlan = async (email = '') => { + const client = await getClient() + const account = + email === '' + ? await Space.selectAccount(client) + : await Space.useAccount(client, { email }) + + if (account) { + const { ok: plan, error } = await account.plan.get() + if (plan) { + console.log(`⁂ ${plan.product}`) + } else if (error?.name === 'PlanNotFound') { + console.log('⁂ no plan has been selected yet') + } else { + console.error(`Failed to get plan - ${error.message}`) + process.exit(1) + } + } else { + process.exit(1) + } +} + +/** + * @param {`${string}@${string}`} email + * @param {object} [opts] + * @param {import('@ucanto/interface').Ability[]|import('@ucanto/interface').Ability} [opts.can] + */ +export async function authorize(email, opts = {}) { + const client = await getClient() + const capabilities = + opts.can != null ? [opts.can].flat().map((can) => ({ can })) : undefined + /** @type {import('ora').Ora|undefined} */ + let spinner + setTimeout(() => { + spinner = ora( + `πŸ”— please click the link we sent to ${email} to authorize this agent` + ).start() + }, 1000) + try { + await client.authorize(email, { capabilities }) + } catch (err) { + if (spinner) spinner.stop() + console.error(err) + process.exit(1) + } + if (spinner) spinner.stop() + console.log(`⁂ agent authorized to use capabilities delegated to ${email}`) +} + +/** + * @param {string} firstPath + * @param {{ + * _: string[], + * car?: boolean + * hidden?: boolean + * json?: boolean + * verbose?: boolean + * wrap?: boolean + * 'shard-size'?: number + * 'concurrent-requests'?: number + * }} [opts] + */ +export async function upload(firstPath, opts) { + /** @type {import('@storacha/client/types').FileLike[]} */ + let files + /** @type {number} */ + let totalSize // -1 when unknown size (input from stdin) + /** @type {import('ora').Ora} */ + let spinner + const client = await getClient() + if (firstPath) { + const paths = checkPathsExist([firstPath, ...(opts?._ ?? [])]) + const hidden = !!opts?.hidden + spinner = ora({ text: 'Reading files', isSilent: opts?.json }).start() + const localFiles = await filesFromPaths(paths, { hidden }) + totalSize = localFiles.reduce((total, f) => total + f.size, 0) + files = localFiles + spinner.stopAndPersist({ + text: `${files.length} file${files.length === 1 ? '' : 's'} ${chalk.dim( + filesize(totalSize) + )}`, + }) + + if (opts?.car && files.length > 1) { + console.error('Error: multiple CAR files not supported') + process.exit(1) + } + } else { + spinner = ora({ text: 'Reading from stdin', isSilent: opts?.json }).start() + files = [{ + name: 'stdin', + stream: () => + /** @type {ReadableStream} */ + (Readable.toWeb(process.stdin)) + }] + totalSize = -1 + opts = opts ?? { _: [] } + opts.wrap = false + } + + spinner.start('Storing') + /** @type {(o?: import('@storacha/client/src/types').UploadOptions) => Promise} */ + const uploadFn = opts?.car + ? client.uploadCAR.bind(client, files[0]) + : files.length === 1 && opts?.wrap === false + ? client.uploadFile.bind(client, files[0]) + : client.uploadDirectory.bind(client, files) + + let totalSent = 0 + const getStoringMessage = () => totalSize == -1 + // for unknown size, display the amount sent so far + ? `Storing ${filesizeMB(totalSent)}` + // for known size, display percentage of total size that has been sent + : `Storing ${Math.min(Math.round((totalSent / totalSize) * 100), 100)}%` + + const root = await uploadFn({ + pieceHasher: { + code: PieceHasher.code, + name: 'fr32-sha2-256-trunc254-padded-binary-tree-multihash', + async digest (input) { + const hasher = PieceHasher.create() + hasher.write(input) + + const bytes = new Uint8Array(hasher.multihashByteLength()) + hasher.digestInto(bytes, 0, true) + hasher.free() + + return Digest.decode(bytes) + } + }, + onShardStored: ({ cid, size, piece }) => { + totalSent += size + if (opts?.verbose) { + spinner.stopAndPersist({ + text: `${cid} ${chalk.dim(filesizeMB(size))}\n${chalk.dim( + ' └── ' + )}Piece CID: ${piece}`, + }) + spinner.start(getStoringMessage()) + } else { + spinner.text = getStoringMessage() + } + opts?.json && + opts?.verbose && + console.log(dagJSON.stringify({ shard: cid, size, piece })) + }, + shardSize: opts?.['shard-size'] && parseInt(String(opts?.['shard-size'])), + concurrentRequests: + opts?.['concurrent-requests'] && + parseInt(String(opts?.['concurrent-requests'])), + receiptsEndpoint: client._receiptsEndpoint.toString() + }) + spinner.stopAndPersist({ + symbol: '⁂', + text: `Stored ${files.length} file${files.length === 1 ? '' : 's'}`, + }) + console.log( + opts?.json ? dagJSON.stringify({ root }) : `⁂ https://w3s.link/ipfs/${root}` + ) +} + +/** + * Print out all the uploads in the current space. + * + * @param {object} opts + * @param {boolean} [opts.json] + * @param {boolean} [opts.shards] + */ +export async function list(opts = {}) { + const client = await getClient() + let count = 0 + /** @type {import('@storacha/client/types').UploadListSuccess|undefined} */ + let res + do { + res = await client.capability.upload.list({ cursor: res?.cursor }) + if (!res) throw new Error('missing upload list response') + count += res.results.length + if (res.results.length) { + console.log(uploadListResponseToString(res, opts)) + } + } while (res.cursor && res.results.length) + + if (count === 0 && !opts.json) { + console.log('⁂ No uploads in space') + console.log('⁂ Try out `storacha up ` to upload some') + } +} +/** + * @param {string} rootCid + * @param {object} opts + * @param {boolean} [opts.shards] + */ +export async function remove(rootCid, opts) { + let root + try { + root = CID.parse(rootCid.trim()) + } catch (/** @type {any} */ err) { + console.error(`Error: ${rootCid} is not a CID`) + process.exit(1) + } + const client = await getClient() + + try { + await client.remove(root, opts) + } catch (/** @type {any} */ err) { + console.error(`Remove failed: ${err.message ?? err}`) + console.error(err) + process.exit(1) + } +} + +/** + * @param {string} name + */ +export async function createSpace(name) { + const client = await getClient() + const space = await client.createSpace(name) + await client.setCurrentSpace(space.did()) + console.log(space.did()) +} + +/** + * @param {string} proofPathOrCid + */ +export async function addSpace(proofPathOrCid) { + const client = await getClient() + + let cid + try { + cid = CID.parse(proofPathOrCid, base64) + } catch (/** @type {any} */ err) { + if (err?.message?.includes('Unexpected end of data')) { + console.error(`Error: failed to read proof. The string has been truncated.`) + process.exit(1) + } + /* otherwise, try as path */ + } + + let delegation + if (cid) { + if (cid.multihash.code !== identity.code) { + console.error(`Error: failed to read proof. Must be identity CID. Fetching of remote proof CARs not supported by this command yet`) + process.exit(1) + } + delegation = await readProofFromBytes(cid.multihash.digest) + } else { + delegation = await readProof(proofPathOrCid) + } + + const space = await client.addSpace(delegation) + console.log(space.did()) +} + +/** + * + */ +export async function listSpaces() { + const client = await getClient() + const current = client.currentSpace() + for (const space of client.spaces()) { + const prefix = current && current.did() === space.did() ? '* ' : ' ' + console.log(`${prefix}${space.did()} ${space.name ?? ''}`) + } +} + +/** + * @param {string} did + */ +export async function useSpace(did) { + const client = await getClient() + const spaces = client.spaces() + const space = + spaces.find((s) => s.did() === did) ?? spaces.find((s) => s.name === did) + if (!space) { + console.error(`Error: space not found: ${did}`) + process.exit(1) + } + await client.setCurrentSpace(space.did()) + console.log(space.did()) +} + +/** + * @param {object} opts + * @param {import('@storacha/client/types').DID} [opts.space] + * @param {string} [opts.json] + */ +export async function spaceInfo(opts) { + const client = await getClient() + const spaceDID = opts.space ?? client.currentSpace()?.did() + if (!spaceDID) { + throw new Error( + 'no current space and no space given: please use --space to specify a space or select one using "space use"' + ) + } + + /** @type {import('@storacha/access/types').SpaceInfoResult} */ + let info + try { + info = await client.capability.space.info(spaceDID) + } catch (/** @type {any} */ err) { + // if the space was not known to the service then that's ok, there's just + // no info to print about it. Don't make it look like something is wrong, + // just print the space DID since that's all we know. + if (err.name === 'SpaceUnknown') { + // @ts-expect-error spaceDID should be a did:key + info = { did: spaceDID } + } else { + return console.log(`Error getting info about ${spaceDID}: ${err.message}`) + } + } + + const space = client.spaces().find((s) => s.did() === spaceDID) + const name = space ? space.name : undefined + + if (opts.json) { + console.log(JSON.stringify({ ...info, name }, null, 4)) + } else { + const providers = info.providers?.join(', ') ?? '' + console.log(` + DID: ${info.did} +Providers: ${providers || chalk.dim('none')} + Name: ${name ?? chalk.dim('none')}`) + } +} + +/** + * @param {string} audienceDID + * @param {object} opts + * @param {string[]|string} opts.can + * @param {string} [opts.name] + * @param {string} [opts.type] + * @param {number} [opts.expiration] + * @param {string} [opts.output] + * @param {string} [opts.with] + * @param {boolean} [opts.base64] + */ +export async function createDelegation(audienceDID, opts) { + const client = await getClient() + + if (client.currentSpace() == null) { + throw new Error('no current space, use `storacha space register` to create one.') + } + const audience = DID.parse(audienceDID) + + const abilities = opts.can ? [opts.can].flat() : Object.keys(spaceAccess) + if (!abilities.length) { + console.error('Error: missing capabilities for delegation') + process.exit(1) + } + const audienceMeta = {} + if (opts.name) audienceMeta.name = opts.name + if (opts.type) audienceMeta.type = opts.type + const expiration = opts.expiration || Infinity + + // @ts-expect-error createDelegation should validate abilities + const delegation = await client.createDelegation(audience, abilities, { + expiration, + audienceMeta, + }) + + const { writer, out } = CarWriter.create() + const dest = opts.output ? fs.createWriteStream(opts.output) : process.stdout + + void pipeline( + out, + async function* maybeBaseEncode(src) { + const chunks = [] + for await (const chunk of src) { + if (!opts.base64) { + yield chunk + } else { + chunks.push(chunk) + } + } + if (!opts.base64) return + const blob = new Blob(chunks) + const bytes = new Uint8Array(await blob.arrayBuffer()) + const idCid = CID.createV1(ucanto.CAR.code, identity.digest(bytes)) + yield idCid.toString(base64) + }, + dest + ) + + for (const block of delegation.export()) { + // @ts-expect-error + await writer.put(block) + } + await writer.close() +} + +/** + * @param {object} opts + * @param {boolean} [opts.json] + */ +export async function listDelegations(opts) { + const client = await getClient() + const delegations = client.delegations() + if (opts.json) { + for (const delegation of delegations) { + console.log( + JSON.stringify({ + cid: delegation.cid.toString(), + audience: delegation.audience.did(), + capabilities: delegation.capabilities.map((c) => ({ + with: c.with, + can: c.can, + })), + }) + ) + } + } else { + for (const delegation of delegations) { + console.log(delegation.cid.toString()) + console.log(` audience: ${delegation.audience.did()}`) + for (const capability of delegation.capabilities) { + console.log(` with: ${capability.with}`) + console.log(` can: ${capability.can}`) + } + } + } +} + +/** + * @param {string} delegationCid + * @param {object} opts + * @param {string} [opts.proof] + */ +export async function revokeDelegation(delegationCid, opts) { + const client = await getClient() + let proof + try { + if (opts.proof) { + proof = await readProof(opts.proof) + } + } catch (/** @type {any} */ err) { + console.log(`Error: reading proof: ${err.message}`) + process.exit(1) + } + let cid + try { + // TODO: we should validate that this is a UCANLink + cid = ucanto.parseLink(delegationCid.trim()) + } catch (/** @type {any} */ err) { + console.error(`Error: invalid CID: ${delegationCid}: ${err.message}`) + process.exit(1) + } + const result = await client.revokeDelegation( + /** @type {import('@ucanto/interface').UCANLink} */ (cid), + { proofs: proof ? [proof] : [] } + ) + if (result.ok) { + console.log(`⁂ delegation ${delegationCid} revoked`) + } else { + console.error(`Error: revoking ${delegationCid}: ${result.error?.message}`) + process.exit(1) + } +} + +/** + * @param {string} proofPath + * @param {{ json?: boolean, 'dry-run'?: boolean }} [opts] + */ +export async function addProof(proofPath, opts) { + const client = await getClient() + let proof + try { + proof = await readProof(proofPath) + if (!opts?.['dry-run']) { + await client.addProof(proof) + } + } catch (/** @type {any} */ err) { + console.log(`Error: ${err.message}`) + process.exit(1) + } + if (opts?.json) { + console.log(JSON.stringify(proof.toJSON())) + } else { + console.log(proof.cid.toString()) + console.log(` issuer: ${proof.issuer.did()}`) + for (const capability of proof.capabilities) { + console.log(` with: ${capability.with}`) + console.log(` can: ${capability.can}`) + } + } +} + +/** + * @param {object} opts + * @param {boolean} [opts.json] + */ +export async function listProofs(opts) { + const client = await getClient() + const proofs = client.proofs() + if (opts.json) { + for (const proof of proofs) { + console.log(JSON.stringify(proof)) + } + } else { + for (const proof of proofs) { + console.log(chalk.dim(`# ${proof.cid.toString()}`)) + console.log(`iss: ${chalk.cyanBright(proof.issuer.did())}`) + if (proof.expiration !== Infinity) { + console.log( + `exp: ${chalk.yellow(proof.expiration)} ${chalk.dim( + ` # expires ${ago(new Date(proof.expiration * 1000))}` + )}` + ) + } + console.log('att:') + for (const capability of proof.capabilities) { + console.log(` - can: ${chalk.magentaBright(capability.can)}`) + console.log(` with: ${chalk.green(capability.with)}`) + if (capability.nb) { + console.log(` nb: ${JSON.stringify(capability.nb)}`) + } + } + if (proof.facts.length > 0) { + console.log('fct:') + } + for (const fact of proof.facts) { + console.log(` - ${JSON.stringify(fact)}`) + } + console.log('') + } + console.log( + chalk.dim( + `# ${proofs.length} proof${ + proofs.length === 1 ? '' : 's' + } for ${client.agent.did()}` + ) + ) + } +} + +/** + * + */ +export async function whoami() { + const client = await getClient() + console.log(client.did()) +} + +/** + * @param {object} [opts] + * @param {boolean} [opts.human] + * @param {boolean} [opts.json] + */ +export async function usageReport(opts) { + const client = await getClient() + const now = new Date() + const period = { + // we may not have done a snapshot for this month _yet_, so get report from last month -> now + from: startOfLastMonth(now), + to: now, + } + const failures = [] + let total = 0 + for await (const result of getSpaceUsageReports( + client, + period + )) { + if ('error' in result) { + failures.push(result) + } else { + if (opts?.json) { + const { account, provider, space, size } = result + console.log( + dagJSON.stringify({ + account, + provider, + space, + size, + reportedAt: now.toISOString(), + }) + ) + } else { + const { account, provider, space, size } = result + console.log(` Account: ${account}`) + console.log(`Provider: ${provider}`) + console.log(` Space: ${space}`) + console.log( + ` Size: ${opts?.human ? filesize(size.final) : size.final}\n` + ) + } + total += result.size.final + } + } + if (!opts?.json) { + console.log(` Total: ${opts?.human ? filesize(total) : total}`) + if (failures.length) { + console.warn(``) + console.warn(` WARNING: there were ${failures.length} errors getting usage reports for some spaces.`) + console.warn(` This may happen if your agent does not have usage/report authorization for a space.`) + console.warn(` These spaces were not included in the usage report total:`) + for (const fail of failures) { + console.warn(` * space: ${fail.space}`) + // @ts-expect-error error is unknown + console.warn(` error: ${fail.error?.message}`) + console.warn(` account: ${fail.account}`) + } + } + } +} + +/** + * @param {import('@storacha/client').Client} client + * @param {{ from: Date, to: Date }} period + */ +async function* getSpaceUsageReports(client, period) { + for (const account of Object.values(client.accounts())) { + const subscriptions = await client.capability.subscription.list( + account.did() + ) + for (const { consumers } of subscriptions.results) { + for (const space of consumers) { + /** @type {import('@storacha/client/types').UsageReportSuccess} */ + let result + try { + result = await client.capability.usage.report(space, period) + } catch (error) { + yield { error, space, period, consumers, account: account.did() } + continue + } + for (const [, report] of Object.entries(result)) { + yield { account: account.did(), ...report } + } + } + } + } +} + +/** + * @param {{ json: boolean }} options + */ +export async function createKey({ json }) { + const signer = await ed25519.generate() + const key = ed25519.format(signer) + if (json) { + console.log(JSON.stringify({ did: signer.did(), key }, null, 2)) + } else { + console.log(`# ${signer.did()}`) + console.log(key) + } +} + +export const reset = async () => { + const store = getStore() + const exportData = await store.load() + if (exportData) { + let data = AgentData.fromExport(exportData) + // do not reset the principal + data = await AgentData.create({ principal: data.principal, meta: data.meta }) + await store.save(data.export()) + } + console.log('⁂ Agent reset.') +} diff --git a/packages/cli/lib.js b/packages/cli/lib.js new file mode 100644 index 000000000..262cca01d --- /dev/null +++ b/packages/cli/lib.js @@ -0,0 +1,298 @@ +import fs from 'node:fs' +import path from 'node:path' +// @ts-expect-error no typings :( +import tree from 'pretty-tree' +import { importDAG } from '@ucanto/core/delegation' +import { connect } from '@ucanto/client' +import * as CAR from '@ucanto/transport/car' +import * as HTTP from '@ucanto/transport/http' +import * as Signer from '@ucanto/principal/ed25519' +import * as Link from 'multiformats/link' +import { base58btc } from 'multiformats/bases/base58' +import * as Digest from 'multiformats/hashes/digest' +import * as raw from 'multiformats/codecs/raw' +import { parse } from '@ipld/dag-ucan/did' +import * as dagJSON from '@ipld/dag-json' +import { create } from '@storacha/client' +import { StoreConf } from '@storacha/client/stores/conf' +import { CarReader } from '@ipld/car' + +/** + * @typedef {import('@storacha/client/types').AnyLink} AnyLink + * @typedef {import('@storacha/client/types').CARLink} CARLink + * @typedef {import('@storacha/client/types').FileLike & { size: number }} FileLike + * @typedef {import('@storacha/client/types').SpaceBlobListSuccess} BlobListSuccess + * @typedef {import('@storacha/client/types').UploadListSuccess} UploadListSuccess + * @typedef {import('@storacha/capabilities/types').FilecoinInfoSuccess} FilecoinInfoSuccess + */ + +/** + * + */ +export function getPkg() { + // @ts-ignore JSON.parse works with Buffer in Node.js + return JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url))) +} + +/** @param {string[]|string} paths */ +export function checkPathsExist(paths) { + paths = Array.isArray(paths) ? paths : [paths] + for (const p of paths) { + if (!fs.existsSync(p)) { + console.error(`The path ${path.resolve(p)} does not exist`) + process.exit(1) + } + } + return paths +} + +/** @param {number} bytes */ +export function filesize(bytes) { + if (bytes < 50) return `${bytes}B` // avoid 0.0KB + if (bytes < 50000) return `${(bytes / 1000).toFixed(1)}KB` // avoid 0.0MB + if (bytes < 50000000) return `${(bytes / 1000 / 1000).toFixed(1)}MB` // avoid 0.0GB + return `${(bytes / 1000 / 1000 / 1000).toFixed(1)}GB` +} + +/** @param {number} bytes */ +export function filesizeMB(bytes) { + return `${(bytes / 1000 / 1000).toFixed(1)}MB` +} + +/** Get a configured w3up store used by the CLI. */ +export function getStore() { + return new StoreConf({ profile: process.env.STORACHA_STORE_NAME ?? 'storacha-cli' }) +} + +/** + * Get a new API client configured from env vars. + */ +export function getClient() { + const store = getStore() + + const uploadServiceDID = process.env.STORACHA_SERVICE_DID + ? parse(process.env.STORACHA_SERVICE_DID) + : undefined + const uploadServiceURL = process.env.STORACHA_SERVICE_URL + ? new URL(process.env.STORACHA_SERVICE_URL) + : undefined + const receiptsEndpointString = process.env.STORACHA_RECEIPTS_URL + let receiptsEndpoint + if (receiptsEndpointString) { + receiptsEndpoint = new URL(receiptsEndpointString) + } + + let serviceConf + if (uploadServiceDID && uploadServiceURL) { + serviceConf = + /** @type {import('@storacha/client/types').ServiceConf} */ + ({ + access: connect({ + id: uploadServiceDID, + codec: CAR.outbound, + channel: HTTP.open({ url: uploadServiceURL, method: 'POST' }), + }), + upload: connect({ + id: uploadServiceDID, + codec: CAR.outbound, + channel: HTTP.open({ url: uploadServiceURL, method: 'POST' }), + }), + filecoin: connect({ + id: uploadServiceDID, + codec: CAR.outbound, + channel: HTTP.open({ url: uploadServiceURL, method: 'POST' }), + }), + }) + } + + /** @type {import('@storacha/client/types').ClientFactoryOptions} */ + const createConfig = { store, serviceConf, receiptsEndpoint } + + const principal = process.env.STORACHA_PRINCIPAL + if (principal) { + createConfig.principal = Signer.parse(principal) + } + + return create(createConfig) +} + +/** + * @param {string} path Path to the proof file. + */ +export async function readProof(path) { + let bytes + try { + const buff = await fs.promises.readFile(path) + bytes = new Uint8Array(buff.buffer) + } catch (/** @type {any} */ err) { + console.error(`Error: failed to read proof: ${err.message}`) + process.exit(1) + } + return readProofFromBytes(bytes) +} + +/** + * @param {Uint8Array} bytes Path to the proof file. + */ +export async function readProofFromBytes(bytes) { + const blocks = [] + try { + const reader = await CarReader.fromBytes(bytes) + for await (const block of reader.blocks()) { + blocks.push(block) + } + } catch (/** @type {any} */ err) { + console.error(`Error: failed to parse proof: ${err.message}`) + process.exit(1) + } + try { + // @ts-expect-error + return importDAG(blocks) + } catch (/** @type {any} */ err) { + console.error(`Error: failed to import proof: ${err.message}`) + process.exit(1) + } +} + +/** + * @param {UploadListSuccess} res + * @param {object} [opts] + * @param {boolean} [opts.raw] + * @param {boolean} [opts.json] + * @param {boolean} [opts.shards] + * @param {boolean} [opts.plainTree] + * @returns {string} + */ +export function uploadListResponseToString(res, opts = {}) { + if (opts.json) { + return res.results + .map(({ root, shards }) => dagJSON.stringify({ root, shards })) + .join('\n') + } else if (opts.shards) { + return res.results + .map(({ root, shards }) => { + const treeBuilder = opts.plainTree ? tree.plain : tree + return treeBuilder({ + label: root.toString(), + nodes: [ + { + label: 'shards', + leaf: shards?.map((s) => s.toString()), + }, + ], + })} + ) + .join('\n') + } else { + return res.results.map(({ root }) => root.toString()).join('\n') + } +} + +/** + * @param {BlobListSuccess} res + * @param {object} [opts] + * @param {boolean} [opts.raw] + * @param {boolean} [opts.json] + * @returns {string} + */ +export function blobListResponseToString(res, opts = {}) { + if (opts.json) { + return res.results + .map(({ blob }) => dagJSON.stringify({ blob })) + .join('\n') + } else { + return res.results + .map(({ blob }) => { + const digest = Digest.decode(blob.digest) + const cid = Link.create(raw.code, digest) + return `${base58btc.encode(digest.bytes)} (${cid})` + }) + .join('\n') + } +} + +/** + * @param {FilecoinInfoSuccess} res + * @param {object} [opts] + * @param {boolean} [opts.raw] + * @param {boolean} [opts.json] + */ +export function filecoinInfoToString(res, opts = {}) { + if (opts.json) { + return res.deals + .map(deal => dagJSON.stringify(({ + aggregate: deal.aggregate.toString(), + provider: deal.provider, + dealId: deal.aux.dataSource.dealID, + inclusion: res.aggregates.find(a => a.aggregate.toString() === deal.aggregate.toString())?.inclusion + }))) + .join('\n') + } else { + if (!res.deals.length) { + return ` + Piece CID: ${res.piece.toString()} + Deals: Piece being aggregated and offered for deal... + ` + } + // not showing inclusion proof as it would just be bytes + return ` + Piece CID: ${res.piece.toString()} + Deals: ${res.deals.map((deal) => ` + Aggregate: ${deal.aggregate.toString()} + Provider: ${deal.provider} + Deal ID: ${deal.aux.dataSource.dealID} + `).join('')} + ` + } +} + +/** + * Return validated CARLink or undefined + * + * @param {AnyLink} cid + */ +export function asCarLink(cid) { + if (cid.version === 1 && cid.code === CAR.codec.code) { + return /** @type {CARLink} */ (cid) + } +} + +/** + * Return validated CARLink type or exit the process with an error code and message + * + * @param {string} cidStr + */ +export function parseCarLink(cidStr) { + try { + return asCarLink(Link.parse(cidStr.trim())) + } catch { + return undefined + } +} + +/** @param {string|number|Date} now */ +const startOfMonth = (now) => { + const d = new Date(now) + d.setUTCDate(1) + d.setUTCHours(0) + d.setUTCMinutes(0) + d.setUTCSeconds(0) + d.setUTCMilliseconds(0) + return d +} + +/** @param {string|number|Date} now */ +export const startOfLastMonth = (now) => { + const d = startOfMonth(now) + d.setUTCMonth(d.getUTCMonth() - 1) + return d +} + +/** @param {ReadableStream} source */ +export const streamToBlob = async source => { + const chunks = /** @type {Uint8Array[]} */ ([]) + await source.pipeTo(new WritableStream({ + write: chunk => { chunks.push(chunk) } + })) + return new Blob(chunks) +} diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 000000000..45e2318c4 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,100 @@ +{ + "name": "@storacha/cli", + "type": "module", + "version": "0.0.0", + "license": "Apache-2.0 OR MIT", + "description": "Command Line Interface to the Storacha Network", + "bin": { + "storacha": "bin.js" + }, + "scripts": { + "lint": "eslint '**/*.{js,ts}'", + "lint:fix": "eslint --fix '**/*.{js,ts}'", + "check": "tsc --build", + "format": "prettier --write '**/*.{js,ts,yml,json}' --ignore-path .gitignore", + "test": "entail **/*.spec.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/storacha/upload-service.git", + "directory": "packages/cli" + }, + "keywords": [ + "storacha", + "web3", + "storage", + "upload", + "cli" + ], + "bugs": { + "url": "https://github.com/storacha/upload-service/issues" + }, + "homepage": "https://github.com/storacha/upload-service#readme", + "devDependencies": { + "@storacha/capabilities": "workspace:^", + "@storacha/eslint-config": "workspace:^", + "@storacha/upload-api": "workspace:^", + "@types/update-notifier": "^6.0.5", + "@ucanto/interface": "^10.0.1", + "@ucanto/principal": "^9.0.1", + "@ucanto/server": "^10.0.0", + "@web-std/blob": "^3.0.5", + "@web3-storage/sigv4": "^1.0.2", + "entail": "^2.1.1", + "multiformats": "^13.1.1", + "npm-run-all": "^4.1.5", + "prettier": "^3.0.3", + "typescript": "^5.2.2" + }, + "dependencies": { + "@inquirer/core": "^5.1.1", + "@inquirer/prompts": "^3.3.0", + "@ipld/car": "^5.2.4", + "@ipld/dag-json": "^10.1.5", + "@ipld/dag-ucan": "^3.4.0", + "@storacha/access": "workspace:^", + "@storacha/client": "workspace:^", + "@storacha/did-mailto": "workspace:^", + "@ucanto/client": "^9.0.1", + "@ucanto/core": "^10.0.1", + "@ucanto/transport": "^9.1.1", + "@web3-storage/content-claims": "^5.1.3", + "@web3-storage/data-segment": "^5.0.0", + "ansi-escapes": "^6.2.0", + "chalk": "^5.3.0", + "crypto-random-string": "^5.0.0", + "files-from-path": "^1.0.4", + "fr32-sha2-256-trunc254-padded-binary-tree-multihash": "^3.3.0", + "open": "^9.1.0", + "ora": "^7.0.1", + "pretty-tree": "^1.0.0", + "s-ago": "^2.2.0", + "sade": "^1.8.1", + "update-notifier": "^7.0.0" + }, + "eslintConfig": { + "extends": [ + "@storacha/eslint-config" + ], + "parserOptions": { + "project": "./tsconfig.json" + }, + "env": { + "es2022": true, + "mocha": true, + "browser": true, + "node": true + }, + "ignorePatterns": [ + "dist", + "coverage", + "api.js" + ] + }, + "prettier": { + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true + } +} diff --git a/packages/cli/space.js b/packages/cli/space.js new file mode 100644 index 000000000..c96540385 --- /dev/null +++ b/packages/cli/space.js @@ -0,0 +1,414 @@ +import * as W3Space from '@storacha/client/space' +import * as W3Account from '@storacha/client/account' +import { getClient } from './lib.js' +import process from 'node:process' +import * as DIDMailto from '@storacha/did-mailto' +import * as Account from './account.js' +import { SpaceDID } from '@storacha/capabilities/utils' +import ora from 'ora' +import { select, input } from '@inquirer/prompts' +import { mnemonic } from './dialog.js' +import { API } from '@ucanto/core' +import * as Result from '@storacha/client/result' + +/** + * @typedef {object} CreateOptions + * @property {false} [recovery] + * @property {false} [caution] + * @property {DIDMailto.EmailAddress|false} [customer] + * @property {string|false} [account] + * + * @param {string|undefined} name + * @param {CreateOptions} options + */ +export const create = async (name, options) => { + const client = await getClient() + const spaces = client.spaces() + + const space = await client.createSpace(await chooseName(name ?? '', spaces)) + + // Unless use opted-out from paper key recovery, we go through the flow + if (options.recovery !== false) { + const recovery = await setupRecovery(space, options) + if (recovery == null) { + console.log( + '⚠️ Aborting, if you want to create space without recovery option pass --no-recovery flag' + ) + process.exit(1) + } + } + + if (options.customer !== false) { + console.log('πŸ—οΈ To serve this space we need to set a billing account') + const setup = await setupBilling(client, { + customer: options.customer, + space: space.did(), + message: '🚜 Setting a billing account', + }) + + if (setup.error) { + if (setup.error.reason === 'abort') { + console.log( + '⏭️ Skipped billing setup. You can do it later using `storacha space provision`' + ) + } else { + console.error( + '⚠️ Failed to to set billing account. You can retry using `storacha space provision`' + ) + console.error(setup.error.cause.message) + } + } else { + console.log(`✨ Billing account is set`) + } + } + + // Authorize this client to allow them to use this space. + // ⚠️ This is a temporary solution until we solve the account sync problem + // after which we will simply delegate to the account. + const authorization = await space.createAuthorization(client) + await client.addSpace(authorization) + // set this space as the current default space + await client.setCurrentSpace(space.did()) + + // Unless user opted-out we go through an account authorization flow + if (options.account !== false) { + console.log( + `⛓️ To manage space across devices we need to authorize an account` + ) + + const account = options.account + ? await useAccount(client, { email: options.account }) + : await selectAccount(client) + + if (account) { + const spinner = ora(`πŸ“© Authorizing ${account.toEmail()}`).start() + const recovery = await space.createRecovery(account.did()) + + const result = await client.capability.access.delegate({ + space: space.did(), + delegations: [recovery], + }) + spinner.stop() + + if (result.ok) { + console.log(`✨ Account is authorized`) + } else { + console.error( + `⚠️ Failed to authorize account. You can still manage space using "paper key"` + ) + console.error(result.error) + } + } else { + console.log( + `⏭️ Skip account authorization. You can still can manage space using "paper key"` + ) + } + } + + console.log(`⁂ Space created: ${space.did()}`) + + return space +} + +/** + * @param {import('@storacha/client').Client} client + * @param {object} options + * @param {import('@storacha/client/types').SpaceDID} options.space + * @param {DIDMailto.EmailAddress} [options.customer] + * @param {string} [options.message] + * @param {string} [options.waitMessage] + * @returns {Promise>} + */ +const setupBilling = async ( + client, + { + customer, + space, + message = 'Setting up a billing account', + waitMessage = 'Waiting for payment plan to be selected', + } +) => { + const account = customer + ? await useAccount(client, { email: customer }) + : await selectAccount(client) + + if (account) { + const spinner = ora(waitMessage).start() + + let plan = null + while (!plan) { + const result = await account.plan.get() + + if (result.ok) { + plan = result.ok + } else { + await new Promise((resolve) => setTimeout(resolve, 1000)) + } + } + + spinner.text = message + + const result = await account.provision(space) + + spinner.stop() + if (result.error) { + return { error: { reason: 'error', cause: result.error } } + } else { + return { ok: {} } + } + } else { + return { error: { reason: 'abort' } } + } +} + +/** + * @typedef {object} ProvisionOptions + * @property {DIDMailto.EmailAddress} [customer] + * @property {string} [coupon] + * @property {string} [provider] + * @property {string} [password] + * + * @param {string} name + * @param {ProvisionOptions} options + */ +export const provision = async (name = '', options = {}) => { + const client = await getClient() + const space = chooseSpace(client, { name }) + if (!space) { + console.log( + `You do not appear to have a space, you can create one by running "w3 space create"` + ) + process.exit(1) + } + + if (options.coupon) { + const { ok: bytes, error: fetchError } = await fetch(options.coupon) + .then((response) => response.arrayBuffer()) + .then((buffer) => Result.ok(new Uint8Array(buffer))) + .catch((error) => Result.error(/** @type {Error} */ (error))) + + if (fetchError) { + console.error(`Failed to fetch coupon from ${options.coupon}`) + process.exit(1) + } + + const { ok: access, error: couponError } = await client.coupon + .redeem(bytes, options) + .then(Result.ok, Result.error) + + if (!access) { + console.error(`Failed to redeem coupon: ${couponError.message}}`) + process.exit(1) + } + + const result = await W3Space.provision( + { did: () => space }, + { + proofs: access.proofs, + agent: client.agent, + } + ) + + if (result.error) { + console.log(`Failed to provision space: ${result.error.message}`) + process.exit(1) + } + } else { + const result = await setupBilling(client, { + customer: options.customer, + space, + }) + + if (result.error) { + console.error( + `⚠️ Failed to set up billing account,\n ${ + Object(result.error).message ?? '' + }` + ) + process.exit(1) + } + } + + console.log(`✨ Billing account is set`) +} + +/** + * @typedef {import('@storacha/client/types').SpaceDID} SpaceDID + * + * @param {import('@storacha/client').Client} client + * @param {object} options + * @param {string} options.name + * @returns {SpaceDID|undefined} + */ +const chooseSpace = (client, { name }) => { + if (name) { + const result = SpaceDID.read(name) + if (result.ok) { + return result.ok + } + + const space = client.spaces().find((space) => space.name === name) + if (space) { + return /** @type {SpaceDID} */ (space.did()) + } + } + + return /** @type {SpaceDID|undefined} */ (client.currentSpace()?.did()) +} + +/** + * + * @param {W3Space.Model} space + * @param {CreateOptions} options + */ +export const setupEmailRecovery = async (space, options = {}) => {} + +/** + * @param {string} email + * @returns {{ok: DIDMailto.EmailAddress, error?:void}|{ok?:void, error: Error}} + */ +const parseEmail = (email) => { + try { + return { ok: DIDMailto.email(email) } + } catch (cause) { + return { error: /** @type {Error} */ (cause) } + } +} + +/** + * @param {W3Space.OwnedSpace} space + * @param {CreateOptions} options + */ +export const setupRecovery = async (space, options = {}) => { + const recoveryKey = W3Space.toMnemonic(space) + + if (options.caution === false) { + console.log(formatRecoveryInstruction(recoveryKey)) + return space + } else { + const verified = await mnemonic({ + secret: recoveryKey.split(/\s+/g), + message: + 'You need to save the following secret recovery key somewhere safe! For example write it down on a piece of paper and put it inside your favorite book.', + revealMessage: + '🀫 Make sure no one is eavesdropping and hit enter to reveal the key', + submitMessage: 'πŸ“ Once you have saved the key hit enter to continue', + validateMessage: + 'πŸ”’ Please type or paste your recovery key to make sure it is correct', + exitMessage: 'πŸ” Secret recovery key is correct!', + }).catch(() => null) + + return verified ? space : null + } +} + +/** + * @param {string} key + */ +const formatRecoveryInstruction = (key) => + `πŸ”‘ You need to save following secret recovery key somewhere safe! For example write it down on a piece of paper and put it inside your favorite book. + + ${key} + +` + +/** + * @param {string} name + * @param {{name:string}[]} spaces + * @returns {Promise} + */ +const chooseName = async (name, spaces) => { + const space = spaces.find((space) => String(space.name) === name) + const message = + name === '' + ? 'What would you like to call this space?' + : space + ? `Name "${space.name}" is already taken, please choose a different one` + : null + + if (message == null) { + return name + } else { + return await input({ + message, + }) + } +} + +/** + * @param {import('@storacha/client').Client} client + * @param {{email?:string}} options + */ +export const pickAccount = async (client, { email }) => + email ? await useAccount(client, { email }) : await selectAccount(client) + +/** + * @param {import('@storacha/client').Client} client + * @param {{email?:string}} options + */ +export const useAccount = (client, { email }) => { + const accounts = Object.values(W3Account.list(client)) + const account = accounts.find((account) => account.toEmail() === email) + + if (!account) { + console.error( + `Agent is not authorized by ${email}, please login with it first` + ) + return null + } + + return account +} + +/** + * @param {import('@storacha/client').Client} client + */ +export const selectAccount = async (client) => { + const accounts = Object.values(W3Account.list(client)) + + // If we do not have any accounts yet we take user through setup flow + if (accounts.length === 0) { + return setupAccount(client) + } + // If we have only one account we use it + else if (accounts.length === 1) { + return accounts[0] + } + // Otherwise we ask user to choose one + else { + return chooseAccount(accounts) + } +} + +/** + * @param {import('@storacha/client').Client} client + */ +export const setupAccount = async (client) => { + const email = await input({ + message: `πŸ“§ Please enter an email address to setup an account`, + validate: (input) => parseEmail(input).ok != null, + }).catch(() => null) + + return email + ? await Account.loginWithClient( + /** @type {DIDMailto.EmailAddress} */ (email), + client + ) + : null +} + +/** + * @param {Account.View[]} accounts + * @returns {Promise} + */ +export const chooseAccount = async (accounts) => { + const account = await select({ + message: 'Please choose an account you would like to use', + choices: accounts.map((account) => ({ + name: account.toEmail(), + value: account, + })), + }).catch(() => null) + + return account +} diff --git a/packages/cli/test/bin.spec.js b/packages/cli/test/bin.spec.js new file mode 100644 index 000000000..7b451e086 --- /dev/null +++ b/packages/cli/test/bin.spec.js @@ -0,0 +1,1430 @@ +import fs from 'fs' +import os from 'os' +import path from 'path' +import * as Signer from '@ucanto/principal/ed25519' +import { importDAG } from '@ucanto/core/delegation' +import { parseLink } from '@ucanto/server' +import * as DID from '@ipld/dag-ucan/did' +import * as dagJSON from '@ipld/dag-json' +import { SpaceDID } from '@storacha/capabilities/utils' +import { CarReader } from '@ipld/car' +import { test } from './helpers/context.js' +import * as Test from './helpers/context.js' +import { pattern, match } from './helpers/util.js' +import * as Command from './helpers/process.js' +import { Absentee, ed25519 } from '@ucanto/principal' +import * as DIDMailto from '@storacha/did-mailto' +import { UCAN, Provider } from '@storacha/capabilities' +import * as ED25519 from '@ucanto/principal/ed25519' +import { sha256, delegate } from '@ucanto/core' +import * as Result from '@storacha/client/result' +import * as Link from 'multiformats/link' +import { base64 } from 'multiformats/bases/base64' +import { base58btc } from 'multiformats/bases/base58' +import * as Digest from 'multiformats/hashes/digest' + +const storacha = Command.create('./bin.js') + +export const testStoracha = { + storacha: test(async (assert, { env }) => { + const { output } = await storacha.env(env.alice).join() + + assert.match(output, /Available Commands/) + }), + + 'storacha nosuchcmd': test(async (assert, context) => { + const { status, output } = await storacha + .args(['nosuchcmd']) + .env(context.env.alice) + .join() + .catch() + + assert.equal(status.code, 1) + assert.match(output, /Invalid command: nosuch/) + }), + + 'storacha --version': test(async (assert, context) => { + const { output, status } = await storacha.args(['--version']).join() + + assert.equal(status.code, 0) + assert.match(output, /storacha, \d.\d.\d/) + }), + + 'storacha whoami': test(async (assert) => { + const { output } = await storacha.args(['whoami']).join() + + assert.match(output, /^did:key:/) + }), +} + +export const testAccount = { + 'storacha account ls': test(async (assert, context) => { + const { output } = await storacha + .env(context.env.alice) + .args(['account ls']) + .join() + + assert.match(output, /has not been authorized yet/) + }), + + 'storacha login': test(async (assert, context) => { + const login = storacha + .args(['login', 'alice@web.mail']) + .env(context.env.alice) + .fork() + + const line = await login.error.lines().take().text() + assert.match(line, /please click the link sent/) + + // receive authorization request + const mail = await context.mail.take() + + // confirm authorization + await context.grantAccess(mail) + + const message = await login.output.text() + + assert.match(message ?? '', /authorized by did:mailto:web.mail:alice/) + }), + + 'storacha account list': test(async (assert, context) => { + await login(context) + + const { output } = await storacha + .env(context.env.alice) + .args(['account list']) + .join() + + assert.match(output, /did:mailto:web.mail:alice/) + }), +} + +export const testSpace = { + 'storacha space create': test(async (assert, context) => { + const command = storacha.args(['space', 'create']).env(context.env.alice).fork() + + const line = await command.output.take(1).text() + + assert.match(line, /What would you like to call this space/) + + await command.terminate().join().catch() + }), + + 'storacha space create home': test(async (assert, context) => { + const create = storacha + .args(['space', 'create', 'home']) + .env(context.env.alice) + .fork() + + const message = await create.output.take(1).text() + + const [prefix, key, suffix] = message.split('\n\n') + + assert.match(prefix, /secret recovery key/) + assert.match(suffix, /hit enter to reveal the key/) + + const secret = key.replaceAll(/[\s\n]+/g, '') + assert.equal(secret, 'β–ˆ'.repeat(secret.length), 'key is concealed') + + assert.ok(secret.length > 60, 'there are several words') + + await create.terminate().join().catch() + }), + + 'storacha space create home --no-caution': test(async (assert, context) => { + const create = storacha + .args(['space', 'create', 'home', '--no-caution']) + .env(context.env.alice) + .fork() + + const message = await create.output.lines().take(6).text() + + const lines = message.split('\n').filter((line) => line.trim() !== '') + const [prefix, key, suffix] = lines + + assert.match(prefix, /secret recovery key/) + assert.match(suffix, /billing account/, 'no heads up') + const words = key.trim().split(' ') + assert.ok( + words.every((word) => [...word].every((letter) => letter !== 'β–ˆ')), + 'key is revealed' + ) + assert.ok(words.length > 20, 'there are several words') + + await create.terminate().join().catch() + }), + + 'storacha space create my-space --no-recovery': test(async (assert, context) => { + const create = storacha + .args(['space', 'create', 'home', '--no-recovery']) + .env(context.env.alice) + .fork() + + const line = await create.output.lines().take().text() + + assert.match(line, /billing account/, 'no paper recovery') + + await create.terminate().join().catch() + }), + + 'storacha space create my-space --no-recovery (logged-in)': test( + async (assert, context) => { + await login(context) + + await selectPlan(context) + + const create = storacha + .args(['space', 'create', 'home', '--no-recovery']) + .env(context.env.alice) + .fork() + + const lines = await create.output.lines().take(2).text() + + assert.match(lines, /billing account is set/i) + + await create.terminate().join().catch() + } + ), + + 'storacha space create my-space --no-recovery (multiple accounts)': test( + async (assert, context) => { + await login(context, { email: 'alice@web.mail' }) + await login(context, { email: 'alice@email.me' }) + + const create = storacha + .args(['space', 'create', 'my-space', '--no-recovery']) + .env(context.env.alice) + .fork() + + const output = await create.output.take(2).text() + + assert.match( + output, + /choose an account you would like to use/, + 'choose account' + ) + + assert.ok(output.includes('alice@web.mail')) + assert.ok(output.includes('alice@email.me')) + + create.terminate() + } + ), + + 'storacha space create void --skip-paper --provision-as unknown@web.mail --skip-email': + test(async (assert, context) => { + const { output, error } = await storacha + .env(context.env.alice) + .args([ + 'space', + 'create', + 'home', + '--no-recovery', + '--customer', + 'unknown@web.mail', + '--no-account', + ]) + .join() + .catch() + + assert.match(output, /billing account/) + assert.match(output, /Skipped billing setup/) + assert.match(error, /not authorized by unknown@web\.mail/) + }), + + 'storacha space create home --no-recovery --customer alice@web.mail --no-account': + test(async (assert, context) => { + await login(context, { email: 'alice@web.mail' }) + await login(context, { email: 'alice@email.me' }) + + await selectPlan(context) + + const create = await storacha + .args([ + 'space', + 'create', + 'home', + '--no-recovery', + '--customer', + 'alice@web.mail', + '--no-account', + ]) + .env(context.env.alice) + .join() + + assert.match(create.output, /Billing account is set/) + + const info = await storacha + .args(['space', 'info']) + .env(context.env.alice) + .join() + + assert.match(info.output, /Providers: did:web:/) + }), + + 'storacha space create home --no-recovery --customer alice@web.mail --account alice@web.mail': + test(async (assert, context) => { + const email = 'alice@web.mail' + await login(context, { email }) + await selectPlan(context, { email }) + + const { output } = await storacha + .args([ + 'space', + 'create', + 'home', + '--no-recovery', + '--customer', + email, + '--account', + email, + ]) + .env(context.env.alice) + .join() + + assert.match(output, /account is authorized/i) + + const result = await context.delegationsStorage.find({ + audience: DIDMailto.fromEmail(email), + }) + + assert.ok( + result.ok?.find((d) => d.capabilities[0].can === '*'), + 'account has been delegated access to the space' + ) + }), + + 'storacha space create home --no-recovery (blocks until plan is selected)': test( + async (assert, context) => { + const email = 'alice@web.mail' + await login(context, { email }) + + context.plansStorage.get = async () => { + return { + ok: { product: 'did:web:free.web3.storage', updatedAt: 'now' }, + } + } + + const { output, error } = await storacha + .env(context.env.alice) + .args(['space', 'create', 'home', '--no-recovery']) + .join() + + assert.match(output, /billing account is set/i) + assert.match(error, /wait.*plan.*select/i) + } + ), + + 'storacha space add': test(async (assert, context) => { + const { env } = context + + const spaceDID = await loginAndCreateSpace(context, { env: env.alice }) + + const whosBob = await storacha.args(['whoami']).env(env.bob).join() + + const bobDID = SpaceDID.from(whosBob.output.trim()) + + const proofPath = path.join( + os.tmpdir(), + `storacha-cli-test-delegation-${Date.now()}` + ) + + await storacha + .args([ + 'delegation', + 'create', + bobDID, + '-c', + 'store/*', + 'upload/*', + '--output', + proofPath, + ]) + .env(env.alice) + .join() + + const listNone = await storacha.args(['space', 'ls']).env(env.bob).join() + assert.ok(!listNone.output.includes(spaceDID)) + + const add = await storacha.args(['space', 'add', proofPath]).env(env.bob).join() + assert.equal(add.output.trim(), spaceDID) + + const listSome = await storacha.args(['space', 'ls']).env(env.bob).join() + assert.ok(listSome.output.includes(spaceDID)) + }), + + 'storacha space add `base64 proof car`': test(async (assert, context) => { + const { env } = context + const spaceDID = await loginAndCreateSpace(context, { env: env.alice }) + const whosBob = await storacha.args(['whoami']).env(env.bob).join() + const bobDID = SpaceDID.from(whosBob.output.trim()) + const res = await storacha + .args([ + 'delegation', + 'create', + bobDID, + '-c', + 'store/*', + 'upload/*', + '--base64' + ]) + .env(env.alice) + .join() + + const listNone = await storacha.args(['space', 'ls']).env(env.bob).join() + assert.ok(!listNone.output.includes(spaceDID)) + + const add = await storacha.args(['space', 'add', res.output]).env(env.bob).join() + assert.equal(add.output.trim(), spaceDID) + + const listSome = await storacha.args(['space', 'ls']).env(env.bob).join() + assert.ok(listSome.output.includes(spaceDID)) + }), + + 'storacha space add invalid/path': test(async (assert, context) => { + const fail = await storacha + .args(['space', 'add', 'djcvbii']) + .env(context.env.alice) + .join() + .catch() + + assert.ok(!fail.status.success()) + assert.match(fail.error, /failed to read proof/) + }), + + 'storacha space add not-a-car.gif': test(async (assert, context) => { + const fail = await storacha + .args(['space', 'add', './package.json']) + .env(context.env.alice) + .join() + .catch() + + assert.equal(fail.status.success(), false) + assert.match(fail.error, /failed to parse proof/) + }), + + 'storacha space add empty.car': test(async (assert, context) => { + const fail = await storacha + .args(['space', 'add', './test/fixtures/empty.car']) + .env(context.env.alice) + .join() + .catch() + + assert.equal(fail.status.success(), false) + assert.match(fail.error, /failed to import proof/) + }), + + 'storacha space ls': test(async (assert, context) => { + const emptyList = await storacha + .args(['space', 'ls']) + .env(context.env.alice) + .join() + + const spaceDID = await loginAndCreateSpace(context) + + const spaceList = await storacha + .args(['space', 'ls']) + .env(context.env.alice) + .join() + + assert.ok(!emptyList.output.includes(spaceDID)) + assert.ok(spaceList.output.includes(spaceDID)) + }), + + 'storacha space use': test(async (assert, context) => { + const spaceDID = await loginAndCreateSpace(context, { + env: context.env.alice, + }) + + const listDefault = await storacha + .args(['space', 'ls']) + .env(context.env.alice) + .join() + assert.ok(listDefault.output.includes(`* ${spaceDID}`)) + + const spaceName = 'laundry' + + const newSpaceDID = await createSpace(context, { name: spaceName }) + + const listNewDefault = await storacha + .args(['space', 'ls']) + .env(context.env.alice) + .join() + + assert.equal( + listNewDefault.output.includes(`* ${spaceDID}`), + false, + 'old space is not default' + ) + assert.equal( + listNewDefault.output.includes(`* ${newSpaceDID}`), + true, + 'new space is the default' + ) + + assert.equal( + listNewDefault.output.includes(spaceDID), + true, + 'old space is still listed' + ) + + await storacha.args(['space', 'use', spaceDID]).env(context.env.alice).join() + const listSetDefault = await storacha + .args(['space', 'ls']) + .env(context.env.alice) + .join() + + assert.equal( + listSetDefault.output.includes(`* ${spaceDID}`), + true, + 'spaceDID is default' + ) + assert.equal( + listSetDefault.output.includes(`* ${newSpaceDID}`), + false, + 'new space is not default' + ) + + await storacha.args(['space', 'use', spaceName]).env(context.env.alice).join() + const listNamedDefault = await storacha + .args(['space', 'ls']) + .env(context.env.alice) + .join() + + assert.equal(listNamedDefault.output.includes(`* ${spaceDID}`), false) + assert.equal(listNamedDefault.output.includes(`* ${newSpaceDID}`), true) + }), + + 'storacha space use did:key:unknown': test(async (assert, context) => { + const space = await Signer.generate() + + const useSpace = await storacha + .args(['space', 'use', space.did()]) + .env(context.env.alice) + .join() + .catch() + + assert.match(useSpace.error, /space not found/) + }), + + 'storacha space use notfound': test(async (assert, context) => { + const useSpace = await storacha + .args(['space', 'use', 'notfound']) + .env(context.env.alice) + .join() + .catch() + + assert.match(useSpace.error, /space not found/) + }), + + 'storacha space info': test(async (assert, context) => { + const spaceDID = await loginAndCreateSpace(context, { + customer: null, + }) + + /** @type {import('@storacha/client/types').DID<'web'>} */ + const providerDID = 'did:web:test.upload.storacha.network' + + const infoWithoutProvider = await storacha + .args(['space', 'info']) + .env(context.env.alice) + .join() + + assert.match( + infoWithoutProvider.output, + pattern`DID: ${spaceDID}\nProviders: .*none`, + 'space has no providers' + ) + + assert.match( + infoWithoutProvider.output, + pattern`Name: home`, + 'space name is set' + ) + + await Test.provisionSpace(context, { + space: spaceDID, + account: 'did:mailto:web.mail:alice', + provider: providerDID, + }) + + const infoWithProvider = await storacha + .args(['space', 'info']) + .env(context.env.alice) + .join() + + assert.match( + infoWithProvider.output, + pattern`DID: ${spaceDID}\nProviders: .*${providerDID}`, + 'added provider shows up in the space info' + ) + + const infoWithProviderJson = await storacha + .args(['space', 'info', '--json']) + .env(context.env.alice) + .join() + + assert.deepEqual(JSON.parse(infoWithProviderJson.output), { + did: spaceDID, + providers: [providerDID], + name: 'home' + }) + }), + + 'storacha space provision --coupon': test(async (assert, context) => { + const spaceDID = await loginAndCreateSpace(context, { customer: null }) + + assert.deepEqual( + await context.provisionsStorage.getStorageProviders(spaceDID), + { ok: [] }, + 'space has no providers yet' + ) + + const archive = await createCustomerSession(context) + context.router['/proof.car'] = async () => { + return { + status: 200, + headers: { 'content-type': 'application/car' }, + body: archive, + } + } + + const url = new URL('/proof.car', context.serverURL) + const provision = await storacha + .env(context.env.alice) + .args(['space', 'provision', '--coupon', url.href]) + .join() + + assert.match(provision.output, /Billing account is set/) + + const info = await storacha.env(context.env.alice).args(['space', 'info']).join() + + assert.match( + info.output, + pattern`Providers: ${context.service.did()}`, + 'space got provisioned' + ) + }), +} + +export const testStorachaUp = { + 'storacha up': test(async (assert, context) => { + const email = 'alice@web.mail' + await login(context, { email }) + await selectPlan(context, { email }) + + const create = await storacha + .args([ + 'space', + 'create', + 'home', + '--no-recovery', + '--no-account', + '--customer', + email, + ]) + .env(context.env.alice) + .join() + + assert.ok(create.status.success()) + + const up = await storacha + .args(['up', 'test/fixtures/pinpie.jpg']) + .env(context.env.alice) + .join() + + assert.match( + up.output, + /bafybeiajdopsmspomlrpaohtzo5sdnpknbolqjpde6huzrsejqmvijrcea/ + ) + assert.match(up.error, /Stored 1 file/) + }), + + 'storacha up --no-wrap': test(async (assert, context) => { + const email = 'alice@web.mail' + await login(context, { email }) + await selectPlan(context, { email }) + + const create = await storacha + .args([ + 'space', + 'create', + 'home', + '--no-recovery', + '--no-account', + '--customer', + email, + ]) + .env(context.env.alice) + .join() + + assert.ok(create.status.success()) + + const up = await storacha + .args(['up', 'test/fixtures/pinpie.jpg', '--no-wrap']) + .env(context.env.alice) + .join() + + assert.match( + up.output, + /bafkreiajkbmpugz75eg2tmocmp3e33sg5kuyq2amzngslahgn6ltmqxxfa/ + ) + assert.match(up.error, /Stored 1 file/) + }), + + 'storacha up --wrap false': test(async (assert, context) => { + const email = 'alice@web.mail' + await login(context, { email }) + await selectPlan(context, { email }) + + const create = await storacha + .args([ + 'space', + 'create', + 'home', + '--no-recovery', + '--no-account', + '--customer', + email, + ]) + .env(context.env.alice) + .join() + + assert.ok(create.status.success()) + + const up = await storacha + .args(['up', 'test/fixtures/pinpie.jpg', '--wrap', 'false']) + .env(context.env.alice) + .join() + + assert.match( + up.output, + /bafkreiajkbmpugz75eg2tmocmp3e33sg5kuyq2amzngslahgn6ltmqxxfa/ + ) + assert.match(up.error, /Stored 1 file/) + }), + + 'storacha up --car': test(async (assert, context) => { + const email = 'alice@web.mail' + await login(context, { email }) + await selectPlan(context, { email }) + await storacha + .args([ + 'space', + 'create', + 'home', + '--no-recovery', + '--no-account', + '--customer', + email, + ]) + .env(context.env.alice) + .join() + + const up = await storacha + .args(['up', '--car', 'test/fixtures/pinpie.car']) + .env(context.env.alice) + .join() + + assert.match( + up.output, + /bafkreiajkbmpugz75eg2tmocmp3e33sg5kuyq2amzngslahgn6ltmqxxfa/ + ) + assert.match(up.error, /Stored 1 file/) + }), + + 'storacha ls': test(async (assert, context) => { + await loginAndCreateSpace(context) + + const list0 = await storacha.args(['ls']).env(context.env.alice).join() + assert.match(list0.output, /No uploads in space/) + + await storacha + .args(['up', 'test/fixtures/pinpie.jpg']) + .env(context.env.alice) + .join() + + // wait a second for invocation to get a different expiry + await new Promise((resolve) => setTimeout(resolve, 1000)) + + const list1 = await storacha.args(['ls', '--json']).env(context.env.alice).join() + + assert.ok(dagJSON.parse(list1.output)) + }), + + 'storacha remove': test(async (assert, context) => { + await loginAndCreateSpace(context) + + const up = await storacha + .args(['up', 'test/fixtures/pinpie.jpg']) + .env(context.env.alice) + .join() + + assert.match( + up.output, + /bafybeiajdopsmspomlrpaohtzo5sdnpknbolqjpde6huzrsejqmvijrcea/ + ) + + const rm = await storacha + .args([ + 'rm', + 'bafybeiajdopsmspomlrpaohtzo5sdnpknbolqjpde6huzrsejqmvijrcea', + ]) + .env(context.env.alice) + .join() + .catch() + + assert.equal(rm.status.code, 0) + assert.equal(rm.output, '') + }), + + 'storacha remove - no such upload': test(async (assert, context) => { + await loginAndCreateSpace(context) + + const rm = await storacha + .args([ + 'rm', + 'bafybeih2k7ughhfwedltjviunmn3esueijz34snyay77zmsml5w24tqamm', + '--shards', + ]) + .env(context.env.alice) + .join() + .catch() + + assert.equal(rm.status.code, 1) + assert.match( + rm.error, + /not found/ + ) + }), +} + +export const testDelegation = { + 'storacha delegation create -c store/* --output file/path': test( + async (assert, context) => { + const env = context.env.alice + const { bob } = Test + + const spaceDID = await loginAndCreateSpace(context) + + const proofPath = path.join( + os.tmpdir(), + `storacha-cli-test-delegation-${Date.now()}` + ) + + await storacha + .args([ + 'delegation', + 'create', + bob.did(), + '-c', + 'store/*', + '--output', + proofPath, + ]) + .env(env) + .join() + + const reader = await CarReader.fromIterable( + fs.createReadStream(proofPath) + ) + const blocks = [] + for await (const block of reader.blocks()) { + blocks.push(block) + } + + // @ts-expect-error + const delegation = importDAG(blocks) + assert.equal(delegation.audience.did(), bob.did()) + assert.equal(delegation.capabilities[0].can, 'store/*') + assert.equal(delegation.capabilities[0].with, spaceDID) + } + ), + + 'storacha delegation create': test(async (assert, context) => { + const env = context.env.alice + const { bob } = Test + await loginAndCreateSpace(context) + + const delegate = await storacha + .args(['delegation', 'create', bob.did()]) + .env(env) + .join() + + // TODO: Test output after we switch to Delegation.archive() / Delegation.extract() + assert.equal(delegate.status.success(), true) + }), + + 'storacha delegation create -c store/add -c upload/add --base64': test( + async (assert, context) => { + const env = context.env.alice + const { bob } = Test + const spaceDID = await loginAndCreateSpace(context) + const res = await storacha + .args([ + 'delegation', + 'create', + bob.did(), + '-c', + 'store/add', + '-c', + 'upload/add', + '--base64' + ]) + .env(env) + .join() + + assert.equal(res.status.success(), true) + + const identityCid = parseLink(res.output, base64) + const reader = await CarReader.fromBytes(identityCid.multihash.digest) + const blocks = [] + for await (const block of reader.blocks()) { + blocks.push(block) + } + + // @ts-expect-error + const delegation = importDAG(blocks) + assert.equal(delegation.audience.did(), bob.did()) + assert.equal(delegation.capabilities[0].can, 'store/add') + assert.equal(delegation.capabilities[0].with, spaceDID) + assert.equal(delegation.capabilities[1].can, 'upload/add') + assert.equal(delegation.capabilities[1].with, spaceDID) + } + ), + + 'storacha delegation ls --json': test(async (assert, context) => { + const { mallory } = Test + + const spaceDID = await loginAndCreateSpace(context) + + // delegate to mallory + await storacha + .args(['delegation', 'create', mallory.did(), '-c', 'store/*']) + .env(context.env.alice) + .join() + + const list = await storacha + .args(['delegation', 'ls', '--json']) + .env(context.env.alice) + .join() + + const data = JSON.parse(list.output) + + assert.equal(data.audience, mallory.did()) + assert.equal(data.capabilities.length, 1) + assert.equal(data.capabilities[0].with, spaceDID) + assert.equal(data.capabilities[0].can, 'store/*') + }), + + 'storacha delegation revoke': test(async (assert, context) => { + const env = context.env.alice + const { mallory } = Test + await loginAndCreateSpace(context) + + const delegationPath = `${os.tmpdir()}/delegation-${Date.now()}.ucan` + await storacha + .args([ + 'delegation', + 'create', + mallory.did(), + '-c', + 'store/*', + 'upload/*', + '-o', + delegationPath, + ]) + .env(env) + .join() + + const list = await storacha + .args(['delegation', 'ls', '--json']) + .env(context.env.alice) + .join() + const { cid } = JSON.parse(list.output) + + // alice should be able to revoke the delegation she just created + const revoke = await storacha + .args(['delegation', 'revoke', cid]) + .env(context.env.alice) + .join() + + assert.match(revoke.output, pattern`delegation ${cid} revoked`) + + await loginAndCreateSpace(context, { + env: context.env.bob, + customer: 'bob@super.host', + }) + + // bob should not be able to because he doesn't have a copy of the delegation + const fail = await storacha + .args(['delegation', 'revoke', cid]) + .env(context.env.bob) + .join() + .catch() + + assert.match( + fail.error, + pattern`Error: revoking ${cid}: could not find delegation ${cid}` + ) + + // but if bob passes the delegation manually, it should succeed - we don't + // validate that bob is able to issue the revocation, it simply won't apply + // if it's not legitimate + + const pass = await storacha + .args(['delegation', 'revoke', cid, '-p', delegationPath]) + .env(context.env.bob) + .join() + + assert.match(pass.output, pattern`delegation ${cid} revoked`) + }), +} + +export const testProof = { + 'storacha proof add': test(async (assert, context) => { + const { env } = context + + const spaceDID = await loginAndCreateSpace(context, { env: env.alice }) + const whoisbob = await storacha.args(['whoami']).env(env.bob).join() + const bobDID = DID.parse(whoisbob.output.trim()).did() + const proofPath = path.join( + os.tmpdir(), + `storacha-cli-test-delegation-${Date.now()}` + ) + + await storacha + .args([ + 'delegation', + 'create', + bobDID, + '-c', + 'store/*', + '--output', + proofPath, + ]) + .env(env.alice) + .join() + + const listNone = await storacha.args(['proof', 'ls']).env(env.bob).join() + assert.ok(!listNone.output.includes(spaceDID)) + + const addProof = await storacha + .args(['proof', 'add', proofPath]) + .env(env.bob) + .join() + + assert.ok(addProof.output.includes(`with: ${spaceDID}`)) + const listProof = await storacha.args(['proof', 'ls']).env(env.bob).join() + assert.ok(listProof.output.includes(spaceDID)) + }), + 'storacha proof add notfound': test(async (assert, context) => { + const proofAdd = await storacha + .args(['proof', 'add', 'djcvbii']) + .env(context.env.alice) + .join() + .catch() + + assert.equal(proofAdd.status.success(), false) + assert.match(proofAdd.error, /failed to read proof/) + }), + 'storacha proof add not-car.json': test(async (assert, context) => { + const proofAdd = await storacha + .args(['proof', 'add', './package.json']) + .env(context.env.alice) + .join() + .catch() + + assert.equal(proofAdd.status.success(), false) + assert.match(proofAdd.error, /failed to parse proof/) + }), + 'storacha proof add invalid.car': test(async (assert, context) => { + const proofAdd = await storacha + .args(['proof', 'add', './test/fixtures/empty.car']) + .env(context.env.alice) + .join() + .catch() + + assert.equal(proofAdd.status.success(), false) + assert.match(proofAdd.error, /failed to import proof/) + }), + 'storacha proof ls': test(async (assert, context) => { + const { env } = context + const spaceDID = await loginAndCreateSpace(context, { env: env.alice }) + const whoisalice = await storacha.args(['whoami']).env(env.alice).join() + const aliceDID = DID.parse(whoisalice.output.trim()).did() + + const whoisbob = await storacha.args(['whoami']).env(env.bob).join() + const bobDID = DID.parse(whoisbob.output.trim()).did() + + const proofPath = path.join(os.tmpdir(), `storacha-cli-test-proof-${Date.now()}`) + await storacha + .args([ + 'delegation', + 'create', + '-c', + 'store/*', + bobDID, + '--output', + proofPath, + ]) + .env(env.alice) + .join() + + await storacha.args(['space', 'add', proofPath]).env(env.bob).join() + + const proofList = await storacha + .args(['proof', 'ls', '--json']) + .env(env.bob) + .join() + const proofData = JSON.parse(proofList.output) + assert.equal(proofData.iss, aliceDID) + assert.equal(proofData.att.length, 1) + assert.equal(proofData.att[0].with, spaceDID) + assert.equal(proofData.att[0].can, 'store/*') + }), +} + +export const testBlob = { + 'storacha can blob add': test(async (assert, context) => { + await loginAndCreateSpace(context) + + const { error } = await storacha + .args(['can', 'blob', 'add', 'test/fixtures/pinpie.jpg']) + .env(context.env.alice) + .join() + + assert.match(error, /Stored zQm/) + }), + + 'storacha can blob ls': test(async (assert, context) => { + await loginAndCreateSpace(context) + + await storacha + .args(['can', 'blob', 'add', 'test/fixtures/pinpie.jpg']) + .env(context.env.alice) + .join() + + const list = await storacha + .args(['can', 'blob', 'ls', '--json']) + .env(context.env.alice) + .join() + + assert.ok(dagJSON.parse(list.output)) + }), + + 'storacha can blob rm': test(async (assert, context) => { + await loginAndCreateSpace(context) + + await storacha + .args(['can', 'blob', 'add', 'test/fixtures/pinpie.jpg']) + .env(context.env.alice) + .join() + + const list = await storacha + .args(['can', 'blob', 'ls', '--json']) + .env(context.env.alice) + .join() + + const digest = Digest.decode(dagJSON.parse(list.output).blob.digest) + + const remove = await storacha + .args(['can', 'blob', 'rm', base58btc.encode(digest.bytes)]) + .env(context.env.alice) + .join() + + assert.match(remove.error, /Removed zQm/) + }), +} + +export const testCan = { + 'storacha can upload add': test(async (assert, context) => { + await loginAndCreateSpace(context) + + const carPath = 'test/fixtures/pinpie.car' + const reader = await CarReader.fromBytes( + await fs.promises.readFile(carPath) + ) + const root = (await reader.getRoots())[0]?.toString() + assert.ok(root) + + const canStore = await storacha + .args(['can', 'blob', 'add', carPath]) + .env(context.env.alice) + .join() + + assert.match(canStore.error, /Stored zQm/) + + const digest = canStore.error.trim().split('\n')[2].split(' ')[2] + const shard = Link.create(0x0202, Digest.decode(base58btc.decode(digest))) + const canUpload = await storacha + .args(['can', 'upload', 'add', root, shard.toString()]) + .env(context.env.alice) + .join() + + assert.match(canUpload.error, /Upload added/) + }), + + 'storacha can upload ls': test(async (assert, context) => { + await loginAndCreateSpace(context) + + await storacha + .args(['up', 'test/fixtures/pinpie.jpg']) + .env(context.env.alice) + .join() + + const list = await storacha + .args(['can', 'upload', 'ls', '--json']) + .env(context.env.alice) + .join() + + assert.ok(dagJSON.parse(list.output)) + }), + 'storacha can upload rm': test(async (assert, context) => { + await loginAndCreateSpace(context) + + const up = await storacha + .args(['up', 'test/fixtures/pinpie.jpg']) + .env(context.env.alice) + .join() + + assert.match( + up.output, + /bafybeiajdopsmspomlrpaohtzo5sdnpknbolqjpde6huzrsejqmvijrcea/ + ) + + const noPath = await storacha + .args(['can', 'upload', 'rm']) + .env(context.env.alice) + .join() + .catch() + + assert.match(noPath.error, /Insufficient arguments/) + + const invalidCID = await storacha + .args(['can', 'upload', 'rm', 'foo']) + .env(context.env.alice) + .join() + .catch() + + assert.match(invalidCID.error, /not a CID/) + + const rm = await storacha + .args([ + 'can', + 'upload', + 'rm', + 'bafybeiajdopsmspomlrpaohtzo5sdnpknbolqjpde6huzrsejqmvijrcea', + ]) + .env(context.env.alice) + .join() + + assert.ok(rm.status.success()) + }), + 'can filecoin info with not found': test(async (assert, context) => { + await loginAndCreateSpace(context) + + const up = await storacha + .args(['up', 'test/fixtures/pinpie.jpg', '--verbose']) + .env(context.env.alice) + .join() + const pieceCid = up.error.split('Piece CID: ')[1].split(`\n`)[0] + + const { error } = await storacha + .args(['can', 'filecoin', 'info', pieceCid, '--json']) + .env(context.env.alice) + .join() + .catch() + // no piece will be available right away + assert.ok(error) + assert.ok(error.includes('not found')) + }), +} + +export const testPlan = { + 'storacha plan get': test(async (assert, context) => { + await login(context) + const notFound = await storacha + .args(['plan', 'get']) + .env(context.env.alice) + .join() + + assert.match(notFound.output, /no plan/i) + + await selectPlan(context) + + // wait a second for invocation to get a different expiry + await new Promise((resolve) => setTimeout(resolve, 1000)) + + const plan = await storacha.args(['plan', 'get']).env(context.env.alice).join() + assert.match(plan.output, /did:web:free.web3.storage/) + }), +} + +export const testKey = { + 'storacha key create': test(async (assert) => { + const res = await storacha.args(['key', 'create', '--json']).join() + const key = ED25519.parse(JSON.parse(res.output).key) + assert.ok(key.did().startsWith('did:key')) + }), +} + +export const testBridge = { + 'storacha bridge generate-tokens': test(async (assert, context) => { + const spaceDID = await loginAndCreateSpace(context) + const res = await storacha.args(['bridge', 'generate-tokens', spaceDID]).join() + assert.match(res.output, /X-Auth-Secret header: u/) + assert.match(res.output, /Authorization header: u/) + }), +} + +/** + * @param {Test.Context} context + * @param {object} options + * @param {string} [options.email] + * @param {Record} [options.env] + */ +export const login = async ( + context, + { email = 'alice@web.mail', env = context.env.alice } = {} +) => { + const login = storacha.env(env).args(['login', email]).fork() + + // wait for the new process to print the status + await login.error.lines().take().text() + + // receive authorization request + const message = await context.mail.take() + + // confirm authorization + await context.grantAccess(message) + + return await login.join() +} + +/** + * @typedef {import('@storacha/client/types').ProviderDID} Plan + * + * @param {Test.Context} context + * @param {object} options + * @param {DIDMailto.EmailAddress} [options.email] + * @param {string} [options.billingID] + * @param {Plan} [options.plan] + */ +export const selectPlan = async ( + context, + { email = 'alice@web.mail', billingID = 'test:cus_alice', plan = 'did:web:free.web3.storage' } = {} +) => { + const customer = DIDMailto.fromEmail(email) + Result.try(await context.plansStorage.initialize(customer, billingID, plan)) +} + +/** + * @param {Test.Context} context + * @param {object} options + * @param {DIDMailto.EmailAddress|null} [options.customer] + * @param {string} [options.name] + * @param {Record} [options.env] + */ +export const createSpace = async ( + context, + { customer = 'alice@web.mail', name = 'home', env = context.env.alice } = {} +) => { + const { output } = await storacha + .args([ + 'space', + 'create', + name, + '--no-recovery', + '--no-account', + ...(customer ? ['--customer', customer] : ['--no-customer']), + ]) + .env(env) + .join() + + const [did] = match(/(did:key:\w+)/, output) + + return SpaceDID.from(did) +} + +/** + * @param {Test.Context} context + * @param {object} options + * @param {DIDMailto.EmailAddress} [options.email] + * @param {DIDMailto.EmailAddress|null} [options.customer] + * @param {string} [options.name] + * @param {Plan} [options.plan] + * @param {Record} [options.env] + */ +export const loginAndCreateSpace = async ( + context, + { + email = 'alice@web.mail', + customer = email, + name = 'home', + plan = 'did:web:free.web3.storage', + env = context.env.alice, + } = {} +) => { + await login(context, { email, env }) + + if (customer != null && plan != null) { + await selectPlan(context, { email: customer, plan }) + } + + return createSpace(context, { customer, name, env }) +} + +/** + * @param {Test.Context} context + * @param {object} options + * @param {string} [options.password] + */ +export const createCustomerSession = async ( + context, + { password = '' } = {} +) => { + // Derive delegation audience from the password + const { digest } = await sha256.digest(new TextEncoder().encode(password)) + const audience = await ED25519.derive(digest) + + // Generate the agent that will be authorized to act on behalf of the customer + const agent = await ed25519.generate() + + const customer = Absentee.from({ id: 'did:mailto:web.mail:workshop' }) + + // First we create delegation from the customer to the agent that authorizing + // it to perform `provider/add` on their behalf. + const delegation = await delegate({ + issuer: customer, + audience: agent, + capabilities: [ + { + with: 'ucan:*', + can: '*', + }, + ], + expiration: Infinity, + }) + + // Then we create an attestation from the service to proof that agent has + // been authorized + const attestation = await UCAN.attest.delegate({ + issuer: context.service, + audience: agent, + with: context.service.did(), + nb: { proof: delegation.cid }, + expiration: delegation.expiration, + }) + + // Finally we create a short lived session that authorizes the audience to + // provider/add with their billing account. + const session = await Provider.add.delegate({ + issuer: agent, + audience, + with: customer.did(), + proofs: [delegation, attestation], + }) + + return Result.try(await session.archive()) +} diff --git a/packages/cli/test/fixtures/empty.car b/packages/cli/test/fixtures/empty.car new file mode 100644 index 000000000..3e0962954 --- /dev/null +++ b/packages/cli/test/fixtures/empty.car @@ -0,0 +1 @@ +οΏ½erootsοΏ½gversion \ No newline at end of file diff --git a/packages/cli/test/fixtures/pinpie.car b/packages/cli/test/fixtures/pinpie.car new file mode 100644 index 0000000000000000000000000000000000000000..779fbef4d9af0ff2fe61ee67f923cdc1f4207776 GIT binary patch literal 47972 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 z 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 X+q~L;dvDE8Z8YX)mP+V7?{fdy40U(N literal 0 HcmV?d00001 diff --git a/packages/cli/test/fixtures/pinpie.jpg b/packages/cli/test/fixtures/pinpie.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1ea4a2c92207caf09ce523c5a3858298619da14d GIT binary patch literal 47874 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

f>f1(pH)Rf`Tk${?LUZ_q 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 input.account + * @param {API.DID<'web'>} input.provider + */ +export const provisionSpace = async (context, { space, account, provider }) => { + // add a provider for this space + return await context.provisionsStorage.put({ + cause: /** @type {*} */ ({}), + consumer: space, + customer: account, + provider, + }) +} + +/** + * @typedef {UcantoServerTestContext & { + * server: import('./http-server').TestingServer['server'] + * receiptsServer: import('./receipt-http-server.js').TestingServer['server'] + * router: import('./http-server').Router + * env: { alice: Record, bob: Record } + * serverURL: URL + * }} Context + * + * @returns {Promise} + */ +export const setup = async () => { + const context = await createContext({ http }) + const { server, serverURL, router } = await createHTTPServer({ + '/': context.connection.channel.request.bind(context.connection.channel), + }) + const { server: receiptsServer, serverURL: receiptsServerUrl } = await createReceiptsServer() + + return Object.assign(context, { + server, + serverURL, + receiptsServer, + router, + serverRouter: router, + env: { + alice: createEnv({ + storeName: `storacha-cli-test-alice-${context.service.did()}`, + servicePrincipal: context.service, + serviceURL: serverURL, + receiptsEndpoint: new URL('receipt', receiptsServerUrl), + }), + bob: createEnv({ + storeName: `storacha-cli-test-bob-${context.service.did()}`, + servicePrincipal: context.service, + serviceURL: serverURL, + receiptsEndpoint: new URL('receipt', receiptsServerUrl), + }), + }, + }) +} + +/** + * @param {Context} context + */ +export const teardown = async (context) => { + await cleanupContext(context) + context.server.close() + context.receiptsServer.close() + + const stores = [ + context.env.alice.STORACHA_STORE_NAME, + context.env.bob.STORACHA_STORE_NAME, + ] + + await Promise.all( + stores.map(async (name) => { + const { path } = new StoreConf({ profile: name }) + try { + await FS.rm(path) + } catch (/** @type {any} */ err) { + if (err.code === 'ENOENT') return // is ok maybe it wasn't used in the test + throw err + } + }) + ) +} + +/** + * @param {(assert: import('entail').Assert, context: Context) => unknown} unit + * @returns {import('entail').Test} + */ +export const test = (unit) => async (assert) => { + const context = await setup() + try { + await unit(assert, context) + } finally { + await teardown(context) + } +} diff --git a/packages/cli/test/helpers/env.js b/packages/cli/test/helpers/env.js new file mode 100644 index 000000000..c8024b436 --- /dev/null +++ b/packages/cli/test/helpers/env.js @@ -0,0 +1,19 @@ +/** + * @param {object} [options] + * @param {import('@ucanto/interface').Principal} [options.servicePrincipal] + * @param {URL} [options.serviceURL] + * @param {string} [options.storeName] + * @param {URL} [options.receiptsEndpoint] + */ +export function createEnv(options = {}) { + const { servicePrincipal, serviceURL, storeName, receiptsEndpoint } = options + const env = { STORACHA_STORE_NAME: storeName ?? 'storacha-test' } + if (servicePrincipal && serviceURL) { + Object.assign(env, { + STORACHA_SERVICE_DID: servicePrincipal.did(), + STORACHA_SERVICE_URL: serviceURL.toString(), + STORACHA_RECEIPTS_URL: receiptsEndpoint?.toString() + }) + } + return env +} diff --git a/packages/cli/test/helpers/http-server.js b/packages/cli/test/helpers/http-server.js new file mode 100644 index 000000000..375d2e834 --- /dev/null +++ b/packages/cli/test/helpers/http-server.js @@ -0,0 +1,61 @@ +import http from 'node:http' +import { once } from 'node:events' + +/** + * @typedef {import('@ucanto/interface').HTTPRequest} HTTPRequest + * @typedef {import('@ucanto/server').HTTPResponse} HTTPResponse + * @typedef {Record PromiseLike|HTTPResponse>} Router + * + * @typedef {{ + * server: http.Server + * serverURL: URL + * router: Router + * }} TestingServer + */ + +/** + * @param {Router} router + * @returns {Promise} + */ +export async function createServer(router) { + /** + * @param {http.IncomingMessage} request + * @param {http.ServerResponse} response + */ + const listener = async (request, response) => { + const chunks = [] + for await (const chunk of request) { + chunks.push(chunk) + } + + const handler = router[request.url ?? '/'] + if (!handler) { + response.writeHead(404) + response.end() + return undefined + } + + const { headers, body } = await handler({ + headers: /** @type {Readonly>} */ ( + request.headers + ), + body: Buffer.concat(chunks), + }) + + response.writeHead(200, headers) + response.write(body) + response.end() + return undefined + } + + const server = http.createServer(listener).listen() + + await once(server, 'listening') + + return { + server, + router, + // @ts-expect-error + serverURL: new URL(`http://127.0.0.1:${server.address().port}`), + } +} diff --git a/packages/cli/test/helpers/process.js b/packages/cli/test/helpers/process.js new file mode 100644 index 000000000..385410d13 --- /dev/null +++ b/packages/cli/test/helpers/process.js @@ -0,0 +1,178 @@ +import Process from 'node:child_process' +import { TextDecoder } from 'node:util' +import { ByteStream } from './stream.js' + +/** + * @typedef {object} Command + * @property {string} program + * @property {string[]} args + * @property {Record} env + * + * @typedef {object} Outcome + * @property {Status} status + * @property {string} output + * @property {string} error + * + * + * @param {string} program + */ +export const create = (program) => + new CommandView({ + program, + args: [], + env: process.env, + }) + +class CommandView { + /** + * @param {Command} model + */ + constructor(model) { + this.model = model + } + + /** + * @param {string[]} args + */ + args(args) { + return new CommandView({ + ...this.model, + args: [...this.model.args, ...args], + }) + } + + /** + * @param {Record} env + */ + env(env) { + return new CommandView({ + ...this.model, + env: { ...this.model.env, ...env }, + }) + } + + fork() { + return fork(this.model) + } + + join() { + return join(this.model) + } +} + +/** + * @param {Command} command + */ +export const fork = (command) => { + const process = Process.spawn(command.program, command.args, { + env: command.env, + }) + return new Fork(process) +} + +/** + * @param {Command} command + */ +export const join = (command) => fork(command).join() + +class Status { + /** + * @param {{code:number, signal?: void}|{signal:NodeJS.Signals, code?:void}} model + */ + constructor(model) { + this.model = model + } + + success() { + return this.model.code === 0 + } + + get code() { + return this.model.code ?? null + } + get signal() { + return this.model.signal ?? null + } +} + +class Fork { + /** + * @param {Process.ChildProcess} process + */ + constructor(process) { + this.process = process + this.output = ByteStream.from(process.stdout ?? []) + + this.error = ByteStream.from(process.stderr ?? []) + } + join() { + return new Join(this) + } + terminate() { + this.process.kill() + return this + } +} + +class Join { + /** + * @param {Fork} fork + */ + constructor(fork) { + this.fork = fork + this.output = '' + this.error = '' + + void readInto(fork.output.reader(), this, 'output') + void readInto(fork.error.reader(), this, 'error') + } + + /** + * @param {(ok: Outcome) => unknown} succeed + * @param {(error: Outcome) => unknown} fail + */ + then(succeed, fail) { + this.fork.process.once('close', (code, signal) => { + const status = + signal !== null + ? new Status({ signal }) + : new Status({ code: /** @type {number} */ (code) }) + + const { output, error } = this + const outcome = { status, output, error } + if (status.success()) { + succeed(outcome) + } else { + fail( + Object.assign( + new Error(`command failed with status ${status.code}\n ${error}`), + outcome + ) + ) + } + }) + } + + /** + * @returns {Promise} + */ + catch() { + return Promise.resolve(this).catch((error) => error) + } +} + + +/** + * @template {string} Channel + * @param {AsyncIterable} source + * @param {{[key in Channel]: string}} output + * @param {Channel} channel + */ +const readInto = async (source, output, channel) => { + const decoder = new TextDecoder() + for await (const chunk of source) { + // Uncomment to debugger easily + // console.log(decoder.decode(chunk)) + output[channel] += decoder.decode(chunk) + } +} diff --git a/packages/cli/test/helpers/random.js b/packages/cli/test/helpers/random.js new file mode 100644 index 000000000..1f4447696 --- /dev/null +++ b/packages/cli/test/helpers/random.js @@ -0,0 +1,61 @@ +import { CarWriter } from '@ipld/car' +import * as CAR from '@ucanto/transport/car' +import { CID } from 'multiformats/cid' +import * as raw from 'multiformats/codecs/raw' +import { sha256 } from 'multiformats/hashes/sha2' + +/** @param {number} size */ +export async function randomBytes(size) { + const bytes = new Uint8Array(size) + while (size) { + const chunk = new Uint8Array(Math.min(size, 65_536)) + if (!globalThis.crypto) { + try { + const { webcrypto } = await import('node:crypto') + webcrypto.getRandomValues(chunk) + } catch (err) { + throw new Error( + 'unknown environment - no global crypto and not Node.js', + { cause: err } + ) + } + } else { + crypto.getRandomValues(chunk) + } + size -= chunk.length + bytes.set(chunk, size) + } + return bytes +} + +/** @param {number} size */ +export async function randomCAR(size) { + const bytes = await randomBytes(size) + return toCAR(bytes) +} + +/** @param {Uint8Array} bytes */ +export async function toBlock(bytes) { + const hash = await sha256.digest(bytes) + const cid = CID.createV1(raw.code, hash) + return { cid, bytes } +} + +/** + * @param {Uint8Array} bytes + */ +export async function toCAR(bytes) { + const block = await toBlock(bytes) + const { writer, out } = CarWriter.create(block.cid) + void writer.put(block) + void writer.close() + + const chunks = [] + for await (const chunk of out) { + chunks.push(chunk) + } + const blob = new Blob(chunks) + const cid = await CAR.codec.link(new Uint8Array(await blob.arrayBuffer())) + + return Object.assign(blob, { cid, roots: [block.cid] }) +} diff --git a/packages/cli/test/helpers/receipt-http-server.js b/packages/cli/test/helpers/receipt-http-server.js new file mode 100644 index 000000000..e04f0efec --- /dev/null +++ b/packages/cli/test/helpers/receipt-http-server.js @@ -0,0 +1,80 @@ +import http from 'node:http' +import { once } from 'node:events' + +import { parseLink } from '@ucanto/server' +import * as Signer from '@ucanto/principal/ed25519' +import { Receipt, Message } from '@ucanto/core' +import * as CAR from '@ucanto/transport/car' +import { Assert } from '@web3-storage/content-claims/capability' +import { randomCAR } from './random.js' + +/** + * @typedef {{ + * server: http.Server + * serverURL: URL + * }} TestingServer + */ + +/** + * @returns {Promise} + */ +export async function createReceiptsServer() { + /** + * @param {http.IncomingMessage} request + * @param {http.ServerResponse} response + */ + const listener = async (request, response) => { + const taskCid = request.url?.split('/')[1] ?? '' + const body = await generateReceipt(taskCid) + response.writeHead(200) + response.end(body) + return undefined + } + + const server = http.createServer(listener).listen() + + await once(server, 'listening') + + return { + server, + // @ts-expect-error + serverURL: new URL(`http://127.0.0.1:${server.address().port}`), + } +} + +/** + * @param {string} taskCid + */ +const generateReceipt = async (taskCid) => { + const issuer = await Signer.generate() + const content = (await randomCAR(128)).cid + const locationClaim = await Assert.location.delegate({ + issuer, + audience: issuer, + with: issuer.toDIDKey(), + nb: { + content, + location: ['http://localhost'], + }, + expiration: Infinity, + }) + + const receipt = await Receipt.issue({ + issuer, + fx: { + fork: [locationClaim], + }, + /** @ts-expect-error not a UCAN Link */ + ran: parseLink(taskCid), + result: { + ok: { + site: locationClaim.link(), + }, + }, + }) + + const message = await Message.build({ + receipts: [receipt], + }) + return CAR.request.encode(message).body +} diff --git a/packages/cli/test/helpers/stream.js b/packages/cli/test/helpers/stream.js new file mode 100644 index 000000000..1447886bc --- /dev/null +++ b/packages/cli/test/helpers/stream.js @@ -0,0 +1,489 @@ +const empty = () => EMPTY + +/** + * @template {{}} T + * @typedef {ReadableStream|AsyncIterable|Iterable} Source + */ + +/** + * @template {{}} T + * @param {Source} source + * @returns {Resource} + */ +const toResource = (source) => { + if ('getReader' in source) { + return source.getReader() + } else { + const iterator = + Symbol.asyncIterator in source + ? source[Symbol.asyncIterator]() + : source[Symbol.iterator]() + + return { + async read() { + return /** @type {ReadableStreamReadResult} */ ( + await iterator.next() + ) + }, + releaseLock() { + return iterator.return?.() + }, + async cancel(reason) { + if (reason != null) { + if (iterator.throw) { + await iterator.throw(reason) + } else if (iterator.return) { + await iterator.return() + } + } else { + await iterator.return?.() + } + }, + } + } +} + +/** + * @template {{}} T + * @param {ReadableStream|AsyncIterable|Iterable} source + * @returns {Stream} + */ +export const from = (source) => new Stream(toResource(source), {}, Direct) + +/** + * @template {{}} T + * @param {Resource} source + * @param {number} n + * @returns {Stream} + */ +const take = (source, n = 1) => + new Stream(source, n, /** @type {Transform} */ (Take)) + +const Take = { + /** + * @template T + * @param {number} n + * @param {T} input + * @returns {[number|undefined, T[]]} + */ + write: (n, input) => { + if (n > 0) { + return input != null ? [n - 1, [input]] : [n, []] + } else { + return [undefined, []] + } + }, + flush: empty, +} + +/** + * @param {Resource} source + * @returns {ByteStream<{}>} + */ +const toByteStream = (source) => new ByteStream(source, {}, Direct) + +/** + * @template {{}} T + * @param {Resource} source + * @returns {Reader} + */ +const toReader = (source) => new Reader(source) + +/** + * @template T + * @param {Resource} source + */ +const collect = async (source) => { + const chunks = [] + for await (const chunk of iterate(source)) { + chunks.push(chunk) + } + + return chunks +} + +/** + * @param {Resource} source + * @param {number} chunkSize + * @returns {ByteStream} + */ +const chop = (source, chunkSize) => + new ByteStream(source, new Uint8Array(chunkSize), Chop) + +const Chop = { + /** + * @param {Uint8Array} bytes + * @param {Uint8Array} input + * @returns {[Uint8Array, Uint8Array[]]} + */ + write(bytes, input) { + const { byteLength } = bytes.buffer + if (bytes.length + input.length < byteLength) { + const buffer = new Uint8Array( + bytes.buffer, + 0, + bytes.length + input.length + ) + buffer.set(input, bytes.length) + return [buffer, []] + } else { + const chunk = new Uint8Array(byteLength) + chunk.set(bytes, 0) + chunk.set(input.slice(0, byteLength - bytes.length), bytes.length) + + const chunks = [chunk] + + let offset = byteLength - bytes.length + while (offset + byteLength < input.length) { + chunks.push(input.subarray(offset, offset + byteLength)) + offset += byteLength + } + + const buffer = new Uint8Array(bytes.buffer, 0, input.length - offset) + buffer.set(input.subarray(offset), 0) + + return [buffer, chunks] + } + }, + /** + * @param {Uint8Array} bytes + */ + flush(bytes) { + return bytes.length ? [bytes] : [] + }, +} + +/** + * @param {Resource} source + * @param {number} byte + */ +const delimit = (source, byte) => + new ByteStream(source, { buffer: new Uint8Array(0), code: byte }, Delimiter) + +const Delimiter = { + /** + * @param {{code: number, buffer:Uint8Array}} state + * @param {Uint8Array} input + * @returns {[{code: number, buffer:Uint8Array}|undefined, Uint8Array[]]} + */ + write({ code, buffer }, input) { + let start = 0 + let end = 0 + const chunks = [] + while (end < input.length) { + const byte = input[end] + end++ + if (byte === code) { + const segment = input.subarray(start, end) + if (buffer.length > 0) { + const chunk = new Uint8Array(buffer.length + segment.length) + chunk.set(buffer, 0) + chunk.set(segment, buffer.length) + chunks.push(chunk) + buffer = new Uint8Array(0) + } else { + chunks.push(segment) + } + start = end + } + } + + const segment = input.subarray(start, end) + const chunk = new Uint8Array(buffer.length + segment.length) + chunk.set(buffer, 0) + chunk.set(segment, buffer.length) + + return [{ code, buffer }, chunks] + }, + /** + * @param {{code: number, buffer:Uint8Array}} state + */ + flush({ buffer }) { + return buffer.length ? [buffer] : [] + }, +} + +/** + * @template {{}} Out + * @template {{}} State + * @template {{}} [In=Out] + * @typedef {object} Transform + * @property {(state: State, input: In) => [State|undefined, Out[]]} write + * @property {(state: State) => Out[]} flush + */ +/** + * @template {{}} Out + * @template {{}} State + * @template {{}} In + * @param {Resource} source + * @param {State} state + * @param {Transform} transform + * @returns {Stream} + */ +const transform = (source, state, transform) => + new Stream(source, state, transform) + +/** + * @template T + * @param {Resource} source + */ +const iterate = async function* (source) { + try { + while (true) { + const { value, done } = await source.read() + if (done) break + yield value + } + } catch (error) { + source.cancel(/** @type {{}} */ (error)) + source.releaseLock() + throw error + } +} + +const Direct = { + /** + * @template {{}} T + * @template {{}} State + * @param {State} state + * @param {T} input + */ + write(state, input) { + OUT.pop() + if (input != null) { + OUT.push(input) + } + STEP[0] = state + return STEP + }, + /** + * @returns {never[]} + */ + flush() { + return EMPTY + }, +} +/** + * @template {{}} Out + * @template {{}} [State={}] + * @template {{}} [In=Out] + * @extends {ReadableStream} + */ +export class Stream extends ReadableStream { + /** + * @param {Resource} source + * @param {State} state + * @param {Transform} transformer + */ + constructor(source, state, { write, flush }) { + super({ + /** + * @param {ReadableStreamDefaultController} controller + */ + pull: async (controller) => { + try { + const { done, value } = await source.read() + if (done) { + controller.close() + source.releaseLock() + } else { + const [next, output] = write(state, value) + for (const item of output) { + controller.enqueue(item) + } + + if (next) { + state = next + } else { + controller.close() + source.cancel() + source.releaseLock() + } + } + } catch (error) { + controller.error(error) + source.releaseLock() + } + }, + cancel(controller) { + source.cancel() + source.releaseLock() + for (const item of flush(state)) { + controller.enqueue(item) + } + }, + }) + } + + /** + * @template {{}} State + * @template {{}} T + * @param {State} state + * @param {Transform} transformer + */ + transform(state, transformer) { + return transform(this.getReader(), state, transformer) + } + + /** + * @returns {Reader} + */ + reader() { + return toReader(this.getReader()) + } + + /** + * @returns {AsyncIterable} + */ + [Symbol.asyncIterator]() { + return iterate(this.getReader()) + } + + /** + * @param {number} n + */ + take(n = 1) { + return take(this.getReader(), n) + } + + collect() { + return collect(this.getReader()) + } +} + +/** + * @template {{}} [State={}] + * @extends {Stream} + */ +export class ByteStream extends Stream { + /** + * @param {Source} source + */ + static from(source) { + return new ByteStream(toResource(source), {}, Direct) + } + + reader() { + return new BytesReader(this.getReader()) + } + + text() { + return this.reader().text() + } + bytes() { + return this.reader().bytes() + } + + /** + * @param {number} n + */ + take(n = 1) { + return toByteStream(take(this.getReader(), n).getReader()) + } + /** + * @param {number} size + */ + chop(size) { + return chop(this.getReader(), size) + } + + /** + * @param {number} byte + */ + delimit(byte) { + return delimit(this.getReader(), byte) + } + + lines() { + return this.delimit('\n'.charCodeAt(0)) + } +} + +/** + * @template T + * @typedef {object} Resource + * @property {() => Promise>} read + * @property {() => void} releaseLock + * @property {(reason?: {}) => void} cancel + */ + +/** @type {never[]} */ +const EMPTY = [] + +/** @type {any[]} */ +const OUT = [] +/** @type {[any, any[]]} */ +const STEP = [{}, OUT] + +/** + * @template {{}} T + */ +class Reader { + /** + * @param {Resource} source + */ + constructor(source) { + this.source = source + } + read() { + return this.source.read() + } + releaseLock() { + return this.source.releaseLock() + } + + /** + * @param {{}} [reason] + */ + cancel(reason) { + const result = this.source.cancel(reason) + this.source.releaseLock() + return result + } + async *[Symbol.asyncIterator]() { + while (true) { + const { value, done } = await this.read() + if (done) break + yield value + } + this.cancel() + } + + take(n = 1) { + return take(this.source, n).reader() + } + + collect() { + return collect(this.source) + } +} + +/** + * @extends {Reader} + */ +class BytesReader extends Reader { + async bytes() { + const chunks = [] + let length = 0 + for await (const chunk of this) { + chunks.push(chunk) + length += chunk.length + } + + const bytes = new Uint8Array(length) + let offset = 0 + for (const chunk of chunks) { + bytes.set(chunk, offset) + offset += chunk.length + } + + return bytes + } + async text() { + return new TextDecoder().decode(await this.bytes()) + } + + take(n = 1) { + return ByteStream.from(take(this.source, n)).reader() + } +} diff --git a/packages/cli/test/helpers/util.js b/packages/cli/test/helpers/util.js new file mode 100644 index 000000000..b39811e50 --- /dev/null +++ b/packages/cli/test/helpers/util.js @@ -0,0 +1,19 @@ +/** + * @param {{ raw: ArrayLike }} template + * @param {unknown[]} substitutions + */ +export const pattern = (template, ...substitutions) => + new RegExp(String.raw(template, ...substitutions)) + +/** + * @param {RegExp} pattern + * @param {string} source + * @returns {string[]} + */ +export const match = (pattern, source) => { + const match = source.match(pattern) + if (!match) { + return [] + } + return match +} diff --git a/packages/cli/test/lib.spec.js b/packages/cli/test/lib.spec.js new file mode 100644 index 000000000..92e0d1705 --- /dev/null +++ b/packages/cli/test/lib.spec.js @@ -0,0 +1,98 @@ +import * as Link from 'multiformats/link' +import { filesize, uploadListResponseToString } from '../lib.js' + +/** + * @typedef {import('multiformats').LinkJSON} LinkJSON + * @typedef {import('@storacha/client/types').CARLink} CARLink + */ + +/** @type {import('entail').Suite} */ +export const testFilesize = { + filesize: (assert) => { + /** @type {Array<[number, string]>} */ + const testdata = [ + [5, '5B'], + [50, '0.1KB'], + [500, '0.5KB'], + [5_000, '5.0KB'], + [50_000, '0.1MB'], + [500_000, '0.5MB'], + [5_000_000, '5.0MB'], + [50_000_000, '0.1GB'], + [500_000_000, '0.5GB'], + [5_000_000_000, '5.0GB'], + ] + testdata.forEach(([size, str]) => assert.equal(filesize(size), str)) + }, +} + +/** @type {import('@storacha/client/types').UploadListSuccess} */ +const uploadListResponse = { + size: 2, + cursor: 'bafybeibvbxjeodaa6hdqlgbwmv4qzdp3bxnwdoukay4dpl7aemkiwc2eje', + results: [ + { + root: Link.parse( + 'bafybeia7tr4dgyln7zeyyyzmkppkcts6azdssykuluwzmmswysieyadcbm' + ), + shards: [ + Link.parse( + 'bagbaierantza4rfjnhqksp2stcnd2tdjrn3f2kgi2wrvaxmayeuolryi66fq' + ), + ], + updatedAt: new Date().toISOString(), + insertedAt: new Date().toISOString(), + }, + { + root: Link.parse( + 'bafybeibvbxjeodaa6hdqlgbwmv4qzdp3bxnwdoukay4dpl7aemkiwc2eje' + ), + shards: [ + Link.parse( + 'bagbaieraxqbkzwvx5on6an4br5hagfgesdfc6adchy3hf5qt34pupfjd3rbq' + ), + ], + updatedAt: new Date().toISOString(), + insertedAt: new Date().toISOString(), + }, + ], + after: 'bafybeibvbxjeodaa6hdqlgbwmv4qzdp3bxnwdoukay4dpl7aemkiwc2eje', + before: 'bafybeia7tr4dgyln7zeyyyzmkppkcts6azdssykuluwzmmswysieyadcbm', +} + +/** @type {import('entail').Suite} */ +export const testUpload = { + 'uploadListResponseToString can return the upload roots CIDs as strings': ( + assert + ) => { + assert.equal( + uploadListResponseToString(uploadListResponse, {}), + `bafybeia7tr4dgyln7zeyyyzmkppkcts6azdssykuluwzmmswysieyadcbm +bafybeibvbxjeodaa6hdqlgbwmv4qzdp3bxnwdoukay4dpl7aemkiwc2eje` + ) + }, + + 'uploadListResponseToString can return the upload roots as newline delimited JSON': + (assert) => { + assert.equal( + uploadListResponseToString(uploadListResponse, { shards: true, plainTree: true }), + `bafybeia7tr4dgyln7zeyyyzmkppkcts6azdssykuluwzmmswysieyadcbm +└─┬ shards + └── bagbaierantza4rfjnhqksp2stcnd2tdjrn3f2kgi2wrvaxmayeuolryi66fq + +bafybeibvbxjeodaa6hdqlgbwmv4qzdp3bxnwdoukay4dpl7aemkiwc2eje +└─┬ shards + └── bagbaieraxqbkzwvx5on6an4br5hagfgesdfc6adchy3hf5qt34pupfjd3rbq +` + ) + }, + + 'uploadListResponseToString can return the upload roots and shards as a tree': + (assert) => { + assert.equal( + uploadListResponseToString(uploadListResponse, { json: true }), + `{"root":{"/":"bafybeia7tr4dgyln7zeyyyzmkppkcts6azdssykuluwzmmswysieyadcbm"},"shards":[{"/":"bagbaierantza4rfjnhqksp2stcnd2tdjrn3f2kgi2wrvaxmayeuolryi66fq"}]} +{"root":{"/":"bafybeibvbxjeodaa6hdqlgbwmv4qzdp3bxnwdoukay4dpl7aemkiwc2eje"},"shards":[{"/":"bagbaieraxqbkzwvx5on6an4br5hagfgesdfc6adchy3hf5qt34pupfjd3rbq"}]}` + ) + }, +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 000000000..7c59f301c --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "dist", + // project options + "allowJs": true, + "checkJs": true, + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "noEmit": true, + "isolatedModules": true, + "removeComments": false, + // module resolution + "esModuleInterop": true, + "moduleResolution": "Node", + // linter checks + "noImplicitReturns": false, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + // advanced + "importsNotUsedAsValues": "remove", + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "stripInternal": true, + "resolveJsonModule": true + } +} diff --git a/packages/w3up-client/src/service.js b/packages/w3up-client/src/service.js index 9052503bb..48af26bc9 100644 --- a/packages/w3up-client/src/service.js +++ b/packages/w3up-client/src/service.js @@ -3,40 +3,31 @@ import { CAR, HTTP } from '@ucanto/transport' import * as DID from '@ipld/dag-ucan/did' import { receiptsEndpoint } from '@storacha/upload-client' -export const accessServiceURL = new URL('https://up.storacha.network') -export const accessServicePrincipal = DID.parse('did:web:storacha.network') +export const accessServiceURL = new URL('https://upload.storacha.network') +export const accessServicePrincipal = DID.parse('did:web:upload.storacha.network') export const accessServiceConnection = client.connect({ id: accessServicePrincipal, codec: CAR.outbound, - channel: HTTP.open({ - url: accessServiceURL, - method: 'POST', - }), + channel: HTTP.open({ url: accessServiceURL, method: 'POST' }), }) -export const uploadServiceURL = new URL('https://up.storacha.network') -export const uploadServicePrincipal = DID.parse('did:web:storacha.network') +export const uploadServiceURL = new URL('https://upload.storacha.network') +export const uploadServicePrincipal = DID.parse('did:web:upload.storacha.network') export const uploadServiceConnection = client.connect({ id: uploadServicePrincipal, codec: CAR.outbound, - channel: HTTP.open({ - url: uploadServiceURL, - method: 'POST', - }), + channel: HTTP.open({ url: accessServiceURL, method: 'POST' }), }) -export const filecoinServiceURL = new URL('https://up.storacha.network') -export const filecoinServicePrincipal = DID.parse('did:web:storacha.network') +export const filecoinServiceURL = new URL('https://upload.storacha.network') +export const filecoinServicePrincipal = DID.parse('did:web:upload.storacha.network') export const filecoinServiceConnection = client.connect({ id: filecoinServicePrincipal, codec: CAR.outbound, - channel: HTTP.open({ - url: filecoinServiceURL, - method: 'POST', - }), + channel: HTTP.open({ url: accessServiceURL, method: 'POST' }), }) /** @type {import('./types.js').ServiceConf} */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c471b108..0e77e5970 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,10 +20,10 @@ importers: version: 0.15.4 '@docusaurus/core': specifier: ^3.0.0 - version: 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + version: 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) '@docusaurus/preset-classic': specifier: ^3.0.0 - version: 3.6.0(@algolia/client-search@5.12.0)(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(typescript@5.2.2) + version: 3.6.1(@algolia/client-search@5.13.0)(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(typescript@5.2.2) docusaurus-plugin-typedoc: specifier: ^0.21.0 version: 0.21.0(typedoc-plugin-markdown@3.17.1(typedoc@0.25.13(typescript@5.2.2)))(typedoc@0.25.13(typescript@5.2.2)) @@ -247,6 +247,124 @@ importers: specifier: ^1.0.2 version: 1.0.2 + packages/cli: + dependencies: + '@inquirer/core': + specifier: ^5.1.1 + version: 5.1.2 + '@inquirer/prompts': + specifier: ^3.3.0 + version: 3.3.2 + '@ipld/car': + specifier: ^5.2.4 + version: 5.3.3 + '@ipld/dag-json': + specifier: ^10.1.5 + version: 10.2.3 + '@ipld/dag-ucan': + specifier: ^3.4.0 + version: 3.4.0 + '@storacha/access': + specifier: workspace:^ + version: link:../access-client + '@storacha/client': + specifier: workspace:^ + version: link:../w3up-client + '@storacha/did-mailto': + specifier: workspace:^ + version: link:../did-mailto + '@ucanto/client': + specifier: ^9.0.1 + version: 9.0.1 + '@ucanto/core': + specifier: ^10.0.1 + version: 10.0.1 + '@ucanto/transport': + specifier: ^9.1.1 + version: 9.1.1 + '@web3-storage/content-claims': + specifier: ^5.1.3 + version: 5.1.3 + '@web3-storage/data-segment': + specifier: ^5.0.0 + version: 5.3.0 + ansi-escapes: + specifier: ^6.2.0 + version: 6.2.1 + chalk: + specifier: ^5.3.0 + version: 5.3.0 + crypto-random-string: + specifier: ^5.0.0 + version: 5.0.0 + files-from-path: + specifier: ^1.0.4 + version: 1.0.4 + fr32-sha2-256-trunc254-padded-binary-tree-multihash: + specifier: ^3.3.0 + version: 3.3.0 + open: + specifier: ^9.1.0 + version: 9.1.0 + ora: + specifier: ^7.0.1 + version: 7.0.1 + pretty-tree: + specifier: ^1.0.0 + version: 1.0.0 + s-ago: + specifier: ^2.2.0 + version: 2.2.0 + sade: + specifier: ^1.8.1 + version: 1.8.1 + update-notifier: + specifier: ^7.0.0 + version: 7.3.1 + devDependencies: + '@storacha/capabilities': + specifier: workspace:^ + version: link:../capabilities + '@storacha/eslint-config': + specifier: workspace:^ + version: link:../eslint-config-w3up + '@storacha/upload-api': + specifier: workspace:^ + version: link:../upload-api + '@types/update-notifier': + specifier: ^6.0.5 + version: 6.0.8 + '@ucanto/interface': + specifier: ^10.0.1 + version: 10.0.1 + '@ucanto/principal': + specifier: ^9.0.1 + version: 9.0.1 + '@ucanto/server': + specifier: ^10.0.0 + version: 10.0.0 + '@web-std/blob': + specifier: ^3.0.5 + version: 3.0.5 + '@web3-storage/sigv4': + specifier: ^1.0.2 + version: 1.0.2 + entail: + specifier: ^2.1.1 + version: 2.1.2 + multiformats: + specifier: ^13.1.1 + version: 13.3.1 + npm-run-all: + specifier: ^4.1.5 + version: 4.1.5 + prettier: + specifier: ^3.0.3 + version: 3.3.3 + typescript: + specifier: ^5.2.2 + version: 5.2.2 + packages/did-mailto: devDependencies: '@storacha/eslint-config': @@ -695,11 +813,11 @@ importers: packages: - '@algolia/autocomplete-core@1.9.3': - resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} + '@algolia/autocomplete-core@1.17.6': + resolution: {integrity: sha512-lkDoW4I7h2kKlIgf3pUt1LqvxyYKkVyiypoGLlUnhPSnCpmeOwudM6rNq6YYsCmdQtnDQoW5lUNNuj6ASg3qeg==} - '@algolia/autocomplete-plugin-algolia-insights@1.9.3': - resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} + '@algolia/autocomplete-plugin-algolia-insights@1.17.6': + resolution: {integrity: sha512-17NnaacuFzSWVuZu4NKzVeaFIe9Abpw8w+/gjc7xhZFtqj+GadufzodIdchwiB2eM2cDdiR3icW7gbNTB3K2YA==} peerDependencies: search-insights: '>= 1 < 3' @@ -715,12 +833,6 @@ packages: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - '@algolia/autocomplete-shared@1.9.3': - resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} - peerDependencies: - '@algolia/client-search': '>= 4.9.1 < 6' - algoliasearch: '>= 4.9.1 < 6' - '@algolia/cache-browser-local-storage@4.24.0': resolution: {integrity: sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==} @@ -730,8 +842,8 @@ packages: '@algolia/cache-in-memory@4.24.0': resolution: {integrity: sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==} - '@algolia/client-abtesting@5.12.0': - resolution: {integrity: sha512-hx4eVydkm3yrFCFxmcBtSzI/ykt0cZ6sDWch+v3JTgKpD2WtosMJU3Upv1AjQ4B6COSHCOWEX3vfFxW6OoH6aA==} + '@algolia/client-abtesting@5.13.0': + resolution: {integrity: sha512-6CoQjlMi1pmQYMQO8tXfuGxSPf6iKX5FP9MuMe6IWmvC81wwTvOehnwchyBl2wuPVhcw2Ar53K53mQ60DAC64g==} engines: {node: '>= 14.0.0'} '@algolia/client-account@4.24.0': @@ -740,44 +852,44 @@ packages: '@algolia/client-analytics@4.24.0': resolution: {integrity: sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==} - '@algolia/client-analytics@5.12.0': - resolution: {integrity: sha512-EpTsSv6IW8maCfXCDIptgT7+mQJj7pImEkcNUnxR8yUKAHzTogTXv9yGm2WXOZFVuwstd2i0sImhQ1Vz8RH/hA==} + '@algolia/client-analytics@5.13.0': + resolution: {integrity: sha512-pS3qyXiWTwKnrt/jE79fqkNqZp7kjsFNlJDcBGkSWid74DNc6DmArlkvPqyLxnoaYGjUGACT6g56n7E3mVV2TA==} engines: {node: '>= 14.0.0'} '@algolia/client-common@4.24.0': resolution: {integrity: sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==} - '@algolia/client-common@5.12.0': - resolution: {integrity: sha512-od3WmO8qxyfNhKc+K3D17tvun3IMs/xMNmxCG9MiElAkYVbPPTRUYMkRneCpmJyQI0hNx2/EA4kZgzVfQjO86Q==} + '@algolia/client-common@5.13.0': + resolution: {integrity: sha512-2SP6bGGWOTN920MLZv8s7yIR3OqY03vEe4U+vb2MGdL8a/8EQznF3L/nTC/rGf/hvEfZlX2tGFxPJaF2waravg==} engines: {node: '>= 14.0.0'} - '@algolia/client-insights@5.12.0': - resolution: {integrity: sha512-8alajmsYUd+7vfX5lpRNdxqv3Xx9clIHLUItyQK0Z6gwGMbVEFe6YYhgDtwslMAP0y6b0WeJEIZJMLgT7VYpRw==} + '@algolia/client-insights@5.13.0': + resolution: {integrity: sha512-ldHTe+LVgC6L4Wr6doAQQ7Ku0jAdhaaPg1T+IHzmmiRZb2Uq5OsjW2yC65JifOmzPCiMkIZE2mGRpWgkn5ktlw==} engines: {node: '>= 14.0.0'} '@algolia/client-personalization@4.24.0': resolution: {integrity: sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==} - '@algolia/client-personalization@5.12.0': - resolution: {integrity: sha512-bUV9HtfkTBgpoVhxFrMkmVPG03ZN1Rtn51kiaEtukucdk3ggjR9Qu1YUfRSU2lFgxr9qJc8lTxwfvhjCeJRcqw==} + '@algolia/client-personalization@5.13.0': + resolution: {integrity: sha512-RnCfOSN4OUJDuMNHFca2M8lY64Tmw0kQOZikge4TknTqHmlbKJb8IbJE7Rol79Z80W2Y+B1ydcjV7DPje4GMRA==} engines: {node: '>= 14.0.0'} - '@algolia/client-query-suggestions@5.12.0': - resolution: {integrity: sha512-Q5CszzGWfxbIDs9DJ/QJsL7bP6h+lJMg27KxieEnI9KGCu0Jt5iFA3GkREkgRZxRdzlHbZKkrIzhtHVbSHw/rg==} + '@algolia/client-query-suggestions@5.13.0': + resolution: {integrity: sha512-pYo0jbLUtPDN1r341UHTaF2fgN5rbaZfDZqjPRKPM+FRlRmxFxqFQm1UUfpkSUWYGn7lECwDpbKYiKUf81MTwA==} engines: {node: '>= 14.0.0'} '@algolia/client-search@4.24.0': resolution: {integrity: sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==} - '@algolia/client-search@5.12.0': - resolution: {integrity: sha512-R3qzEytgVLHOGNri+bpta6NtTt7YtkvUe/QBcAmMDjW4Jk1P0eBYIPfvnzIPbINRsLxIq9fZs9uAYBgsrts4Zg==} + '@algolia/client-search@5.13.0': + resolution: {integrity: sha512-s2ge3uZ6Zg2sPSFibqijgEYsuorxcc8KVHg3I95nOPHvFHdnBtSHymhZvq4sp/fu8ijt/Y8jLwkuqm5myn+2Sg==} engines: {node: '>= 14.0.0'} '@algolia/events@4.0.1': resolution: {integrity: sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==} - '@algolia/ingestion@1.12.0': - resolution: {integrity: sha512-zpHo6qhR22tL8FsdSI4DvEraPDi/019HmMrCFB/TUX98yzh5ooAU7sNW0qPL1I7+S++VbBmNzJOEU9VI8tEC8A==} + '@algolia/ingestion@1.13.0': + resolution: {integrity: sha512-fm5LEOe4FPDOc1D+M9stEs8hfcdmbdD+pt9og5shql6ueTZJANDbFoQhDOpiPJizR/ps1GwmjkWfUEywx3sV+Q==} engines: {node: '>= 14.0.0'} '@algolia/logger-common@4.24.0': @@ -786,36 +898,36 @@ packages: '@algolia/logger-console@4.24.0': resolution: {integrity: sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==} - '@algolia/monitoring@1.12.0': - resolution: {integrity: sha512-i2AJZED/zf4uhxezAJUhMKoL5QoepCBp2ynOYol0N76+TSoohaMADdPnWCqOULF4RzOwrG8wWynAwBlXsAI1RQ==} + '@algolia/monitoring@1.13.0': + resolution: {integrity: sha512-e8Hshlnm2G5fapyUgWTBwhJ22yXcnLtPC4LWZKx7KOvv35GcdoHtlUBX94I/sWCJLraUr65JvR8qOo3LXC43dg==} engines: {node: '>= 14.0.0'} '@algolia/recommend@4.24.0': resolution: {integrity: sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==} - '@algolia/recommend@5.12.0': - resolution: {integrity: sha512-0jmZyKvYnB/Bj5c7WKsKedOUjnr0UtXm0LVFUdQrxXfqOqvWv9n6Vpr65UjdYG4Q49kRQxhlwtal9WJYrYymXg==} + '@algolia/recommend@5.13.0': + resolution: {integrity: sha512-53/wW96oaj1FKMzGdFcZ/epygfTppLDUvgI1thLkd475EtVZCH3ZZVUNCEvf1AtnNyH1RnItkFzX8ayWCpx2PQ==} engines: {node: '>= 14.0.0'} '@algolia/requester-browser-xhr@4.24.0': resolution: {integrity: sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==} - '@algolia/requester-browser-xhr@5.12.0': - resolution: {integrity: sha512-KxwleraFuVoEGCoeW6Y1RAEbgBMS7SavqeyzWdtkJc6mXeCOJXn1iZitb8Tyn2FcpMNUKlSm0adrUTt7G47+Ow==} + '@algolia/requester-browser-xhr@5.13.0': + resolution: {integrity: sha512-NV6oSCt5lFuzfsVQoSBpewEWf/h4ySr7pv2bfwu9yF/jc/g39pig8+YpuqsxlRWBm/lTGVA2V0Ai9ySwrNumIA==} engines: {node: '>= 14.0.0'} '@algolia/requester-common@4.24.0': resolution: {integrity: sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==} - '@algolia/requester-fetch@5.12.0': - resolution: {integrity: sha512-FuDZXUGU1pAg2HCnrt8+q1VGHKChV/LhvjvZlLOT7e56GJie6p+EuLu4/hMKPOVuQQ8XXtrTHKIU3Lw+7O5/bQ==} + '@algolia/requester-fetch@5.13.0': + resolution: {integrity: sha512-094bK4rumf+rXJazxv3mq6eKRM0ep5AxIo8T0YmOdldswQt79apeufFiPLN19nHEWH22xR2FelimD+T/wRSP+Q==} engines: {node: '>= 14.0.0'} '@algolia/requester-node-http@4.24.0': resolution: {integrity: sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==} - '@algolia/requester-node-http@5.12.0': - resolution: {integrity: sha512-ncDDY7CxZhMs6LIoPl+vHFQceIBhYPY5EfuGF1V7beO0U38xfsCYEyutEFB2kRzf4D9Gqppn3iWX71sNtrKcuw==} + '@algolia/requester-node-http@5.13.0': + resolution: {integrity: sha512-JY5xhEYMgki53Wm+A6R2jUpOUdD0zZnBq+PC5R1TGMNOYL1s6JjDrJeMsvaI2YWxYMUSoCnRoltN/yf9RI8n3A==} engines: {node: '>= 14.0.0'} '@algolia/transporter@4.24.0': @@ -1420,11 +1532,11 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - '@docsearch/css@3.6.3': - resolution: {integrity: sha512-3uvbg8E7rhqE1C4oBAK3tGlS2qfhi9zpfZgH/yjDPF73vd9B41urVIKujF4rczcF4E3qs34SedhehiDJ4UdNBA==} + '@docsearch/css@3.7.0': + resolution: {integrity: sha512-1OorbTwi1eeDmr0v5t+ckSRlt1zM5GHjm92iIl3kUu7im3GHuP+csf6E0WBg8pdXQczTWP9J9+o9n+Vg6DH5cQ==} - '@docsearch/react@3.6.3': - resolution: {integrity: sha512-2munr4uBuZq1PG+Ge+F+ldIdxb3Wi8OmEIv2tQQb4RvEvvph+xtQkxwHzVIEnt5s+HecwucuXwB+3JhcZboFLg==} + '@docsearch/react@3.7.0': + resolution: {integrity: sha512-8e6tdDfkYoxafEEPuX5eE1h9cTkLvhe4KgoFkO5JCddXSQONnN1FHcDZRI4r8894eMpbYq6rdJF0dVYh8ikwNQ==} peerDependencies: '@types/react': '>= 16.8.0 < 19.0.0' react: '>= 16.8.0 < 19.0.0' @@ -1440,21 +1552,21 @@ packages: search-insights: optional: true - '@docusaurus/babel@3.6.0': - resolution: {integrity: sha512-7CsoQFiadoq7AHSUIQNkI/lGfg9AQ2ZBzsf9BqfZGXkHwWDy6twuohEaG0PgQv1npSRSAB2dioVxhRSErnqKNA==} + '@docusaurus/babel@3.6.1': + resolution: {integrity: sha512-JcKaunW8Ml2nTnfnvFc55T00Y+aCpNWnf1KY/gG+wWxHYDH0IdXOOz+k6NAlEAerW8+VYLfUqRIqHZ7N/DVXvQ==} engines: {node: '>=18.0'} - '@docusaurus/bundler@3.6.0': - resolution: {integrity: sha512-o5T9HXkPKH0OQAifTxEXaebcO8kaz3tU1+wlIShZ2DKJHlsyWX3N4rToWBHroWnV/ZCT2XN3kLRzXASqrnb9Tw==} + '@docusaurus/bundler@3.6.1': + resolution: {integrity: sha512-vHSEx8Ku9x/gfIC6k4xb8J2nTxagLia0KvZkPZhxfkD1+n8i+Dj4BZPWTmv+kCA17RbgAvECG0XRZ0/ZEspQBQ==} engines: {node: '>=18.0'} peerDependencies: - '@docusaurus/faster': 3.5.2 + '@docusaurus/faster': '*' peerDependenciesMeta: '@docusaurus/faster': optional: true - '@docusaurus/core@3.6.0': - resolution: {integrity: sha512-lvRgMoKJJSRDt9+HhAqFcICV4kp/mw1cJJrLxIw4Q2XZnFGM1XUuwcbuaqWmGog+NcOLZaPCcCtZbn60EMCtjQ==} + '@docusaurus/core@3.6.1': + resolution: {integrity: sha512-cDKxPihiM2z7G+4QtpTczS7uxNfNG6naSqM65OmAJET0CFRHbc9mDlLFtQF0lsVES91SHqfcGaaLZmi2FjdwWA==} engines: {node: '>=18.0'} hasBin: true peerDependencies: @@ -1462,86 +1574,86 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/cssnano-preset@3.6.0': - resolution: {integrity: sha512-h3jlOXqqzNSoU+C4CZLNpFtD+v2xr1UBf4idZpwMgqid9r6lb5GS7tWKnQnauio6OipacbHbDXEX3JyT1PlDkg==} + '@docusaurus/cssnano-preset@3.6.1': + resolution: {integrity: sha512-ZxYUmNeyQHW2w4/PJ7d07jQDuxzmKr9uPAQ6IVe5dTkeIeV0mDBB3jOLeJkNoI42Ru9JKEqQ9aVDtM9ct6QHnw==} engines: {node: '>=18.0'} - '@docusaurus/logger@3.6.0': - resolution: {integrity: sha512-BcQhoXilXW0607cH/kO6P5Gt5KxCGfoJ+QDKNf3yO2S09/RsITlW+0QljXPbI3DklTrHrhRDmgGk1yX4nUhWTA==} + '@docusaurus/logger@3.6.1': + resolution: {integrity: sha512-OvetI/nnOMBSqCkUzKAQhnIjhxduECK4qTu3tq/8/h/qqvLsvKURojm04WPE54L+Uy+UXMas0hnbBJd8zDlEOw==} engines: {node: '>=18.0'} - '@docusaurus/mdx-loader@3.6.0': - resolution: {integrity: sha512-GhRzL1Af/AdSSrGesSPOU/iP/aXadTGmVKuysCxZDrQR2RtBtubQZ9aw+KvdFVV7R4K/CsbgD6J5oqrXlEPk3Q==} + '@docusaurus/mdx-loader@3.6.1': + resolution: {integrity: sha512-KPIsYi0S3X3/rNrW3V1fgOu5t6ahYWc31zTHHod8pacFxdmk9Uf6uuw+Jd6Cly1ilgal+41Ku+s0gmMuqKqiqg==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/module-type-aliases@3.6.0': - resolution: {integrity: sha512-szTrIN/6/fuk0xkf3XbRfdTFJzRQ8d1s3sQj5++58wltrT7v3yn1149oc9ryYjMpRcbsarGloQwMu7ofPe4XPg==} + '@docusaurus/module-type-aliases@3.6.1': + resolution: {integrity: sha512-J+q1jgm7TnEfVIUZImSFeLA1rghb6nwtoB9siHdcgKpDqFJ9/S7xhQL2aEKE7iZMZYzpu+2F390E9A7GkdEJNA==} peerDependencies: react: '*' react-dom: '*' - '@docusaurus/plugin-content-blog@3.6.0': - resolution: {integrity: sha512-o4aT1/E0Ldpzs/hQff5uyoSriAhS/yqBhqSn+fvSw465AaqRsva6O7CZSYleuBq6x2bewyE3QJq2PcTiHhAd8g==} + '@docusaurus/plugin-content-blog@3.6.1': + resolution: {integrity: sha512-FUmsn3xg/XD/K/4FQd8XHrs92aQdZO5LUtpHnRvO1/6DY87SMz6B6ERAN9IGQQld//M2/LVTHkZy8oVhQZQHIQ==} engines: {node: '>=18.0'} peerDependencies: '@docusaurus/plugin-content-docs': '*' react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/plugin-content-docs@3.6.0': - resolution: {integrity: sha512-c5gZOxocJKO/Zev2MEZInli+b+VNswDGuKHE6QtFgidhAJonwjh2kwj967RvWFaMMk62HlLJLZ+IGK2XsVy4Aw==} + '@docusaurus/plugin-content-docs@3.6.1': + resolution: {integrity: sha512-Uq8kyn5DYCDmkUlB9sWChhWghS4lUFNiQU+RXcAXJ3qCVXsBpPsh6RF+npQG1N+j4wAbjydM1iLLJJzp+x3eMQ==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/plugin-content-pages@3.6.0': - resolution: {integrity: sha512-RKHhJrfkadHc7+tt1cP48NWifOrhkSRMPdXNYytzhoQrXlP6Ph+3tfQ4/n+nT0S3Y9+wwRxYqRqA380ZLt+QtQ==} + '@docusaurus/plugin-content-pages@3.6.1': + resolution: {integrity: sha512-TZtL+2zq20gqGalzoIT2rEF1T4YCZ26jTvlCJXs78+incIajfdHtmdOq7rQW0oV7oqTjpGllbp788nY/vY9jgw==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/plugin-debug@3.6.0': - resolution: {integrity: sha512-o8T1Rl94COLdSlKvjYLQpRJQRU8WWZ8EX1B0yV0dQLNN8reyH7MQW+6z1ig4sQFfH3pnjPWVGHfuEjcib5m7Eg==} + '@docusaurus/plugin-debug@3.6.1': + resolution: {integrity: sha512-DeKPZtoVExDSYCbzoz7y5Dhc6+YPqRWfVGwEEUyKopSyQYefp0OV8hvASmbJCn2WyThRgspOUhog3FSEhz+agw==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/plugin-google-analytics@3.6.0': - resolution: {integrity: sha512-kgRFbfpi6Hshj75YUztKyEMtI/kw0trPRwoTN4g+W1NK99R/vh8phTvhBTIMnDbetU79795LkwfG0rZ/ce6zWQ==} + '@docusaurus/plugin-google-analytics@3.6.1': + resolution: {integrity: sha512-ZEoERiDHxSfhaEeT35ukQ892NzGHWiUvfxUsnPiRuGEhMoQlxMSp60shBuSZ1sUKuZlndoEl5qAXJg09Wls/Sg==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/plugin-google-gtag@3.6.0': - resolution: {integrity: sha512-nqu4IfjaO4UX+dojHL2BxHRS+sKj31CIMWYo49huQ3wTET0Oc3u/WGTaKd3ShTPDhkgiRhTOSTPUwJWrU55nHg==} + '@docusaurus/plugin-google-gtag@3.6.1': + resolution: {integrity: sha512-u/E9vXUsZxYaV6Brvfee8NiH/iR0cMml9P/ifz4EpH/Jfxdbw8rbCT0Nm/h7EFgEY48Uqkl5huSbIvFB9n8aTQ==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/plugin-google-tag-manager@3.6.0': - resolution: {integrity: sha512-OU6c5xI0nOVbEc9eImGvvsgNWe4vGm97t/W3aLHjWsHyNk3uwFNBQMHRvBUwAi9k/K3kyC5E7DWnc67REhdLOw==} + '@docusaurus/plugin-google-tag-manager@3.6.1': + resolution: {integrity: sha512-By+NKkGYV8tSo8/RyS1OXikOtqsko5jJZ/uioJfBjsBGgSbiMJ+Y/HogFBke0mgSvf7NPGKZTbYm5+FJ8YUtPQ==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/plugin-sitemap@3.6.0': - resolution: {integrity: sha512-YB5XMdf9FjLhgbHY/cDbYhVxsgcpPIjxY9769HUgFOB7GVzItTLOR71W035R1BiR2CA5QAn3XOSg36WLRxlhQQ==} + '@docusaurus/plugin-sitemap@3.6.1': + resolution: {integrity: sha512-i8R/GTKew4Cufb+7YQTwfPcNOhKTJzZ1VZ5OqQwI9c3pZK2TltQyhqKDVN94KCTbSSKvOYYytYfRAB2uPnH1/A==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/preset-classic@3.6.0': - resolution: {integrity: sha512-kpGNdQzr/Dpm7o3b1iaQrz4DMDx3WIeBbl4V4P4maa2zAQkTdlaP4CMgA5oKrRrpqPLnQFsUM/b+qf2glhl2Tw==} + '@docusaurus/preset-classic@3.6.1': + resolution: {integrity: sha512-b90Y1XRH9e+oa/E3NmiFEFOwgYUd+knFcZUy81nM3FJs038WbEA0T55NQsuPW0s7nOsCShQ7dVFyKxV+Wp31Nw==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 @@ -1552,59 +1664,49 @@ packages: peerDependencies: react: '*' - '@docusaurus/theme-classic@3.6.0': - resolution: {integrity: sha512-sAXNfwPL6uRD+BuHuKXZfAXud7SS7IK/JdrPuzyQxdO1gJKzI5GFfe1ED1QoJDNWJWJ01JHE5rSnwYLEADc2rQ==} + '@docusaurus/theme-classic@3.6.1': + resolution: {integrity: sha512-5lVUmIXk7zp+n9Ki2lYWrmhbd6mssOlKCnnDJvY4QDi3EgjRisIu5g4yKXoWTIbiqE7m7q/dS9cbeShEtfkKng==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/theme-common@3.6.0': - resolution: {integrity: sha512-frjlYE5sRs+GuPs4XXlp9aMLI2O4H5FPpznDAXBrCm+8EpWRiIb443ePMxM3IyMCQ5bwFlki0PI9C+r4apstnw==} + '@docusaurus/theme-common@3.6.1': + resolution: {integrity: sha512-18iEYNpMvarGfq9gVRpGowSZD24vZ39Iz4acqaj64180i54V9el8tVnhNr/wRvrUm1FY30A1NHLqnMnDz4rYEQ==} engines: {node: '>=18.0'} peerDependencies: '@docusaurus/plugin-content-docs': '*' react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/theme-search-algolia@3.6.0': - resolution: {integrity: sha512-4IwRUkxjrisR8LXBHeE4d2btraWdMficbgiVL3UHvJURmyvgzMBZQP8KrK8rjdXeu8SuRxSmeV6NSVomRvdbEg==} + '@docusaurus/theme-search-algolia@3.6.1': + resolution: {integrity: sha512-BjmuiFRpQP1WEm8Mzu1Bb0Wdas6G65VHXDDNr7XTKgbstxalE6vuxt0ioXTDFS2YVep5748aVhKvnxR9gm2Liw==} engines: {node: '>=18.0'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/theme-translations@3.6.0': - resolution: {integrity: sha512-L555X8lWE3fv8VaF0Bc1VnAgi10UvRKFcvADHiYR7Gj37ItaWP5i7xLHsSw7fi/SHTXe5wfIeCFNqUYHyCOHAQ==} + '@docusaurus/theme-translations@3.6.1': + resolution: {integrity: sha512-bNm5G6sueUezvyhsBegA1wwM38yW0BnqpZTE9KHO2yKnkERNMaV5x/yPJ/DNCOHjJtCcJ5Uz55g2AS75Go31xA==} engines: {node: '>=18.0'} - '@docusaurus/types@3.6.0': - resolution: {integrity: sha512-jADLgoZGWhAzThr+mRiyuFD4OUzt6jHnb7NRArRKorgxckqUBaPyFOau9hhbcSTHtU6ceyeWjN7FDt7uG2Hplw==} + '@docusaurus/types@3.6.1': + resolution: {integrity: sha512-hCB1hj9DYutVYBisnPNobz9SzEmCcf1EetJv09O49Cov3BqOkm+vnnjB3d957YJMtpLGQoKBeN/FF1DZ830JwQ==} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@docusaurus/utils-common@3.6.0': - resolution: {integrity: sha512-diUDNfbw33GaZMmKwdTckT2IBfVouXLXRD+zphH9ywswuaEIKqixvuf5g41H7MBBrlMsxhna3uTMoB4B/OPDcA==} + '@docusaurus/utils-common@3.6.1': + resolution: {integrity: sha512-LX1qiTiC0aS8c92uZ+Wj2iNCNJyYZJIKY8/nZDKNMBfo759VYVS3RX3fKP3DznB+16sYp7++MyCz/T6fOGaRfw==} engines: {node: '>=18.0'} - peerDependencies: - '@docusaurus/types': '*' - peerDependenciesMeta: - '@docusaurus/types': - optional: true - '@docusaurus/utils-validation@3.6.0': - resolution: {integrity: sha512-CRHiKKJEKA0GFlfOf71JWHl7PtwOyX0+Zg9ep9NFEZv6Lcx3RJ9nhl7p8HRjPL6deyYceavM//BsfW4pCI4BtA==} + '@docusaurus/utils-validation@3.6.1': + resolution: {integrity: sha512-+iMd6zRl5cJQm7nUP+7pSO/oAXsN79eHO34ME7l2YJt4GEAr70l5kkD58u2jEPpp+wSXT70c7x2A2lzJI1E8jw==} engines: {node: '>=18.0'} - '@docusaurus/utils@3.6.0': - resolution: {integrity: sha512-VKczAutI4mptiAw/WcYEu5WeVhQ6Q1zdIUl64SGw9K++9lziH+Kt10Ee8l2dMpRkiUk6zzK20kMNlX2WCUwXYQ==} + '@docusaurus/utils@3.6.1': + resolution: {integrity: sha512-nS3WCvepwrnBEgSG5vQu40XG95lC9Jeh/odV5u5IhU1eQFEGDst9xBi6IK5yZdsGvbuaXBZLZtOqWYtuuFa/rQ==} engines: {node: '>=18.0'} - peerDependencies: - '@docusaurus/types': '*' - peerDependenciesMeta: - '@docusaurus/types': - optional: true '@es-joy/jsdoccomment@0.41.0': resolution: {integrity: sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw==} @@ -1779,6 +1881,54 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@inquirer/checkbox@1.5.2': + resolution: {integrity: sha512-CifrkgQjDkUkWexmgYYNyB5603HhTHI91vLFeQXh6qrTKiCMVASol01Rs1cv6LP/A2WccZSRlJKZhbaBIs/9ZA==} + engines: {node: '>=14.18.0'} + + '@inquirer/confirm@2.0.17': + resolution: {integrity: sha512-EqzhGryzmGpy2aJf6LxJVhndxYmFs+m8cxXzf8nejb1DE3sabf6mUgBcp4J0jAUEiAcYzqmkqRr7LPFh/WdnXA==} + engines: {node: '>=14.18.0'} + + '@inquirer/core@5.1.2': + resolution: {integrity: sha512-w3PMZH5rahrukn8/I7P9Ihil+twgLTUHDZtJlJyBbUKyPaOSSQjLZkb0PpncVhin1gCaMgOFXy6iNPgcZUoo2w==} + engines: {node: '>=14.18.0'} + + '@inquirer/core@6.0.0': + resolution: {integrity: sha512-fKi63Khkisgda3ohnskNf5uZJj+zXOaBvOllHsOkdsXRA/ubQLJQrZchFFi57NKbZzkTunXiBMdvWOv71alonw==} + engines: {node: '>=14.18.0'} + + '@inquirer/editor@1.2.15': + resolution: {integrity: sha512-gQ77Ls09x5vKLVNMH9q/7xvYPT6sIs5f7URksw+a2iJZ0j48tVS6crLqm2ugG33tgXHIwiEqkytY60Zyh5GkJQ==} + engines: {node: '>=14.18.0'} + + '@inquirer/expand@1.1.16': + resolution: {integrity: sha512-TGLU9egcuo+s7PxphKUCnJnpCIVY32/EwPCLLuu+gTvYiD8hZgx8Z2niNQD36sa6xcfpdLY6xXDBiL/+g1r2XQ==} + engines: {node: '>=14.18.0'} + + '@inquirer/input@1.2.16': + resolution: {integrity: sha512-Ou0LaSWvj1ni+egnyQ+NBtfM1885UwhRCMtsRt2bBO47DoC1dwtCa+ZUNgrxlnCHHF0IXsbQHYtIIjFGAavI4g==} + engines: {node: '>=14.18.0'} + + '@inquirer/password@1.1.16': + resolution: {integrity: sha512-aZYZVHLUXZ2gbBot+i+zOJrks1WaiI95lvZCn1sKfcw6MtSSlYC8uDX8sTzQvAsQ8epHoP84UNvAIT0KVGOGqw==} + engines: {node: '>=14.18.0'} + + '@inquirer/prompts@3.3.2': + resolution: {integrity: sha512-k52mOMRvTUejrqyF1h8Z07chC+sbaoaUYzzr1KrJXyj7yaX7Nrh0a9vktv8TuocRwIJOQMaj5oZEmkspEcJFYQ==} + engines: {node: '>=14.18.0'} + + '@inquirer/rawlist@1.2.16': + resolution: {integrity: sha512-pZ6TRg2qMwZAOZAV6TvghCtkr53dGnK29GMNQ3vMZXSNguvGqtOVc4j/h1T8kqGJFagjyfBZhUPGwNS55O5qPQ==} + engines: {node: '>=14.18.0'} + + '@inquirer/select@1.3.3': + resolution: {integrity: sha512-RzlRISXWqIKEf83FDC9ZtJ3JvuK1l7aGpretf41BCWYrvla2wU8W8MTRNMiPrPJ+1SIqrRC1nZdZ60hD9hRXLg==} + engines: {node: '>=14.18.0'} + + '@inquirer/type@1.5.5': + resolution: {integrity: sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==} + engines: {node: '>=18'} + '@ipld/car@5.3.3': resolution: {integrity: sha512-4vgV5Ml4HCJ2iTx7vYhu0ui+Xxo1HQTtVeYgD+JKd5Wij8TlOFZnxOSickqpLcuf1fdGEStgqVItx15UWfzDYA==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} @@ -2081,6 +2231,9 @@ packages: '@types/bonjour@3.5.13': resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} + '@types/configstore@6.0.2': + resolution: {integrity: sha512-OS//b51j9uyR3zvwD04Kfs5kHpve2qalQ18JhY/ho3voGYUTPLEG90/ocfKPI48hyHH8T04f7KEEbK6Ue60oZQ==} + '@types/connect-history-api-fallback@1.5.4': resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} @@ -2165,6 +2318,9 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/mute-stream@0.0.4': + resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + '@types/node-forge@1.3.11': resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} @@ -2183,8 +2339,8 @@ packages: '@types/prop-types@15.7.13': resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} - '@types/qs@6.9.16': - resolution: {integrity: sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==} + '@types/qs@6.9.17': + resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -2240,9 +2396,15 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/update-notifier@6.0.8': + resolution: {integrity: sha512-IlDFnfSVfYQD+cKIg63DEXn3RFmd7W1iYtKQsJodcHK9R1yr8aKbKaPKfBxzPpcHCq2DU8zUq4PIPmy19Thjfg==} + '@types/varint@6.0.3': resolution: {integrity: sha512-DHukoGWdJ2aYkveZJTB2rN2lp6m7APzVsoJQ7j/qy1fQxyamJTPD5xQzCMoJ2Qtgn0mE3wWeNOpbTyBFvF+dyA==} + '@types/wrap-ansi@3.0.0': + resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} + '@types/ws@8.5.13': resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} @@ -2367,50 +2529,53 @@ packages: '@web3-storage/data-segment@5.3.0': resolution: {integrity: sha512-zFJ4m+pEKqtKatJNsFrk/2lHeFSbkXZ6KKXjBe7/2ayA9wAar7T/unewnOcZrrZTnCWmaxKsXWqdMFy9bXK9dw==} - '@webassemblyjs/ast@1.12.1': - resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} + '@web3-storage/sigv4@1.0.2': + resolution: {integrity: sha512-ZUXKK10NmuQgPkqByhb1H3OQxkIM0CIn2BMPhGQw7vQw8WIzrBkk9IJiAVfJ/UVBFrf6uzPbx2lEBLt4diCMnQ==} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} - '@webassemblyjs/floating-point-hex-parser@1.11.6': - resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==} + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} - '@webassemblyjs/helper-api-error@1.11.6': - resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} - '@webassemblyjs/helper-buffer@1.12.1': - resolution: {integrity: sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==} + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} - '@webassemblyjs/helper-numbers@1.11.6': - resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==} + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} - '@webassemblyjs/helper-wasm-bytecode@1.11.6': - resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} - '@webassemblyjs/helper-wasm-section@1.12.1': - resolution: {integrity: sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==} + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} - '@webassemblyjs/ieee754@1.11.6': - resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==} + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} - '@webassemblyjs/leb128@1.11.6': - resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==} + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} - '@webassemblyjs/utf8@1.11.6': - resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} - '@webassemblyjs/wasm-edit@1.12.1': - resolution: {integrity: sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==} + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} - '@webassemblyjs/wasm-gen@1.12.1': - resolution: {integrity: sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==} + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} - '@webassemblyjs/wasm-opt@1.12.1': - resolution: {integrity: sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==} + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} - '@webassemblyjs/wasm-parser@1.12.1': - resolution: {integrity: sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==} + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} - '@webassemblyjs/wast-printer@1.12.1': - resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -2486,8 +2651,8 @@ packages: algoliasearch@4.24.0: resolution: {integrity: sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==} - algoliasearch@5.12.0: - resolution: {integrity: sha512-psGBRYdGgik8I6m28iAB8xpubvjEt7UQU+w5MAJUA2324WHiGoHap5BPkkjB14rMaXeRts6pmOsrVIglGyOVwg==} + algoliasearch@5.13.0: + resolution: {integrity: sha512-04lyQX3Ev/oLYQx+aagamQDXvkUUfX1mwrLrus15+9fNaYj28GDxxEzbwaRfvmHFcZyoxvup7mMtDTTw8SrTEQ==} engines: {node: '>= 14.0.0'} ansi-align@3.0.1: @@ -2505,6 +2670,10 @@ packages: resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==} engines: {node: '>=12'} + ansi-escapes@6.2.1: + resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} + engines: {node: '>=14.16'} + ansi-escapes@7.0.0: resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} engines: {node: '>=18'} @@ -2514,6 +2683,10 @@ packages: engines: {'0': node >= 0.8.0} hasBin: true + ansi-regex@1.1.1: + resolution: {integrity: sha512-q5i8bFLg2wDfsuR56c1NzlJFPzVD+9mxhDrhqOGigEFa87OZHlF+9dWeGWzVTP/0ECiA/JUGzfzRr2t3eYORRw==} + engines: {node: '>=0.10.0'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2525,6 +2698,10 @@ packages: ansi-sequence-parser@1.1.1: resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==} + ansi-styles@2.2.1: + resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} + engines: {node: '>=0.10.0'} + ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -2547,6 +2724,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + archy@0.0.2: + resolution: {integrity: sha512-8mMsetjXv4pCPTrMbPPO2cxy9vzJn2jwbd+ug+mf8fEUZG2E78Vo5erJMjrnGuLTKqOLtS5ulFHJSfg1yaCjxA==} + are-docs-informative@0.0.2: resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} engines: {node: '>=14'} @@ -2645,6 +2825,10 @@ packages: batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} @@ -2681,6 +2865,14 @@ packages: resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} engines: {node: '>=14.16'} + boxen@8.0.1: + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} + engines: {node: '>=18'} + + bplist-parser@0.2.0: + resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} + engines: {node: '>= 5.10.0'} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -2712,6 +2904,10 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} + bundle-name@3.0.0: + resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} + engines: {node: '>=12'} + bytes@3.0.0: resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} engines: {node: '>= 0.8'} @@ -2777,8 +2973,8 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001677: - resolution: {integrity: sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==} + caniuse-lite@1.0.30001679: + resolution: {integrity: sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==} carstream@1.1.1: resolution: {integrity: sha512-cgn3TqHo6SPsHBTfM5QgXngv6HtwgO1bKCHcdS35vBrweLcYrIG/+UboCbvnIGA0k8NtAYl/DvDdej/9pZGZxQ==} @@ -2793,6 +2989,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chalk@1.0.0: + resolution: {integrity: sha512-1TE3hpADga5iWinlcCpyhC7fTl9uQumLD8i2jJoJeVg7UbveY5jj7F6uCq8w0hQpSeLhaPn5QFe8e56toMVP1A==} + engines: {node: '>=0.10.0'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -2821,6 +3021,9 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} @@ -2873,6 +3076,10 @@ packages: resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -2969,6 +3176,10 @@ packages: resolution: {integrity: sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==} engines: {node: '>=12'} + configstore@7.0.0: + resolution: {integrity: sha512-yk7/5PN5im4qwz0WFZW3PXnzHgPu9mX29Y8uZ3aefe2lBPC1FYttWZRcaW9fKkT0pBCJyuQ2HfbmPVaODi9jcQ==} + engines: {node: '>=18'} + connect-history-api-fallback@2.0.0: resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} engines: {node: '>=0.8'} @@ -3050,14 +3261,18 @@ packages: resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} engines: {node: '>=4.8'} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.5: + resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} engines: {node: '>= 8'} crypto-random-string@4.0.0: resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} engines: {node: '>=12'} + crypto-random-string@5.0.0: + resolution: {integrity: sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ==} + engines: {node: '>=14.16'} + css-declaration-sorter@7.2.0: resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} engines: {node: ^14 || ^16 || >=18} @@ -3222,6 +3437,14 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + default-browser-id@3.0.0: + resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} + engines: {node: '>=12'} + + default-browser@4.0.0: + resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} + engines: {node: '>=14.16'} + default-gateway@6.0.3: resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} engines: {node: '>= 10'} @@ -3238,6 +3461,10 @@ packages: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -3349,6 +3576,10 @@ packages: resolution: {integrity: sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dot-prop@9.0.0: + resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==} + engines: {node: '>=18'} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -3362,8 +3593,8 @@ packages: resolution: {integrity: sha512-M9qw6oUILGVrcENMSRRefE1MbHPIz0h79EKIeJWK9v563aT9Qkh8aEHPO1H5vi970wPirNY+jO9OpFoLiMsMGA==} engines: {node: '>=6'} - electron-to-chromium@1.5.51: - resolution: {integrity: sha512-kKeWV57KSS8jH4alKt/jKnvHPmJgBxXzGUSbMd4eQF+iOsVPl7bz2KUmu6eo80eMP8wVioTfTyTzdMgM15WXNg==} + electron-to-chromium@1.5.55: + resolution: {integrity: sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==} emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} @@ -3625,6 +3856,10 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -3675,6 +3910,10 @@ packages: peerDependencies: webpack: ^4.0.0 || ^5.0.0 + files-from-path@1.0.4: + resolution: {integrity: sha512-sMNIVdpRh1uCSIaat3qnM3E6aA1C5FVn5/B16z8sN3gIMjZPkxtVCorkEL07xTcCIxVwTXzjU1Ota7Wif6RfQQ==} + engines: {node: '>=18'} + filesize@8.0.7: resolution: {integrity: sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==} engines: {node: '>= 0.4.0'} @@ -3811,6 +4050,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.3.0: + resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} + engines: {node: '>=18'} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -3821,6 +4064,10 @@ packages: get-own-enumerable-property-symbols@3.0.2: resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + get-stdin@4.0.1: + resolution: {integrity: sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==} + engines: {node: '>=0.10.0'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -3860,6 +4107,10 @@ packages: engines: {node: '>=12'} deprecated: Glob versions prior to v9 are no longer supported + global-directory@4.0.1: + resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} + engines: {node: '>=18'} + global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} @@ -3943,6 +4194,11 @@ packages: engines: {node: '>=0.4.7'} hasBin: true + has-ansi@1.0.3: + resolution: {integrity: sha512-XwLzIec2hoj/LW9F3nCcQpEwZ5fDJ1LOc6SAgc0pz79CGiY9zmZhIkbf7OnK+tC36UhpQBa03HPt13QavGoF6Q==} + engines: {node: '>=0.10.0'} + hasBin: true + has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -4181,6 +4437,10 @@ packages: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} + ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + inline-style-parser@0.1.1: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} @@ -4286,6 +4546,11 @@ packages: engines: {node: '>=8'} hasBin: true + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-electron@2.2.2: resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} @@ -4316,10 +4581,24 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-in-ci@1.0.0: + resolution: {integrity: sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==} + engines: {node: '>=18'} + hasBin: true + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-installed-globally@0.4.0: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} + is-installed-globally@1.0.0: + resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==} + engines: {node: '>=18'} + is-interactive@2.0.0: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} @@ -4360,6 +4639,10 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + is-path-inside@4.0.0: + resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} + engines: {node: '>=12'} + is-plain-obj@2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} @@ -4620,10 +4903,18 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + ky@1.7.2: + resolution: {integrity: sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg==} + engines: {node: '>=18'} + latest-version@7.0.0: resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} engines: {node: '>=14.16'} + latest-version@9.0.0: + resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==} + engines: {node: '>=18'} + launch-editor@2.9.1: resolution: {integrity: sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==} @@ -5126,6 +5417,10 @@ packages: resolution: {integrity: sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==} engines: {node: '>=8.0.0'} + mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -5276,6 +5571,10 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} + open@9.1.0: + resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} + engines: {node: '>=14.16'} + opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -5288,6 +5587,10 @@ packages: resolution: {integrity: sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==} engines: {node: '>=16'} + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + p-cancelable@3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} @@ -5378,6 +5681,10 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-json@10.0.1: + resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==} + engines: {node: '>=18'} + package-json@8.1.1: resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} engines: {node: '>=14.16'} @@ -5791,6 +6098,11 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + pretty-error@4.0.0: resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} @@ -5798,6 +6110,9 @@ packages: resolution: {integrity: sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==} engines: {node: '>=4'} + pretty-tree@1.0.0: + resolution: {integrity: sha512-BfBHciLsD6KtzG60Lfz6+FRWAjLvBwembXOzJg8lUqYe5eA7ujWB+9wZgrlN4lskhcgUEuIj8OFkIHwxUyh+tQ==} + prism-react-renderer@2.4.0: resolution: {integrity: sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==} peerDependencies: @@ -6137,12 +6452,23 @@ packages: engines: {node: '>=12.0.0'} hasBin: true + run-applescript@5.0.0: + resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} + engines: {node: '>=12'} + + run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + s-ago@2.2.0: + resolution: {integrity: sha512-t6Q/aFCCJSBf5UUkR/WH0mDHX8EGm2IBQ7nQLobVLsdxOlkryYMbOlwu2D4Cf7jPUp0v1LhfPgvIZNoi9k8lUA==} + sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -6397,8 +6723,8 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} stdin-discarder@0.1.0: resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} @@ -6426,6 +6752,10 @@ packages: resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} engines: {node: '>=16'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + string.prototype.padend@3.1.6: resolution: {integrity: sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==} engines: {node: '>= 0.4'} @@ -6454,6 +6784,11 @@ packages: resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} engines: {node: '>=4'} + strip-ansi@2.0.1: + resolution: {integrity: sha512-2h8q2CP3EeOhDJ+jd932PRMpa3/pOJFGoF22J1U/DNbEK2gSW2DqeF46VjCXsSQXhC+k/l8/gaaRBQKL6hUPfQ==} + engines: {node: '>=0.10.0'} + hasBin: true + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -6501,6 +6836,11 @@ packages: peerDependencies: postcss: ^8.4.31 + supports-color@1.3.1: + resolution: {integrity: sha512-OHbMkscHFRcNWEcW80fYhCrzAjheSIBwJChpFaBqA6zEz53nxumqi6ukciRb/UA0/v2nDNMk28ce/uBbYRDsng==} + engines: {node: '>=0.8.0'} + hasBin: true + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -6596,6 +6936,14 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + titleize@3.0.0: + resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} + engines: {node: '>=12'} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -6796,6 +7144,10 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + update-browserslist-db@1.1.1: resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true @@ -6806,6 +7158,10 @@ packages: resolution: {integrity: sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==} engines: {node: '>=14.16'} + update-notifier@7.3.1: + resolution: {integrity: sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==} + engines: {node: '>=18'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -6992,6 +7348,10 @@ packages: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} engines: {node: '>=12'} + widest-line@5.0.0: + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} + wildcard@2.0.1: resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} @@ -7005,6 +7365,10 @@ packages: workerpool@6.5.1: resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -7013,6 +7377,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -7099,38 +7467,33 @@ packages: snapshots: - '@algolia/autocomplete-core@1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)(search-insights@2.17.2)': + '@algolia/autocomplete-core@1.17.6(@algolia/client-search@5.13.0)(algoliasearch@5.13.0)(search-insights@2.17.2)': dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)(search-insights@2.17.2) - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0) + '@algolia/autocomplete-plugin-algolia-insights': 1.17.6(@algolia/client-search@5.13.0)(algoliasearch@5.13.0)(search-insights@2.17.2) + '@algolia/autocomplete-shared': 1.17.6(@algolia/client-search@5.13.0)(algoliasearch@5.13.0) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights - '@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)(search-insights@2.17.2)': + '@algolia/autocomplete-plugin-algolia-insights@1.17.6(@algolia/client-search@5.13.0)(algoliasearch@5.13.0)(search-insights@2.17.2)': dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0) + '@algolia/autocomplete-shared': 1.17.6(@algolia/client-search@5.13.0)(algoliasearch@5.13.0) search-insights: 2.17.2 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - '@algolia/autocomplete-preset-algolia@1.17.6(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)': - dependencies: - '@algolia/autocomplete-shared': 1.17.6(@algolia/client-search@5.12.0)(algoliasearch@5.12.0) - '@algolia/client-search': 5.12.0 - algoliasearch: 5.12.0 - - '@algolia/autocomplete-shared@1.17.6(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)': + '@algolia/autocomplete-preset-algolia@1.17.6(@algolia/client-search@5.13.0)(algoliasearch@5.13.0)': dependencies: - '@algolia/client-search': 5.12.0 - algoliasearch: 5.12.0 + '@algolia/autocomplete-shared': 1.17.6(@algolia/client-search@5.13.0)(algoliasearch@5.13.0) + '@algolia/client-search': 5.13.0 + algoliasearch: 5.13.0 - '@algolia/autocomplete-shared@1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)': + '@algolia/autocomplete-shared@1.17.6(@algolia/client-search@5.13.0)(algoliasearch@5.13.0)': dependencies: - '@algolia/client-search': 5.12.0 - algoliasearch: 5.12.0 + '@algolia/client-search': 5.13.0 + algoliasearch: 5.13.0 '@algolia/cache-browser-local-storage@4.24.0': dependencies: @@ -7142,12 +7505,12 @@ snapshots: dependencies: '@algolia/cache-common': 4.24.0 - '@algolia/client-abtesting@5.12.0': + '@algolia/client-abtesting@5.13.0': dependencies: - '@algolia/client-common': 5.12.0 - '@algolia/requester-browser-xhr': 5.12.0 - '@algolia/requester-fetch': 5.12.0 - '@algolia/requester-node-http': 5.12.0 + '@algolia/client-common': 5.13.0 + '@algolia/requester-browser-xhr': 5.13.0 + '@algolia/requester-fetch': 5.13.0 + '@algolia/requester-node-http': 5.13.0 '@algolia/client-account@4.24.0': dependencies: @@ -7162,26 +7525,26 @@ snapshots: '@algolia/requester-common': 4.24.0 '@algolia/transporter': 4.24.0 - '@algolia/client-analytics@5.12.0': + '@algolia/client-analytics@5.13.0': dependencies: - '@algolia/client-common': 5.12.0 - '@algolia/requester-browser-xhr': 5.12.0 - '@algolia/requester-fetch': 5.12.0 - '@algolia/requester-node-http': 5.12.0 + '@algolia/client-common': 5.13.0 + '@algolia/requester-browser-xhr': 5.13.0 + '@algolia/requester-fetch': 5.13.0 + '@algolia/requester-node-http': 5.13.0 '@algolia/client-common@4.24.0': dependencies: '@algolia/requester-common': 4.24.0 '@algolia/transporter': 4.24.0 - '@algolia/client-common@5.12.0': {} + '@algolia/client-common@5.13.0': {} - '@algolia/client-insights@5.12.0': + '@algolia/client-insights@5.13.0': dependencies: - '@algolia/client-common': 5.12.0 - '@algolia/requester-browser-xhr': 5.12.0 - '@algolia/requester-fetch': 5.12.0 - '@algolia/requester-node-http': 5.12.0 + '@algolia/client-common': 5.13.0 + '@algolia/requester-browser-xhr': 5.13.0 + '@algolia/requester-fetch': 5.13.0 + '@algolia/requester-node-http': 5.13.0 '@algolia/client-personalization@4.24.0': dependencies: @@ -7189,19 +7552,19 @@ snapshots: '@algolia/requester-common': 4.24.0 '@algolia/transporter': 4.24.0 - '@algolia/client-personalization@5.12.0': + '@algolia/client-personalization@5.13.0': dependencies: - '@algolia/client-common': 5.12.0 - '@algolia/requester-browser-xhr': 5.12.0 - '@algolia/requester-fetch': 5.12.0 - '@algolia/requester-node-http': 5.12.0 + '@algolia/client-common': 5.13.0 + '@algolia/requester-browser-xhr': 5.13.0 + '@algolia/requester-fetch': 5.13.0 + '@algolia/requester-node-http': 5.13.0 - '@algolia/client-query-suggestions@5.12.0': + '@algolia/client-query-suggestions@5.13.0': dependencies: - '@algolia/client-common': 5.12.0 - '@algolia/requester-browser-xhr': 5.12.0 - '@algolia/requester-fetch': 5.12.0 - '@algolia/requester-node-http': 5.12.0 + '@algolia/client-common': 5.13.0 + '@algolia/requester-browser-xhr': 5.13.0 + '@algolia/requester-fetch': 5.13.0 + '@algolia/requester-node-http': 5.13.0 '@algolia/client-search@4.24.0': dependencies: @@ -7209,21 +7572,21 @@ snapshots: '@algolia/requester-common': 4.24.0 '@algolia/transporter': 4.24.0 - '@algolia/client-search@5.12.0': + '@algolia/client-search@5.13.0': dependencies: - '@algolia/client-common': 5.12.0 - '@algolia/requester-browser-xhr': 5.12.0 - '@algolia/requester-fetch': 5.12.0 - '@algolia/requester-node-http': 5.12.0 + '@algolia/client-common': 5.13.0 + '@algolia/requester-browser-xhr': 5.13.0 + '@algolia/requester-fetch': 5.13.0 + '@algolia/requester-node-http': 5.13.0 '@algolia/events@4.0.1': {} - '@algolia/ingestion@1.12.0': + '@algolia/ingestion@1.13.0': dependencies: - '@algolia/client-common': 5.12.0 - '@algolia/requester-browser-xhr': 5.12.0 - '@algolia/requester-fetch': 5.12.0 - '@algolia/requester-node-http': 5.12.0 + '@algolia/client-common': 5.13.0 + '@algolia/requester-browser-xhr': 5.13.0 + '@algolia/requester-fetch': 5.13.0 + '@algolia/requester-node-http': 5.13.0 '@algolia/logger-common@4.24.0': {} @@ -7231,12 +7594,12 @@ snapshots: dependencies: '@algolia/logger-common': 4.24.0 - '@algolia/monitoring@1.12.0': + '@algolia/monitoring@1.13.0': dependencies: - '@algolia/client-common': 5.12.0 - '@algolia/requester-browser-xhr': 5.12.0 - '@algolia/requester-fetch': 5.12.0 - '@algolia/requester-node-http': 5.12.0 + '@algolia/client-common': 5.13.0 + '@algolia/requester-browser-xhr': 5.13.0 + '@algolia/requester-fetch': 5.13.0 + '@algolia/requester-node-http': 5.13.0 '@algolia/recommend@4.24.0': dependencies: @@ -7252,34 +7615,34 @@ snapshots: '@algolia/requester-node-http': 4.24.0 '@algolia/transporter': 4.24.0 - '@algolia/recommend@5.12.0': + '@algolia/recommend@5.13.0': dependencies: - '@algolia/client-common': 5.12.0 - '@algolia/requester-browser-xhr': 5.12.0 - '@algolia/requester-fetch': 5.12.0 - '@algolia/requester-node-http': 5.12.0 + '@algolia/client-common': 5.13.0 + '@algolia/requester-browser-xhr': 5.13.0 + '@algolia/requester-fetch': 5.13.0 + '@algolia/requester-node-http': 5.13.0 '@algolia/requester-browser-xhr@4.24.0': dependencies: '@algolia/requester-common': 4.24.0 - '@algolia/requester-browser-xhr@5.12.0': + '@algolia/requester-browser-xhr@5.13.0': dependencies: - '@algolia/client-common': 5.12.0 + '@algolia/client-common': 5.13.0 '@algolia/requester-common@4.24.0': {} - '@algolia/requester-fetch@5.12.0': + '@algolia/requester-fetch@5.13.0': dependencies: - '@algolia/client-common': 5.12.0 + '@algolia/client-common': 5.13.0 '@algolia/requester-node-http@4.24.0': dependencies: '@algolia/requester-common': 4.24.0 - '@algolia/requester-node-http@5.12.0': + '@algolia/requester-node-http@5.13.0': dependencies: - '@algolia/client-common': 5.12.0 + '@algolia/client-common': 5.13.0 '@algolia/transporter@4.24.0': dependencies: @@ -8079,14 +8442,14 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@docsearch/css@3.6.3': {} + '@docsearch/css@3.7.0': {} - '@docsearch/react@3.6.3(@algolia/client-search@5.12.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)': + '@docsearch/react@3.7.0(@algolia/client-search@5.13.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)': dependencies: - '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)(search-insights@2.17.2) - '@algolia/autocomplete-preset-algolia': 1.17.6(@algolia/client-search@5.12.0)(algoliasearch@5.12.0) - '@docsearch/css': 3.6.3 - algoliasearch: 5.12.0 + '@algolia/autocomplete-core': 1.17.6(@algolia/client-search@5.13.0)(algoliasearch@5.13.0)(search-insights@2.17.2) + '@algolia/autocomplete-preset-algolia': 1.17.6(@algolia/client-search@5.13.0)(algoliasearch@5.13.0) + '@docsearch/css': 3.7.0 + algoliasearch: 5.13.0 optionalDependencies: '@types/react': 18.3.12 react: 18.3.1 @@ -8095,7 +8458,7 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/babel@3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2)': + '@docusaurus/babel@3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: '@babel/core': 7.26.0 '@babel/generator': 7.26.2 @@ -8107,28 +8470,30 @@ snapshots: '@babel/runtime': 7.26.0 '@babel/runtime-corejs3': 7.26.0 '@babel/traverse': 7.25.9 - '@docusaurus/logger': 3.6.0 - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/logger': 3.6.1 + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) babel-plugin-dynamic-import-node: 2.3.3 fs-extra: 11.2.0 tslib: 2.8.1 transitivePeerDependencies: - - '@docusaurus/types' - '@swc/core' + - acorn - esbuild + - react + - react-dom - supports-color - typescript - uglify-js - webpack-cli - '@docusaurus/bundler@3.6.0(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + '@docusaurus/bundler@3.6.1(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: '@babel/core': 7.26.0 - '@docusaurus/babel': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/cssnano-preset': 3.6.0 - '@docusaurus/logger': 3.6.0 - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/babel': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/cssnano-preset': 3.6.1 + '@docusaurus/logger': 3.6.1 + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) autoprefixer: 10.4.20(postcss@8.4.47) babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.96.1) clean-css: 5.3.3 @@ -8166,15 +8531,15 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/core@3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + '@docusaurus/core@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: - '@docusaurus/babel': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/bundler': 3.6.0(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/logger': 3.6.0 - '@docusaurus/mdx-loader': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/utils-common': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/babel': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/bundler': 3.6.1(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/logger': 3.6.1 + '@docusaurus/mdx-loader': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils-common': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) '@mdx-js/react': 3.1.0(@types/react@18.3.12)(react@18.3.1) boxen: 6.2.1 chalk: 4.1.2 @@ -8216,7 +8581,6 @@ snapshots: webpack-merge: 6.0.1 transitivePeerDependencies: - '@docusaurus/faster' - - '@docusaurus/types' - '@parcel/css' - '@rspack/core' - '@swc/core' @@ -8235,23 +8599,23 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/cssnano-preset@3.6.0': + '@docusaurus/cssnano-preset@3.6.1': dependencies: cssnano-preset-advanced: 6.1.2(postcss@8.4.47) postcss: 8.4.47 postcss-sort-media-queries: 5.2.0(postcss@8.4.47) tslib: 2.8.1 - '@docusaurus/logger@3.6.0': + '@docusaurus/logger@3.6.1': dependencies: chalk: 4.1.2 tslib: 2.8.1 - '@docusaurus/mdx-loader@3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + '@docusaurus/mdx-loader@3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: - '@docusaurus/logger': 3.6.0 - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/utils-validation': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/logger': 3.6.1 + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) '@mdx-js/mdx': 3.1.0(acorn@8.14.0) '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 @@ -8276,7 +8640,6 @@ snapshots: vfile: 6.0.3 webpack: 5.96.1 transitivePeerDependencies: - - '@docusaurus/types' - '@swc/core' - acorn - esbuild @@ -8285,9 +8648,9 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/module-type-aliases@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/module-type-aliases@3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 '@types/react': 18.3.12 '@types/react-router-config': 5.0.11 @@ -8304,17 +8667,17 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.6.0(@docusaurus/plugin-content-docs@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': - dependencies: - '@docusaurus/core': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/logger': 3.6.0 - '@docusaurus/mdx-loader': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/plugin-content-docs': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/theme-common': 3.6.0(@docusaurus/plugin-content-docs@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/utils-common': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/plugin-content-blog@3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + dependencies: + '@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/logger': 3.6.1 + '@docusaurus/mdx-loader': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/theme-common': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils-common': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) cheerio: 1.0.0-rc.12 feed: 4.2.2 fs-extra: 11.2.0 @@ -8348,17 +8711,17 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-content-docs@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': - dependencies: - '@docusaurus/core': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/logger': 3.6.0 - '@docusaurus/mdx-loader': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/module-type-aliases': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-common': 3.6.0(@docusaurus/plugin-content-docs@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/utils-common': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + dependencies: + '@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/logger': 3.6.1 + '@docusaurus/mdx-loader': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/module-type-aliases': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-common': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils-common': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 fs-extra: 11.2.0 @@ -8390,13 +8753,13 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-content-pages@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + '@docusaurus/plugin-content-pages@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: - '@docusaurus/core': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/mdx-loader': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/utils-validation': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/mdx-loader': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) fs-extra: 11.2.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -8423,11 +8786,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-debug@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + '@docusaurus/plugin-debug@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: - '@docusaurus/core': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) fs-extra: 11.2.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -8454,11 +8817,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-analytics@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + '@docusaurus/plugin-google-analytics@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: - '@docusaurus/core': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 @@ -8483,11 +8846,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-gtag@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + '@docusaurus/plugin-google-gtag@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: - '@docusaurus/core': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) '@types/gtag.js': 0.0.12 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -8513,11 +8876,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + '@docusaurus/plugin-google-tag-manager@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: - '@docusaurus/core': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 @@ -8542,14 +8905,14 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-sitemap@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + '@docusaurus/plugin-sitemap@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: - '@docusaurus/core': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/logger': 3.6.0 - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/utils-common': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/logger': 3.6.1 + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils-common': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) fs-extra: 11.2.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -8576,21 +8939,21 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/preset-classic@3.6.0(@algolia/client-search@5.12.0)(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(typescript@5.2.2)': - dependencies: - '@docusaurus/core': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/plugin-content-blog': 3.6.0(@docusaurus/plugin-content-docs@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/plugin-content-docs': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/plugin-content-pages': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/plugin-debug': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/plugin-google-analytics': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/plugin-google-gtag': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/plugin-google-tag-manager': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/plugin-sitemap': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/theme-classic': 3.6.0(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/theme-common': 3.6.0(@docusaurus/plugin-content-docs@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/theme-search-algolia': 3.6.0(@algolia/client-search@5.12.0)(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(typescript@5.2.2) - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/preset-classic@3.6.1(@algolia/client-search@5.13.0)(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(typescript@5.2.2)': + dependencies: + '@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/plugin-content-blog': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/plugin-content-pages': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/plugin-debug': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/plugin-google-analytics': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/plugin-google-gtag': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/plugin-google-tag-manager': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/plugin-sitemap': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/theme-classic': 3.6.1(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/theme-common': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/theme-search-algolia': 3.6.1(@algolia/client-search@5.13.0)(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(typescript@5.2.2) + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: @@ -8622,21 +8985,21 @@ snapshots: '@types/react': 18.3.12 react: 18.3.1 - '@docusaurus/theme-classic@3.6.0(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': - dependencies: - '@docusaurus/core': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/logger': 3.6.0 - '@docusaurus/mdx-loader': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/module-type-aliases': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-blog': 3.6.0(@docusaurus/plugin-content-docs@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/plugin-content-docs': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/plugin-content-pages': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/theme-common': 3.6.0(@docusaurus/plugin-content-docs@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/theme-translations': 3.6.0 - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/utils-common': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docusaurus/theme-classic@3.6.1(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + dependencies: + '@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/logger': 3.6.1 + '@docusaurus/mdx-loader': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/module-type-aliases': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-blog': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/plugin-content-pages': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/theme-common': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/theme-translations': 3.6.1 + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils-common': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) '@mdx-js/react': 3.1.0(@types/react@18.3.12)(react@18.3.1) clsx: 2.1.1 copy-text-to-clipboard: 3.2.0 @@ -8673,13 +9036,13 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-common@3.6.0(@docusaurus/plugin-content-docs@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': + '@docusaurus/theme-common@3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: - '@docusaurus/mdx-loader': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/module-type-aliases': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/utils-common': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@docusaurus/mdx-loader': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/module-type-aliases': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils-common': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 '@types/react': 18.3.12 '@types/react-router-config': 5.0.11 @@ -8691,7 +9054,6 @@ snapshots: tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: - - '@docusaurus/types' - '@swc/core' - acorn - esbuild @@ -8700,16 +9062,16 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/theme-search-algolia@3.6.0(@algolia/client-search@5.12.0)(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(typescript@5.2.2)': + '@docusaurus/theme-search-algolia@3.6.1(@algolia/client-search@5.13.0)(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(typescript@5.2.2)': dependencies: - '@docsearch/react': 3.6.3(@algolia/client-search@5.12.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2) - '@docusaurus/core': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/logger': 3.6.0 - '@docusaurus/plugin-content-docs': 3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/theme-common': 3.6.0(@docusaurus/plugin-content-docs@3.6.0(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) - '@docusaurus/theme-translations': 3.6.0 - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/utils-validation': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) + '@docsearch/react': 3.7.0(@algolia/client-search@5.13.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2) + '@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/logger': 3.6.1 + '@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/theme-common': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/theme-translations': 3.6.1 + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) algoliasearch: 4.24.0 algoliasearch-helper: 3.22.5(algoliasearch@4.24.0) clsx: 2.1.1 @@ -8723,7 +9085,6 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/faster' - - '@docusaurus/types' - '@mdx-js/react' - '@parcel/css' - '@rspack/core' @@ -8745,12 +9106,12 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-translations@3.6.0': + '@docusaurus/theme-translations@3.6.1': dependencies: fs-extra: 11.2.0 tslib: 2.8.1 - '@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/types@3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@mdx-js/mdx': 3.1.0(acorn@8.14.0) '@types/history': 4.7.11 @@ -8771,35 +9132,46 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-common@3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': + '@docusaurus/utils-common@3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tslib: 2.8.1 - optionalDependencies: - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli - '@docusaurus/utils-validation@3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2)': + '@docusaurus/utils-validation@3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: - '@docusaurus/logger': 3.6.0 - '@docusaurus/utils': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2) - '@docusaurus/utils-common': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@docusaurus/logger': 3.6.1 + '@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2) + '@docusaurus/utils-common': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fs-extra: 11.2.0 joi: 17.13.3 js-yaml: 4.1.0 lodash: 4.17.21 tslib: 2.8.1 transitivePeerDependencies: - - '@docusaurus/types' - '@swc/core' + - acorn - esbuild + - react + - react-dom - supports-color - typescript - uglify-js - webpack-cli - '@docusaurus/utils@3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.2.2)': + '@docusaurus/utils@3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': dependencies: - '@docusaurus/logger': 3.6.0 - '@docusaurus/utils-common': 3.6.0(@docusaurus/types@3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@docusaurus/logger': 3.6.1 + '@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@svgr/webpack': 8.1.0(typescript@5.2.2) escape-string-regexp: 4.0.0 file-loader: 6.2.0(webpack@5.96.1) @@ -8818,11 +9190,12 @@ snapshots: url-loader: 4.1.1(file-loader@6.2.0(webpack@5.96.1))(webpack@5.96.1) utility-types: 3.11.0 webpack: 5.96.1 - optionalDependencies: - '@docusaurus/types': 3.6.0(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - '@swc/core' + - acorn - esbuild + - react + - react-dom - supports-color - typescript - uglify-js @@ -8941,6 +9314,111 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@inquirer/checkbox@1.5.2': + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + figures: 3.2.0 + + '@inquirer/confirm@2.0.17': + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + chalk: 4.1.2 + + '@inquirer/core@5.1.2': + dependencies: + '@inquirer/type': 1.5.5 + '@types/mute-stream': 0.0.4 + '@types/node': 20.17.6 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-spinners: 2.9.2 + cli-width: 4.1.0 + figures: 3.2.0 + mute-stream: 1.0.0 + run-async: 3.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + '@inquirer/core@6.0.0': + dependencies: + '@inquirer/type': 1.5.5 + '@types/mute-stream': 0.0.4 + '@types/node': 20.17.6 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-spinners: 2.9.2 + cli-width: 4.1.0 + figures: 3.2.0 + mute-stream: 1.0.0 + run-async: 3.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + '@inquirer/editor@1.2.15': + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + chalk: 4.1.2 + external-editor: 3.1.0 + + '@inquirer/expand@1.1.16': + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + chalk: 4.1.2 + figures: 3.2.0 + + '@inquirer/input@1.2.16': + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + chalk: 4.1.2 + + '@inquirer/password@1.1.16': + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + + '@inquirer/prompts@3.3.2': + dependencies: + '@inquirer/checkbox': 1.5.2 + '@inquirer/confirm': 2.0.17 + '@inquirer/core': 6.0.0 + '@inquirer/editor': 1.2.15 + '@inquirer/expand': 1.1.16 + '@inquirer/input': 1.2.16 + '@inquirer/password': 1.1.16 + '@inquirer/rawlist': 1.2.16 + '@inquirer/select': 1.3.3 + + '@inquirer/rawlist@1.2.16': + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + chalk: 4.1.2 + + '@inquirer/select@1.3.3': + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + figures: 3.2.0 + + '@inquirer/type@1.5.5': + dependencies: + mute-stream: 1.0.0 + '@ipld/car@5.3.3': dependencies: '@ipld/dag-cbor': 9.2.2 @@ -9299,6 +9777,8 @@ snapshots: dependencies: '@types/node': 20.17.6 + '@types/configstore@6.0.2': {} + '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 5.0.1 @@ -9331,14 +9811,14 @@ snapshots: '@types/express-serve-static-core@4.19.6': dependencies: '@types/node': 20.17.6 - '@types/qs': 6.9.16 + '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 '@types/express-serve-static-core@5.0.1': dependencies: '@types/node': 20.17.6 - '@types/qs': 6.9.16 + '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -9346,7 +9826,7 @@ snapshots: dependencies: '@types/body-parser': 1.19.5 '@types/express-serve-static-core': 4.19.6 - '@types/qs': 6.9.16 + '@types/qs': 6.9.17 '@types/serve-static': 1.15.7 '@types/gtag.js@0.0.12': {} @@ -9398,6 +9878,10 @@ snapshots: '@types/ms@0.7.34': {} + '@types/mute-stream@0.0.4': + dependencies: + '@types/node': 20.17.6 + '@types/node-forge@1.3.11': dependencies: '@types/node': 20.17.6 @@ -9414,7 +9898,7 @@ snapshots: '@types/prop-types@15.7.13': {} - '@types/qs@6.9.16': {} + '@types/qs@6.9.17': {} '@types/range-parser@1.2.7': {} @@ -9483,10 +9967,17 @@ snapshots: '@types/unist@3.0.3': {} + '@types/update-notifier@6.0.8': + dependencies: + '@types/configstore': 6.0.2 + boxen: 7.1.1 + '@types/varint@6.0.3': dependencies: '@types/node': 20.17.6 + '@types/wrap-ansi@3.0.0': {} + '@types/ws@8.5.13': dependencies: '@types/node': 20.17.6 @@ -9704,80 +10195,84 @@ snapshots: multiformats: 13.3.1 sync-multihash-sha2: 1.0.0 - '@webassemblyjs/ast@1.12.1': + '@web3-storage/sigv4@1.0.2': + dependencies: + '@noble/hashes': 1.5.0 + + '@webassemblyjs/ast@1.14.1': dependencies: - '@webassemblyjs/helper-numbers': 1.11.6 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/floating-point-hex-parser@1.11.6': {} + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} - '@webassemblyjs/helper-api-error@1.11.6': {} + '@webassemblyjs/helper-api-error@1.13.2': {} - '@webassemblyjs/helper-buffer@1.12.1': {} + '@webassemblyjs/helper-buffer@1.14.1': {} - '@webassemblyjs/helper-numbers@1.11.6': + '@webassemblyjs/helper-numbers@1.13.2': dependencies: - '@webassemblyjs/floating-point-hex-parser': 1.11.6 - '@webassemblyjs/helper-api-error': 1.11.6 + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 '@xtuc/long': 4.2.2 - '@webassemblyjs/helper-wasm-bytecode@1.11.6': {} + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} - '@webassemblyjs/helper-wasm-section@1.12.1': + '@webassemblyjs/helper-wasm-section@1.14.1': 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 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/ieee754@1.11.6': + '@webassemblyjs/ieee754@1.13.2': dependencies: '@xtuc/ieee754': 1.2.0 - '@webassemblyjs/leb128@1.11.6': + '@webassemblyjs/leb128@1.13.2': dependencies: '@xtuc/long': 4.2.2 - '@webassemblyjs/utf8@1.11.6': {} + '@webassemblyjs/utf8@1.13.2': {} - '@webassemblyjs/wasm-edit@1.12.1': + '@webassemblyjs/wasm-edit@1.14.1': 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 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 - '@webassemblyjs/wasm-gen@1.12.1': + '@webassemblyjs/wasm-gen@1.14.1': dependencies: - '@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 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 - '@webassemblyjs/wasm-opt@1.12.1': + '@webassemblyjs/wasm-opt@1.14.1': dependencies: - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/helper-buffer': 1.12.1 - '@webassemblyjs/wasm-gen': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 - '@webassemblyjs/wasm-parser@1.12.1': + '@webassemblyjs/wasm-parser@1.14.1': 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 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 - '@webassemblyjs/wast-printer@1.12.1': + '@webassemblyjs/wast-printer@1.14.1': dependencies: - '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 '@xtuc/ieee754@1.2.0': {} @@ -9865,21 +10360,21 @@ snapshots: '@algolia/requester-node-http': 4.24.0 '@algolia/transporter': 4.24.0 - algoliasearch@5.12.0: - dependencies: - '@algolia/client-abtesting': 5.12.0 - '@algolia/client-analytics': 5.12.0 - '@algolia/client-common': 5.12.0 - '@algolia/client-insights': 5.12.0 - '@algolia/client-personalization': 5.12.0 - '@algolia/client-query-suggestions': 5.12.0 - '@algolia/client-search': 5.12.0 - '@algolia/ingestion': 1.12.0 - '@algolia/monitoring': 1.12.0 - '@algolia/recommend': 5.12.0 - '@algolia/requester-browser-xhr': 5.12.0 - '@algolia/requester-fetch': 5.12.0 - '@algolia/requester-node-http': 5.12.0 + algoliasearch@5.13.0: + dependencies: + '@algolia/client-abtesting': 5.13.0 + '@algolia/client-analytics': 5.13.0 + '@algolia/client-common': 5.13.0 + '@algolia/client-insights': 5.13.0 + '@algolia/client-personalization': 5.13.0 + '@algolia/client-query-suggestions': 5.13.0 + '@algolia/client-search': 5.13.0 + '@algolia/ingestion': 1.13.0 + '@algolia/monitoring': 1.13.0 + '@algolia/recommend': 5.13.0 + '@algolia/requester-browser-xhr': 5.13.0 + '@algolia/requester-fetch': 5.13.0 + '@algolia/requester-node-http': 5.13.0 ansi-align@3.0.1: dependencies: @@ -9895,18 +10390,24 @@ snapshots: dependencies: type-fest: 1.4.0 + ansi-escapes@6.2.1: {} + ansi-escapes@7.0.0: dependencies: environment: 1.1.0 ansi-html-community@0.0.8: {} + ansi-regex@1.1.1: {} + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} ansi-sequence-parser@1.1.1: {} + ansi-styles@2.2.1: {} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 @@ -9926,6 +10427,8 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + archy@0.0.2: {} + are-docs-informative@0.0.2: {} arg@5.0.2: {} @@ -9980,7 +10483,7 @@ snapshots: autoprefixer@10.4.20(postcss@8.4.47): dependencies: browserslist: 4.24.2 - caniuse-lite: 1.0.30001677 + caniuse-lite: 1.0.30001679 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -10034,6 +10537,8 @@ snapshots: batch@0.6.1: {} + big-integer@1.6.52: {} + big.js@5.2.2: {} bigint-mod-arith@3.3.1: {} @@ -10103,6 +10608,21 @@ snapshots: widest-line: 4.0.1 wrap-ansi: 8.1.0 + boxen@8.0.1: + dependencies: + ansi-align: 3.0.1 + camelcase: 8.0.0 + chalk: 5.3.0 + cli-boxes: 3.0.0 + string-width: 7.2.0 + type-fest: 4.26.1 + widest-line: 5.0.0 + wrap-ansi: 9.0.0 + + bplist-parser@0.2.0: + dependencies: + big-integer: 1.6.52 + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -10122,8 +10642,8 @@ snapshots: browserslist@4.24.2: dependencies: - caniuse-lite: 1.0.30001677 - electron-to-chromium: 1.5.51 + caniuse-lite: 1.0.30001679 + electron-to-chromium: 1.5.55 node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) @@ -10136,6 +10656,10 @@ snapshots: builtin-modules@3.3.0: {} + bundle-name@3.0.0: + dependencies: + run-applescript: 5.0.0 + bytes@3.0.0: {} bytes@3.1.2: {} @@ -10222,11 +10746,11 @@ snapshots: caniuse-api@3.0.0: dependencies: browserslist: 4.24.2 - caniuse-lite: 1.0.30001677 + caniuse-lite: 1.0.30001679 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001677: {} + caniuse-lite@1.0.30001679: {} carstream@1.1.1: dependencies: @@ -10244,6 +10768,14 @@ snapshots: ccount@2.0.1: {} + chalk@1.0.0: + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 1.0.3 + strip-ansi: 2.0.1 + supports-color: 1.3.1 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -10267,6 +10799,8 @@ snapshots: character-reference-invalid@2.0.1: {} + chardet@0.7.0: {} + cheerio-select@2.1.0: dependencies: boolbase: 1.0.0 @@ -10336,6 +10870,8 @@ snapshots: slice-ansi: 5.0.0 string-width: 5.1.2 + cli-width@4.1.0: {} + cliui@7.0.4: dependencies: string-width: 4.2.3 @@ -10436,6 +10972,13 @@ snapshots: write-file-atomic: 3.0.3 xdg-basedir: 5.1.0 + configstore@7.0.0: + dependencies: + atomically: 2.0.3 + dot-prop: 9.0.0 + graceful-fs: 4.2.11 + xdg-basedir: 5.1.0 + connect-history-api-fallback@2.0.0: {} consola@3.2.3: {} @@ -10523,7 +11066,7 @@ snapshots: shebang-command: 1.2.0 which: 1.3.1 - cross-spawn@7.0.3: + cross-spawn@7.0.5: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -10533,6 +11076,10 @@ snapshots: dependencies: type-fest: 1.4.0 + crypto-random-string@5.0.0: + dependencies: + type-fest: 2.19.0 + css-declaration-sorter@7.2.0(postcss@8.4.47): dependencies: postcss: 8.4.47 @@ -10707,6 +11254,18 @@ snapshots: deepmerge@4.3.1: {} + default-browser-id@3.0.0: + dependencies: + bplist-parser: 0.2.0 + untildify: 4.0.0 + + default-browser@4.0.0: + dependencies: + bundle-name: 3.0.0 + default-browser-id: 3.0.0 + execa: 7.2.0 + titleize: 3.0.0 + default-gateway@6.0.3: dependencies: execa: 5.1.1 @@ -10721,6 +11280,8 @@ snapshots: define-lazy-prop@2.0.0: {} + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -10868,6 +11429,10 @@ snapshots: dependencies: type-fest: 2.19.0 + dot-prop@9.0.0: + dependencies: + type-fest: 4.26.1 + duplexer@0.1.2: {} eastasianwidth@0.2.0: {} @@ -10878,7 +11443,7 @@ snapshots: dependencies: encoding: 0.1.13 - electron-to-chromium@1.5.51: {} + electron-to-chromium@1.5.55: {} emoji-regex@10.4.0: {} @@ -11092,7 +11657,7 @@ snapshots: '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 debug: 4.3.7(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 @@ -11205,7 +11770,7 @@ snapshots: execa@5.1.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -11217,7 +11782,7 @@ snapshots: execa@7.2.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 get-stream: 6.0.1 human-signals: 4.3.1 is-stream: 3.0.0 @@ -11229,7 +11794,7 @@ snapshots: execa@8.0.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 get-stream: 8.0.1 human-signals: 5.0.0 is-stream: 3.0.0 @@ -11287,6 +11852,12 @@ snapshots: extend@3.0.2: {} + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} @@ -11337,6 +11908,10 @@ snapshots: schema-utils: 3.3.0 webpack: 5.96.1 + files-from-path@1.0.4: + dependencies: + graceful-fs: 4.2.11 + filesize@8.0.7: {} fill-range@7.1.1: @@ -11399,12 +11974,12 @@ snapshots: foreground-child@2.0.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 signal-exit: 3.0.7 foreground-child@3.3.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 signal-exit: 4.1.0 fork-ts-checker-webpack-plugin@6.5.3(eslint@8.57.1)(typescript@5.2.2)(webpack@5.96.1): @@ -11474,6 +12049,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.3.0: {} + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -11486,6 +12063,8 @@ snapshots: get-own-enumerable-property-symbols@3.0.2: {} + get-stdin@4.0.1: {} + get-stream@6.0.1: {} get-stream@8.0.1: {} @@ -11534,6 +12113,10 @@ snapshots: minimatch: 5.1.6 once: 1.4.0 + global-directory@4.0.1: + dependencies: + ini: 4.1.1 + global-dirs@3.0.1: dependencies: ini: 2.0.0 @@ -11658,6 +12241,11 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 + has-ansi@1.0.3: + dependencies: + ansi-regex: 1.1.1 + get-stdin: 4.0.1 + has-bigints@1.0.2: {} has-flag@3.0.0: {} @@ -11962,6 +12550,8 @@ snapshots: ini@2.0.0: {} + ini@4.1.1: {} + inline-style-parser@0.1.1: {} inline-style-parser@0.2.4: {} @@ -12091,6 +12681,8 @@ snapshots: is-docker@2.2.1: {} + is-docker@3.0.0: {} + is-electron@2.2.2: {} is-extendable@0.1.1: {} @@ -12111,11 +12703,22 @@ snapshots: is-hexadecimal@2.0.1: {} + is-in-ci@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-installed-globally@0.4.0: dependencies: global-dirs: 3.0.1 is-path-inside: 3.0.3 + is-installed-globally@1.0.0: + dependencies: + global-directory: 4.0.1 + is-path-inside: 4.0.0 + is-interactive@2.0.0: {} is-nan@1.3.2: @@ -12141,6 +12744,8 @@ snapshots: is-path-inside@3.0.3: {} + is-path-inside@4.0.0: {} + is-plain-obj@2.1.0: {} is-plain-obj@3.0.0: {} @@ -12364,10 +12969,16 @@ snapshots: kleur@4.1.5: {} + ky@1.7.2: {} + latest-version@7.0.0: dependencies: package-json: 8.1.1 + latest-version@9.0.0: + dependencies: + package-json: 10.0.1 + launch-editor@2.9.1: dependencies: picocolors: 1.1.1 @@ -13158,6 +13769,8 @@ snapshots: murmurhash3js-revisited@3.0.0: {} + mute-stream@1.0.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -13303,6 +13916,13 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 + open@9.1.0: + dependencies: + default-browser: 4.0.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + is-wsl: 2.2.0 + opener@1.5.2: {} optionator@0.9.4: @@ -13326,6 +13946,8 @@ snapshots: string-width: 6.1.0 strip-ansi: 7.1.0 + os-tmpdir@1.0.2: {} + p-cancelable@3.0.0: {} p-defer@3.0.0: {} @@ -13404,6 +14026,13 @@ snapshots: package-json-from-dist@1.0.1: {} + package-json@10.0.1: + dependencies: + ky: 1.7.2 + registry-auth-token: 5.0.2 + registry-url: 6.0.1 + semver: 7.6.3 + package-json@8.1.1: dependencies: got: 12.6.1 @@ -13804,6 +14433,8 @@ snapshots: prettier@2.8.3: {} + prettier@3.3.3: {} + pretty-error@4.0.0: dependencies: lodash: 4.17.21 @@ -13811,6 +14442,11 @@ snapshots: pretty-time@1.1.0: {} + pretty-tree@1.0.0: + dependencies: + archy: 0.0.2 + chalk: 1.0.0 + prism-react-renderer@2.4.0(react@18.3.1): dependencies: '@types/prismjs': 1.26.5 @@ -13906,7 +14542,7 @@ snapshots: address: 1.2.2 browserslist: 4.24.2 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.5 detect-port-alt: 1.1.6 escape-string-regexp: 4.0.0 filesize: 8.0.7 @@ -14269,6 +14905,12 @@ snapshots: postcss: 8.4.47 strip-json-comments: 3.1.1 + run-applescript@5.0.0: + dependencies: + execa: 5.1.1 + + run-async@3.0.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -14277,6 +14919,8 @@ snapshots: dependencies: tslib: 2.8.1 + s-ago@2.2.0: {} + sade@1.8.1: dependencies: mri: 1.2.0 @@ -14583,7 +15227,7 @@ snapshots: statuses@2.0.1: {} - std-env@3.7.0: {} + std-env@3.8.0: {} stdin-discarder@0.1.0: dependencies: @@ -14618,6 +15262,12 @@ snapshots: emoji-regex: 10.4.0 strip-ansi: 7.1.0 + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + string.prototype.padend@3.1.6: dependencies: call-bind: 1.0.7 @@ -14663,6 +15313,10 @@ snapshots: is-obj: 1.0.1 is-regexp: 1.0.0 + strip-ansi@2.0.1: + dependencies: + ansi-regex: 1.1.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -14699,6 +15353,8 @@ snapshots: postcss: 8.4.47 postcss-selector-parser: 6.1.2 + supports-color@1.3.1: {} + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -14791,6 +15447,12 @@ snapshots: tiny-warning@1.0.3: {} + titleize@3.0.0: {} + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -14983,6 +15645,8 @@ snapshots: unpipe@1.0.0: {} + untildify@4.0.0: {} + update-browserslist-db@1.1.1(browserslist@4.24.2): dependencies: browserslist: 4.24.2 @@ -15006,6 +15670,19 @@ snapshots: semver-diff: 4.0.0 xdg-basedir: 5.1.0 + update-notifier@7.3.1: + dependencies: + boxen: 8.0.1 + chalk: 5.3.0 + configstore: 7.0.0 + is-in-ci: 1.0.0 + is-installed-globally: 1.0.0 + is-npm: 6.0.0 + latest-version: 9.0.0 + pupa: 3.1.0 + semver: 7.6.3 + xdg-basedir: 5.1.0 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -15193,9 +15870,9 @@ snapshots: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/wasm-edit': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.14.0 browserslist: 4.24.2 chrome-trace-event: 1.0.4 @@ -15227,7 +15904,7 @@ snapshots: figures: 3.2.0 markdown-table: 2.0.0 pretty-time: 1.1.0 - std-env: 3.7.0 + std-env: 3.8.0 webpack: 5.96.1 wrap-ansi: 7.0.0 @@ -15274,6 +15951,10 @@ snapshots: dependencies: string-width: 5.1.2 + widest-line@5.0.0: + dependencies: + string-width: 7.2.0 + wildcard@2.0.1: {} word-wrap@1.2.5: {} @@ -15282,6 +15963,12 @@ snapshots: workerpool@6.5.1: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -15294,6 +15981,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + wrappy@1.0.2: {} write-file-atomic@3.0.3: From df594071b6d6c376cea633c3c60c06f576822e82 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 Nov 2024 07:47:59 +0700 Subject: [PATCH 2/8] chore: format --- packages/cli/bin.js | 17 +++- packages/cli/bridge.js | 10 +- packages/cli/can.js | 11 ++- packages/cli/index.js | 67 +++++++------ packages/cli/lib.js | 52 +++++++---- packages/cli/space.js | 4 +- packages/cli/test/bin.spec.js | 93 ++++++++++++------- packages/cli/test/helpers/context.js | 3 +- packages/cli/test/helpers/env.js | 2 +- packages/cli/test/helpers/process.js | 1 - packages/cli/test/lib.spec.js | 5 +- packages/upload-api/src/types.ts | 6 +- .../upload-api/test/external-service/index.js | 8 +- .../test/external-service/storage-node.js | 12 ++- packages/upload-api/test/handlers/blob.js | 2 +- packages/w3up-client/src/service.js | 12 ++- 16 files changed, 202 insertions(+), 103 deletions(-) diff --git a/packages/cli/bin.js b/packages/cli/bin.js index b40254216..2b5769a35 100755 --- a/packages/cli/bin.js +++ b/packages/cli/bin.js @@ -76,7 +76,11 @@ cli .describe('Store a file(s) to the service and register an upload.') .option('-H, --hidden', 'Include paths that start with ".".', false) .option('-c, --car', 'File is a CAR file.', false) - .option('--wrap', "Wrap single input file in a directory. Has no effect on directory or CAR uploads. Pass --no-wrap to disable.", true) + .option( + '--wrap', + 'Wrap single input file in a directory. Has no effect on directory or CAR uploads. Pass --no-wrap to disable.', + true + ) .option('--json', 'Format as newline delimited JSON', false) .option('--verbose', 'Output more details.', false) .option( @@ -181,7 +185,7 @@ cli ) .action(Coupon.issue) - cli +cli .command('bridge generate-tokens ') .option('-c, --can', 'One or more abilities to delegate.') .option( @@ -195,7 +199,6 @@ cli ) .action(Bridge.generateTokens) - cli .command('delegation create ') .describe( @@ -323,13 +326,17 @@ cli cli .command('key create') - .describe('Generate and print a new ed25519 key pair. Does not change your current signing key.') + .describe( + 'Generate and print a new ed25519 key pair. Does not change your current signing key.' + ) .option('--json', 'output as json') .action(createKey) cli .command('reset') - .describe('Remove all proofs/delegations from the store but retain the agent DID.') + .describe( + 'Remove all proofs/delegations from the store but retain the agent DID.' + ) .action(reset) // show help text if no command provided diff --git a/packages/cli/bridge.js b/packages/cli/bridge.js index ad6d0d7cb..f3df16c32 100644 --- a/packages/cli/bridge.js +++ b/packages/cli/bridge.js @@ -52,10 +52,12 @@ export const generateTokens = async ( const authorization = base64url.encode(bytes) if (json) { - console.log(JSON.stringify({ - "X-Auth-Secret": xAuthSecret, - "Authorization": authorization - })) + console.log( + JSON.stringify({ + 'X-Auth-Secret': xAuthSecret, + Authorization: authorization, + }) + ) } else { console.log(` X-Auth-Secret header: ${xAuthSecret} diff --git a/packages/cli/can.js b/packages/cli/can.js index 6e6a42ab5..569848642 100644 --- a/packages/cli/can.js +++ b/packages/cli/can.js @@ -37,10 +37,13 @@ export async function blobAdd(blobPath) { spinner.start('Storing') const { digest } = await client.capability.blob.add(blob, { - receiptsEndpoint: client._receiptsEndpoint.toString() + receiptsEndpoint: client._receiptsEndpoint.toString(), }) const cid = Link.create(raw.code, digest) - spinner.stopAndPersist({ symbol: '⁂', text: `Stored ${base58btc.encode(digest.bytes)} (${cid})` }) + spinner.stopAndPersist({ + symbol: '⁂', + text: `Stored ${base58btc.encode(digest.bytes)} (${cid})`, + }) } /** @@ -211,7 +214,9 @@ export async function filecoinInfo(pieceCid, opts) { const client = await getClient() const info = await client.capability.filecoin.info(pieceInfo.link) if (info.out.error) { - spinner.fail(`Error: failed to get filecoin info: ${info.out.error.message}`) + spinner.fail( + `Error: failed to get filecoin info: ${info.out.error.message}` + ) process.exit(1) } spinner.stop() diff --git a/packages/cli/index.js b/packages/cli/index.js index 43ed81623..35b4ed585 100644 --- a/packages/cli/index.js +++ b/packages/cli/index.js @@ -135,12 +135,14 @@ export async function upload(firstPath, opts) { } } else { spinner = ora({ text: 'Reading from stdin', isSilent: opts?.json }).start() - files = [{ - name: 'stdin', - stream: () => - /** @type {ReadableStream} */ - (Readable.toWeb(process.stdin)) - }] + files = [ + { + name: 'stdin', + stream: () => + /** @type {ReadableStream} */ + (Readable.toWeb(process.stdin)), + }, + ] totalSize = -1 opts = opts ?? { _: [] } opts.wrap = false @@ -155,26 +157,27 @@ export async function upload(firstPath, opts) { : client.uploadDirectory.bind(client, files) let totalSent = 0 - const getStoringMessage = () => totalSize == -1 - // for unknown size, display the amount sent so far - ? `Storing ${filesizeMB(totalSent)}` - // for known size, display percentage of total size that has been sent - : `Storing ${Math.min(Math.round((totalSent / totalSize) * 100), 100)}%` + const getStoringMessage = () => + totalSize == -1 + ? // for unknown size, display the amount sent so far + `Storing ${filesizeMB(totalSent)}` + : // for known size, display percentage of total size that has been sent + `Storing ${Math.min(Math.round((totalSent / totalSize) * 100), 100)}%` const root = await uploadFn({ pieceHasher: { code: PieceHasher.code, name: 'fr32-sha2-256-trunc254-padded-binary-tree-multihash', - async digest (input) { + async digest(input) { const hasher = PieceHasher.create() hasher.write(input) - + const bytes = new Uint8Array(hasher.multihashByteLength()) hasher.digestInto(bytes, 0, true) hasher.free() return Digest.decode(bytes) - } + }, }, onShardStored: ({ cid, size, piece }) => { totalSent += size @@ -196,7 +199,7 @@ export async function upload(firstPath, opts) { concurrentRequests: opts?.['concurrent-requests'] && parseInt(String(opts?.['concurrent-requests'])), - receiptsEndpoint: client._receiptsEndpoint.toString() + receiptsEndpoint: client._receiptsEndpoint.toString(), }) spinner.stopAndPersist({ symbol: '⁂', @@ -278,7 +281,9 @@ export async function addSpace(proofPathOrCid) { cid = CID.parse(proofPathOrCid, base64) } catch (/** @type {any} */ err) { if (err?.message?.includes('Unexpected end of data')) { - console.error(`Error: failed to read proof. The string has been truncated.`) + console.error( + `Error: failed to read proof. The string has been truncated.` + ) process.exit(1) } /* otherwise, try as path */ @@ -287,7 +292,9 @@ export async function addSpace(proofPathOrCid) { let delegation if (cid) { if (cid.multihash.code !== identity.code) { - console.error(`Error: failed to read proof. Must be identity CID. Fetching of remote proof CARs not supported by this command yet`) + console.error( + `Error: failed to read proof. Must be identity CID. Fetching of remote proof CARs not supported by this command yet` + ) process.exit(1) } delegation = await readProofFromBytes(cid.multihash.digest) @@ -386,7 +393,9 @@ export async function createDelegation(audienceDID, opts) { const client = await getClient() if (client.currentSpace() == null) { - throw new Error('no current space, use `storacha space register` to create one.') + throw new Error( + 'no current space, use `storacha space register` to create one.' + ) } const audience = DID.parse(audienceDID) @@ -603,10 +612,7 @@ export async function usageReport(opts) { } const failures = [] let total = 0 - for await (const result of getSpaceUsageReports( - client, - period - )) { + for await (const result of getSpaceUsageReports(client, period)) { if ('error' in result) { failures.push(result) } else { @@ -637,9 +643,15 @@ export async function usageReport(opts) { console.log(` Total: ${opts?.human ? filesize(total) : total}`) if (failures.length) { console.warn(``) - console.warn(` WARNING: there were ${failures.length} errors getting usage reports for some spaces.`) - console.warn(` This may happen if your agent does not have usage/report authorization for a space.`) - console.warn(` These spaces were not included in the usage report total:`) + console.warn( + ` WARNING: there were ${failures.length} errors getting usage reports for some spaces.` + ) + console.warn( + ` This may happen if your agent does not have usage/report authorization for a space.` + ) + console.warn( + ` These spaces were not included in the usage report total:` + ) for (const fail of failures) { console.warn(` * space: ${fail.space}`) // @ts-expect-error error is unknown @@ -697,7 +709,10 @@ export const reset = async () => { if (exportData) { let data = AgentData.fromExport(exportData) // do not reset the principal - data = await AgentData.create({ principal: data.principal, meta: data.meta }) + data = await AgentData.create({ + principal: data.principal, + meta: data.meta, + }) await store.save(data.export()) } console.log('⁂ Agent reset.') diff --git a/packages/cli/lib.js b/packages/cli/lib.js index 262cca01d..8215b3363 100644 --- a/packages/cli/lib.js +++ b/packages/cli/lib.js @@ -61,7 +61,9 @@ export function filesizeMB(bytes) { /** Get a configured w3up store used by the CLI. */ export function getStore() { - return new StoreConf({ profile: process.env.STORACHA_STORE_NAME ?? 'storacha-cli' }) + return new StoreConf({ + profile: process.env.STORACHA_STORE_NAME ?? 'storacha-cli', + }) } /** @@ -81,7 +83,7 @@ export function getClient() { if (receiptsEndpointString) { receiptsEndpoint = new URL(receiptsEndpointString) } - + let serviceConf if (uploadServiceDID && uploadServiceURL) { serviceConf = @@ -180,8 +182,8 @@ export function uploadListResponseToString(res, opts = {}) { leaf: shards?.map((s) => s.toString()), }, ], - })} - ) + }) + }) .join('\n') } else { return res.results.map(({ root }) => root.toString()).join('\n') @@ -197,9 +199,7 @@ export function uploadListResponseToString(res, opts = {}) { */ export function blobListResponseToString(res, opts = {}) { if (opts.json) { - return res.results - .map(({ blob }) => dagJSON.stringify({ blob })) - .join('\n') + return res.results.map(({ blob }) => dagJSON.stringify({ blob })).join('\n') } else { return res.results .map(({ blob }) => { @@ -212,7 +212,7 @@ export function blobListResponseToString(res, opts = {}) { } /** - * @param {FilecoinInfoSuccess} res + * @param {FilecoinInfoSuccess} res * @param {object} [opts] * @param {boolean} [opts.raw] * @param {boolean} [opts.json] @@ -220,12 +220,16 @@ export function blobListResponseToString(res, opts = {}) { export function filecoinInfoToString(res, opts = {}) { if (opts.json) { return res.deals - .map(deal => dagJSON.stringify(({ - aggregate: deal.aggregate.toString(), - provider: deal.provider, - dealId: deal.aux.dataSource.dealID, - inclusion: res.aggregates.find(a => a.aggregate.toString() === deal.aggregate.toString())?.inclusion - }))) + .map((deal) => + dagJSON.stringify({ + aggregate: deal.aggregate.toString(), + provider: deal.provider, + dealId: deal.aux.dataSource.dealID, + inclusion: res.aggregates.find( + (a) => a.aggregate.toString() === deal.aggregate.toString() + )?.inclusion, + }) + ) .join('\n') } else { if (!res.deals.length) { @@ -237,11 +241,15 @@ export function filecoinInfoToString(res, opts = {}) { // not showing inclusion proof as it would just be bytes return ` Piece CID: ${res.piece.toString()} - Deals: ${res.deals.map((deal) => ` + Deals: ${res.deals + .map( + (deal) => ` Aggregate: ${deal.aggregate.toString()} Provider: ${deal.provider} Deal ID: ${deal.aux.dataSource.dealID} - `).join('')} + ` + ) + .join('')} ` } } @@ -289,10 +297,14 @@ export const startOfLastMonth = (now) => { } /** @param {ReadableStream} source */ -export const streamToBlob = async source => { +export const streamToBlob = async (source) => { const chunks = /** @type {Uint8Array[]} */ ([]) - await source.pipeTo(new WritableStream({ - write: chunk => { chunks.push(chunk) } - })) + await source.pipeTo( + new WritableStream({ + write: (chunk) => { + chunks.push(chunk) + }, + }) + ) return new Blob(chunks) } diff --git a/packages/cli/space.js b/packages/cli/space.js index c96540385..93afd8cd5 100644 --- a/packages/cli/space.js +++ b/packages/cli/space.js @@ -323,8 +323,8 @@ const chooseName = async (name, spaces) => { name === '' ? 'What would you like to call this space?' : space - ? `Name "${space.name}" is already taken, please choose a different one` - : null + ? `Name "${space.name}" is already taken, please choose a different one` + : null if (message == null) { return name diff --git a/packages/cli/test/bin.spec.js b/packages/cli/test/bin.spec.js index 7b451e086..f1cd1b73a 100644 --- a/packages/cli/test/bin.spec.js +++ b/packages/cli/test/bin.spec.js @@ -101,7 +101,10 @@ export const testAccount = { export const testSpace = { 'storacha space create': test(async (assert, context) => { - const command = storacha.args(['space', 'create']).env(context.env.alice).fork() + const command = storacha + .args(['space', 'create']) + .env(context.env.alice) + .fork() const line = await command.output.take(1).text() @@ -154,18 +157,20 @@ export const testSpace = { await create.terminate().join().catch() }), - 'storacha space create my-space --no-recovery': test(async (assert, context) => { - const create = storacha - .args(['space', 'create', 'home', '--no-recovery']) - .env(context.env.alice) - .fork() + 'storacha space create my-space --no-recovery': test( + async (assert, context) => { + const create = storacha + .args(['space', 'create', 'home', '--no-recovery']) + .env(context.env.alice) + .fork() - const line = await create.output.lines().take().text() + const line = await create.output.lines().take().text() - assert.match(line, /billing account/, 'no paper recovery') + assert.match(line, /billing account/, 'no paper recovery') - await create.terminate().join().catch() - }), + await create.terminate().join().catch() + } + ), 'storacha space create my-space --no-recovery (logged-in)': test( async (assert, context) => { @@ -294,8 +299,8 @@ export const testSpace = { ) }), - 'storacha space create home --no-recovery (blocks until plan is selected)': test( - async (assert, context) => { + 'storacha space create home --no-recovery (blocks until plan is selected)': + test(async (assert, context) => { const email = 'alice@web.mail' await login(context, { email }) @@ -312,8 +317,7 @@ export const testSpace = { assert.match(output, /billing account is set/i) assert.match(error, /wait.*plan.*select/i) - } - ), + }), 'storacha space add': test(async (assert, context) => { const { env } = context @@ -346,7 +350,10 @@ export const testSpace = { const listNone = await storacha.args(['space', 'ls']).env(env.bob).join() assert.ok(!listNone.output.includes(spaceDID)) - const add = await storacha.args(['space', 'add', proofPath]).env(env.bob).join() + const add = await storacha + .args(['space', 'add', proofPath]) + .env(env.bob) + .join() assert.equal(add.output.trim(), spaceDID) const listSome = await storacha.args(['space', 'ls']).env(env.bob).join() @@ -366,7 +373,7 @@ export const testSpace = { '-c', 'store/*', 'upload/*', - '--base64' + '--base64', ]) .env(env.alice) .join() @@ -374,7 +381,10 @@ export const testSpace = { const listNone = await storacha.args(['space', 'ls']).env(env.bob).join() assert.ok(!listNone.output.includes(spaceDID)) - const add = await storacha.args(['space', 'add', res.output]).env(env.bob).join() + const add = await storacha + .args(['space', 'add', res.output]) + .env(env.bob) + .join() assert.equal(add.output.trim(), spaceDID) const listSome = await storacha.args(['space', 'ls']).env(env.bob).join() @@ -468,7 +478,10 @@ export const testSpace = { 'old space is still listed' ) - await storacha.args(['space', 'use', spaceDID]).env(context.env.alice).join() + await storacha + .args(['space', 'use', spaceDID]) + .env(context.env.alice) + .join() const listSetDefault = await storacha .args(['space', 'ls']) .env(context.env.alice) @@ -485,7 +498,10 @@ export const testSpace = { 'new space is not default' ) - await storacha.args(['space', 'use', spaceName]).env(context.env.alice).join() + await storacha + .args(['space', 'use', spaceName]) + .env(context.env.alice) + .join() const listNamedDefault = await storacha .args(['space', 'ls']) .env(context.env.alice) @@ -567,7 +583,7 @@ export const testSpace = { assert.deepEqual(JSON.parse(infoWithProviderJson.output), { did: spaceDID, providers: [providerDID], - name: 'home' + name: 'home', }) }), @@ -597,7 +613,10 @@ export const testSpace = { assert.match(provision.output, /Billing account is set/) - const info = await storacha.env(context.env.alice).args(['space', 'info']).join() + const info = await storacha + .env(context.env.alice) + .args(['space', 'info']) + .join() assert.match( info.output, @@ -747,7 +766,10 @@ export const testStorachaUp = { // wait a second for invocation to get a different expiry await new Promise((resolve) => setTimeout(resolve, 1000)) - const list1 = await storacha.args(['ls', '--json']).env(context.env.alice).join() + const list1 = await storacha + .args(['ls', '--json']) + .env(context.env.alice) + .join() assert.ok(dagJSON.parse(list1.output)) }), @@ -792,10 +814,7 @@ export const testStorachaUp = { .catch() assert.equal(rm.status.code, 1) - assert.match( - rm.error, - /not found/ - ) + assert.match(rm.error, /not found/) }), } @@ -869,7 +888,7 @@ export const testDelegation = { 'store/add', '-c', 'upload/add', - '--base64' + '--base64', ]) .env(env) .join() @@ -1057,7 +1076,10 @@ export const testProof = { const whoisbob = await storacha.args(['whoami']).env(env.bob).join() const bobDID = DID.parse(whoisbob.output.trim()).did() - const proofPath = path.join(os.tmpdir(), `storacha-cli-test-proof-${Date.now()}`) + const proofPath = path.join( + os.tmpdir(), + `storacha-cli-test-proof-${Date.now()}` + ) await storacha .args([ 'delegation', @@ -1256,7 +1278,10 @@ export const testPlan = { // wait a second for invocation to get a different expiry await new Promise((resolve) => setTimeout(resolve, 1000)) - const plan = await storacha.args(['plan', 'get']).env(context.env.alice).join() + const plan = await storacha + .args(['plan', 'get']) + .env(context.env.alice) + .join() assert.match(plan.output, /did:web:free.web3.storage/) }), } @@ -1272,7 +1297,9 @@ export const testKey = { export const testBridge = { 'storacha bridge generate-tokens': test(async (assert, context) => { const spaceDID = await loginAndCreateSpace(context) - const res = await storacha.args(['bridge', 'generate-tokens', spaceDID]).join() + const res = await storacha + .args(['bridge', 'generate-tokens', spaceDID]) + .join() assert.match(res.output, /X-Auth-Secret header: u/) assert.match(res.output, /Authorization header: u/) }), @@ -1313,7 +1340,11 @@ export const login = async ( */ export const selectPlan = async ( context, - { email = 'alice@web.mail', billingID = 'test:cus_alice', plan = 'did:web:free.web3.storage' } = {} + { + email = 'alice@web.mail', + billingID = 'test:cus_alice', + plan = 'did:web:free.web3.storage', + } = {} ) => { const customer = DIDMailto.fromEmail(email) Result.try(await context.plansStorage.initialize(customer, billingID, plan)) diff --git a/packages/cli/test/helpers/context.js b/packages/cli/test/helpers/context.js index 4264183a9..98ed86bfb 100644 --- a/packages/cli/test/helpers/context.js +++ b/packages/cli/test/helpers/context.js @@ -62,7 +62,8 @@ export const setup = async () => { const { server, serverURL, router } = await createHTTPServer({ '/': context.connection.channel.request.bind(context.connection.channel), }) - const { server: receiptsServer, serverURL: receiptsServerUrl } = await createReceiptsServer() + const { server: receiptsServer, serverURL: receiptsServerUrl } = + await createReceiptsServer() return Object.assign(context, { server, diff --git a/packages/cli/test/helpers/env.js b/packages/cli/test/helpers/env.js index c8024b436..58249539e 100644 --- a/packages/cli/test/helpers/env.js +++ b/packages/cli/test/helpers/env.js @@ -12,7 +12,7 @@ export function createEnv(options = {}) { Object.assign(env, { STORACHA_SERVICE_DID: servicePrincipal.did(), STORACHA_SERVICE_URL: serviceURL.toString(), - STORACHA_RECEIPTS_URL: receiptsEndpoint?.toString() + STORACHA_RECEIPTS_URL: receiptsEndpoint?.toString(), }) } return env diff --git a/packages/cli/test/helpers/process.js b/packages/cli/test/helpers/process.js index 385410d13..70eb682e1 100644 --- a/packages/cli/test/helpers/process.js +++ b/packages/cli/test/helpers/process.js @@ -161,7 +161,6 @@ class Join { } } - /** * @template {string} Channel * @param {AsyncIterable} source diff --git a/packages/cli/test/lib.spec.js b/packages/cli/test/lib.spec.js index 92e0d1705..6ed711154 100644 --- a/packages/cli/test/lib.spec.js +++ b/packages/cli/test/lib.spec.js @@ -75,7 +75,10 @@ bafybeibvbxjeodaa6hdqlgbwmv4qzdp3bxnwdoukay4dpl7aemkiwc2eje` 'uploadListResponseToString can return the upload roots as newline delimited JSON': (assert) => { assert.equal( - uploadListResponseToString(uploadListResponse, { shards: true, plainTree: true }), + uploadListResponseToString(uploadListResponse, { + shards: true, + plainTree: true, + }), `bafybeia7tr4dgyln7zeyyyzmkppkcts6azdssykuluwzmmswysieyadcbm └─┬ shards └── bagbaierantza4rfjnhqksp2stcnd2tdjrn3f2kgi2wrvaxmayeuolryi66fq diff --git a/packages/upload-api/src/types.ts b/packages/upload-api/src/types.ts index 375409ebb..18101f195 100644 --- a/packages/upload-api/src/types.ts +++ b/packages/upload-api/src/types.ts @@ -161,7 +161,11 @@ import { RevocationsStorage } from './types/revocations.js' export * from '@storacha/capabilities/types' export * from '@ucanto/interface' -export type { ProvisionsStorage, Provision, Subscription } from './types/provisions.js' +export type { + ProvisionsStorage, + Provision, + Subscription, +} from './types/provisions.js' export type { DelegationsStorage, Query as DelegationsStorageQuery, diff --git a/packages/upload-api/test/external-service/index.js b/packages/upload-api/test/external-service/index.js index 6dedf1842..98c1e274f 100644 --- a/packages/upload-api/test/external-service/index.js +++ b/packages/upload-api/test/external-service/index.js @@ -5,7 +5,13 @@ import { BrowserStorageNode, StorageNode } from './storage-node.js' import * as BlobRetriever from './blob-retriever.js' import * as RoutingService from './router.js' -export { ClaimsService, BrowserStorageNode, StorageNode, BlobRetriever, RoutingService } +export { + ClaimsService, + BrowserStorageNode, + StorageNode, + BlobRetriever, + RoutingService, +} /** * @param {object} config diff --git a/packages/upload-api/test/external-service/storage-node.js b/packages/upload-api/test/external-service/storage-node.js index 77cb74a03..614ea780a 100644 --- a/packages/upload-api/test/external-service/storage-node.js +++ b/packages/upload-api/test/external-service/storage-node.js @@ -10,7 +10,10 @@ import { ed25519 } from '@ucanto/principal' import { CAR, HTTP } from '@ucanto/transport' import * as Server from '@ucanto/server' import { connect } from '@ucanto/client' -import { AllocatedMemoryNotWrittenError, BlobSizeLimitExceededError } from '../../src/blob.js' +import { + AllocatedMemoryNotWrittenError, + BlobSizeLimitExceededError, +} from '../../src/blob.js' /** * @typedef {{ @@ -61,7 +64,12 @@ const createService = ({ const digest = Digest.decode(capability.nb.blob.digest) const checksum = base64pad.baseEncode(digest.digest) if (capability.nb.blob.size > MaxUploadSize) { - return error(new BlobSizeLimitExceededError(capability.nb.blob.size, MaxUploadSize)) + return error( + new BlobSizeLimitExceededError( + capability.nb.blob.size, + MaxUploadSize + ) + ) } if (await contentStore.has(digest)) { return ok({ size: 0 }) diff --git a/packages/upload-api/test/handlers/blob.js b/packages/upload-api/test/handlers/blob.js index 5ea71ce60..3a491f49b 100644 --- a/packages/upload-api/test/handlers/blob.js +++ b/packages/upload-api/test/handlers/blob.js @@ -115,7 +115,7 @@ export const test = { proofs: [proof], // Note: we have to set an expiration, or the default expiration value // will be set when the invocation is executed and the UCAN issued. - expiration: (Math.floor(Date.now() / 1000)) + 10 + expiration: Math.floor(Date.now() / 1000) + 10, }) // Invoke `blob/add` for the first time const firstBlobAdd = await invocation.execute(connection) diff --git a/packages/w3up-client/src/service.js b/packages/w3up-client/src/service.js index 48af26bc9..41bdec60c 100644 --- a/packages/w3up-client/src/service.js +++ b/packages/w3up-client/src/service.js @@ -4,7 +4,9 @@ import * as DID from '@ipld/dag-ucan/did' import { receiptsEndpoint } from '@storacha/upload-client' export const accessServiceURL = new URL('https://upload.storacha.network') -export const accessServicePrincipal = DID.parse('did:web:upload.storacha.network') +export const accessServicePrincipal = DID.parse( + 'did:web:upload.storacha.network' +) export const accessServiceConnection = client.connect({ id: accessServicePrincipal, @@ -13,7 +15,9 @@ export const accessServiceConnection = client.connect({ }) export const uploadServiceURL = new URL('https://upload.storacha.network') -export const uploadServicePrincipal = DID.parse('did:web:upload.storacha.network') +export const uploadServicePrincipal = DID.parse( + 'did:web:upload.storacha.network' +) export const uploadServiceConnection = client.connect({ id: uploadServicePrincipal, @@ -22,7 +26,9 @@ export const uploadServiceConnection = client.connect({ }) export const filecoinServiceURL = new URL('https://upload.storacha.network') -export const filecoinServicePrincipal = DID.parse('did:web:upload.storacha.network') +export const filecoinServicePrincipal = DID.parse( + 'did:web:upload.storacha.network' +) export const filecoinServiceConnection = client.connect({ id: filecoinServicePrincipal, From 471a8b5cb63faee4a503e2376c407173233f46c9 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 Nov 2024 07:52:32 +0700 Subject: [PATCH 3/8] fix: test --- packages/w3up-client/test/client.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/w3up-client/test/client.test.js b/packages/w3up-client/test/client.test.js index cfaee56a2..4b2041f75 100644 --- a/packages/w3up-client/test/client.test.js +++ b/packages/w3up-client/test/client.test.js @@ -613,7 +613,7 @@ export const testClient = { defaultProvider: { 'should return the connection ID': async (assert) => { const alice = new Client(await AgentData.create()) - assert.equal(alice.defaultProvider(), 'did:web:storacha.network') + assert.equal(alice.defaultProvider(), 'did:web:upload.storacha.network') }, }, From 2a07267459d72fec04df7338dc168fbf09d357db Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 Nov 2024 07:58:06 +0700 Subject: [PATCH 4/8] fix: test service DID --- packages/upload-api/test/helpers/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/upload-api/test/helpers/context.js b/packages/upload-api/test/helpers/context.js index 3ed5dd34c..c1b67a7bb 100644 --- a/packages/upload-api/test/helpers/context.js +++ b/packages/upload-api/test/helpers/context.js @@ -29,7 +29,7 @@ export const createContext = async ( const signer = await Signer.generate() const aggregatorSigner = await Signer.generate() const dealTrackerSigner = await Signer.generate() - const id = signer.withDID('did:web:test.storacha.network') + const id = signer.withDID('did:web:test.upload.storacha.network') const service = getMockService() const dealTrackerConnection = getConnection( From a9375e3a9fd662201e4dfdc6257df3773e8efa4f Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 Nov 2024 08:13:10 +0700 Subject: [PATCH 5/8] fix: more test fixes --- .../test/agent-use-cases.test.js | 2 +- .../test/capabilities/provider.test.js | 34 +++++++++---------- .../capabilities/test/helpers/fixtures.js | 2 +- packages/upload-api/test/handlers/plan.js | 2 +- packages/upload-api/test/helpers/utils.js | 2 +- .../test/storage/provisions-storage.js | 2 +- packages/upload-api/test/util.js | 2 +- packages/w3up-client/test/coupon.test.js | 4 +-- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/access-client/test/agent-use-cases.test.js b/packages/access-client/test/agent-use-cases.test.js index 86cf7f145..94a585d19 100644 --- a/packages/access-client/test/agent-use-cases.test.js +++ b/packages/access-client/test/agent-use-cases.test.js @@ -197,7 +197,7 @@ describe('authorizeWaitAndClaim', async function () { describe('getAccountPlan', async function () { const accountWithAPlan = 'did:mailto:example.com:i-have-a-plan' const accountWithoutAPlan = 'did:mailto:example.com:i-have-no-plan' - const product = 'did:web:test.storacha.network' + const product = 'did:web:test.upload.storacha.network' /** @type {Record} */ const plans = { [accountWithAPlan]: { diff --git a/packages/capabilities/test/capabilities/provider.test.js b/packages/capabilities/test/capabilities/provider.test.js index 2746a8c09..2d566bf64 100644 --- a/packages/capabilities/test/capabilities/provider.test.js +++ b/packages/capabilities/test/capabilities/provider.test.js @@ -18,7 +18,7 @@ describe('provider/add', function () { audience: service, with: account, nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: space.did(), }, proofs: await createAuthorization({ agent, service, account }), @@ -36,7 +36,7 @@ describe('provider/add', function () { assert.deepEqual(result.ok.audience.did(), service.did()) assert.equal(result.ok.capability.can, 'provider/add') assert.deepEqual(result.ok.capability.nb, { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: space.did(), }) } @@ -51,7 +51,7 @@ describe('provider/add', function () { audience: service, with: account, nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: space.did(), }, }) @@ -80,7 +80,7 @@ describe('provider/add', function () { audience: service, with: account, nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: space.did(), }, proofs: [delegation], @@ -110,7 +110,7 @@ describe('provider/add', function () { audience: service, with: account, nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: space.did(), }, proofs: [attestation], @@ -135,7 +135,7 @@ describe('provider/add', function () { with: bobAccount.did(), // @ts-ignore nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', }, }) }, /Error: Invalid 'nb' - Object contains invalid field "consumer"/) @@ -149,7 +149,7 @@ describe('provider/add', function () { audience: service, with: bobAccount.did(), nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', // @ts-expect-error consumer: 'did:mailto:storacha.network:user', }, @@ -166,7 +166,7 @@ describe('provider/add', function () { with: bobAccount.did(), // @ts-expect-error - missing provider nb: { - // provider: 'did:web:test.storacha.network', + // provider: 'did:web:test.upload.storacha.network', consumer: bob.did(), }, }) @@ -197,7 +197,7 @@ describe('provider/add', function () { audience: service, with: account, nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: space.did(), }, proofs: [ @@ -206,7 +206,7 @@ describe('provider/add', function () { audience: bob, with: account, nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: space.did(), }, proofs: await createAuthorization({ agent, service, account }), @@ -233,7 +233,7 @@ describe('provider/add', function () { audience: service, with: account, nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: space.did(), }, proofs: [ @@ -242,7 +242,7 @@ describe('provider/add', function () { audience: bob, with: account, nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', }, proofs: await createAuthorization({ agent, service, account }), }), @@ -268,7 +268,7 @@ describe('provider/add', function () { audience: service, with: account, nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: space.did(), }, proofs: [ @@ -303,7 +303,7 @@ describe('provider/add', function () { audience: service, with: account, nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: bob.did(), }, proofs: [ @@ -338,7 +338,7 @@ describe('provider/add', function () { audience: service, with: account, nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: bob.did(), }, proofs: [ @@ -379,7 +379,7 @@ describe('provider/add', function () { audience: service, with: 'did:mailto:mallory.com:bob', nb: { - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', consumer: bob.did(), }, proofs: [ @@ -428,7 +428,7 @@ describe('provider/add', function () { with: account.did(), nb: { consumer: space.did(), - provider: 'did:web:test.storacha.network', + provider: 'did:web:test.upload.storacha.network', }, // NOTE: no proofs! }) diff --git a/packages/capabilities/test/helpers/fixtures.js b/packages/capabilities/test/helpers/fixtures.js index 2537f1c7f..2e2c6b1e1 100644 --- a/packages/capabilities/test/helpers/fixtures.js +++ b/packages/capabilities/test/helpers/fixtures.js @@ -31,7 +31,7 @@ export const malloryAccount = Absentee.from({ export const service = Signer.parse( 'MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8=' -).withDID('did:web:test.storacha.network') +).withDID('did:web:test.upload.storacha.network') export const readmeCID = parseLink( 'bafybeihqfdg2ereoijjoyrqzr2x2wsasqm2udurforw7pa3tvbnxhojao4' diff --git a/packages/upload-api/test/handlers/plan.js b/packages/upload-api/test/handlers/plan.js index fa4090caa..a18c197ec 100644 --- a/packages/upload-api/test/handlers/plan.js +++ b/packages/upload-api/test/handlers/plan.js @@ -12,7 +12,7 @@ export const test = { 'an account can get plan information': async (assert, context) => { const account = 'did:mailto:example.com:alice' const billingID = 'stripe:abc123' - const product = 'did:web:test.storacha.network' + const product = 'did:web:test.upload.storacha.network' await context.plansStorage.initialize(account, billingID, product) const connection = connect({ id: context.id, diff --git a/packages/upload-api/test/helpers/utils.js b/packages/upload-api/test/helpers/utils.js index 1af3ac171..ffc2d8ef6 100644 --- a/packages/upload-api/test/helpers/utils.js +++ b/packages/upload-api/test/helpers/utils.js @@ -41,7 +41,7 @@ export const w3 = ed25519 .parse( 'MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8=' ) - .withDID('did:web:test.storacha.network') + .withDID('did:web:test.upload.storacha.network') /** * Creates a server for the given service. diff --git a/packages/upload-api/test/storage/provisions-storage.js b/packages/upload-api/test/storage/provisions-storage.js index 422e3e788..169513b10 100644 --- a/packages/upload-api/test/storage/provisions-storage.js +++ b/packages/upload-api/test/storage/provisions-storage.js @@ -16,7 +16,7 @@ export class ProvisionsStorage { * * @param {Array} providers */ - constructor(providers = ['did:web:test.storacha.network']) { + constructor(providers = ['did:web:test.upload.storacha.network']) { /** * @type {Record} */ diff --git a/packages/upload-api/test/util.js b/packages/upload-api/test/util.js index 8b3a52d1d..f28c2e43b 100644 --- a/packages/upload-api/test/util.js +++ b/packages/upload-api/test/util.js @@ -29,7 +29,7 @@ export const service = ed25519 .parse( 'MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8=' ) - .withDID('did:web:test.storacha.network') + .withDID('did:web:test.upload.storacha.network') /** * @param {import('@ucanto/interface').Principal} audience diff --git a/packages/w3up-client/test/coupon.test.js b/packages/w3up-client/test/coupon.test.js index b6c633447..c2035cb0f 100644 --- a/packages/w3up-client/test/coupon.test.js +++ b/packages/w3up-client/test/coupon.test.js @@ -16,7 +16,7 @@ export const testCoupon = Test.withContext({ const account = await login // Then we setup a billing for this account - await plansStorage.set(account.did(), 'did:web:test.storacha.network') + await plansStorage.set(account.did(), 'did:web:test.upload.storacha.network') // Then we use the account to issue a coupon for the workshop const coupon = await client.coupon.issue({ @@ -45,7 +45,7 @@ export const testCoupon = Test.withContext({ const info = await alice.capability.space.info(space.did()) assert.deepEqual(info.did, space.did()) - assert.deepEqual(info.providers, ['did:web:test.storacha.network']) + assert.deepEqual(info.providers, ['did:web:test.upload.storacha.network']) }, 'coupon with password': async ( From b147e09124d375c1d1c53a072f216434001f09c3 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 Nov 2024 08:20:02 +0700 Subject: [PATCH 6/8] chore: format again --- packages/w3up-client/test/coupon.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/w3up-client/test/coupon.test.js b/packages/w3up-client/test/coupon.test.js index c2035cb0f..d567d5d04 100644 --- a/packages/w3up-client/test/coupon.test.js +++ b/packages/w3up-client/test/coupon.test.js @@ -16,7 +16,10 @@ export const testCoupon = Test.withContext({ const account = await login // Then we setup a billing for this account - await plansStorage.set(account.did(), 'did:web:test.upload.storacha.network') + await plansStorage.set( + account.did(), + 'did:web:test.upload.storacha.network' + ) // Then we use the account to issue a coupon for the workshop const coupon = await client.coupon.issue({ From 4e4ba69727be1f9447f476b72f3c125295358e20 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 Nov 2024 08:29:15 +0700 Subject: [PATCH 7/8] fix: wait 1s to avoid cached receipt --- packages/cli/test/bin.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cli/test/bin.spec.js b/packages/cli/test/bin.spec.js index f1cd1b73a..645de6f8e 100644 --- a/packages/cli/test/bin.spec.js +++ b/packages/cli/test/bin.spec.js @@ -564,6 +564,9 @@ export const testSpace = { provider: providerDID, }) + // wait 1 second so we don't get a cached receipt + await new Promise(resolve => setTimeout(resolve, 1000)) + const infoWithProvider = await storacha .args(['space', 'info']) .env(context.env.alice) From 8f7652c76145fc631fa4dd8eb15527aac34fee15 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 Nov 2024 09:15:01 +0700 Subject: [PATCH 8/8] fix(upload-client): service details --- packages/upload-client/src/service.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/upload-client/src/service.js b/packages/upload-client/src/service.js index 668b0bb66..ca8f27b8e 100644 --- a/packages/upload-client/src/service.js +++ b/packages/upload-client/src/service.js @@ -2,9 +2,9 @@ import { connect } from '@ucanto/client' import { CAR, HTTP } from '@ucanto/transport' import * as DID from '@ipld/dag-ucan/did' -export const serviceURL = new URL('https://up.storacha.network') -export const servicePrincipal = DID.parse('did:web:storacha.network') -export const receiptsEndpoint = 'https://up.storacha.network/receipt/' +export const serviceURL = new URL('https://upload.storacha.network') +export const servicePrincipal = DID.parse('did:web:upload.storacha.network') +export const receiptsEndpoint = 'https://upload.storacha.network/receipt/' /** @type {import('@ucanto/interface').ConnectionView} */ export const connection = connect({