From 0dc2d445f1379e268e6e34102065e32b1499a402 Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Mon, 8 Jan 2024 08:36:35 -0500 Subject: [PATCH 01/11] Clarify the default entrypoint (#201) * Squash commits to entrypoints file from default-entrypoint branch * Most people can ignore this info --- docs/smart-contracts/entrypoints.md | 88 ++++++++++++++--------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/docs/smart-contracts/entrypoints.md b/docs/smart-contracts/entrypoints.md index 708530bc1..8a1c5495c 100644 --- a/docs/smart-contracts/entrypoints.md +++ b/docs/smart-contracts/entrypoints.md @@ -2,7 +2,7 @@ title: Entrypoints authors: 'Mathias Hiron (Nomadic Labs), Sasha Aldrick (TriliTech), Tim McMackin (TriliTech)' last_update: - date: 4 October 2023 + date: 26 December 2023 --- The entrypoints of a contract represent the different ways that it can be called, similar to a method or function in many programming languages or an endpoint of an API. @@ -12,77 +12,77 @@ The entrypoints in a Tezos smart contract must meet these specifications: - Each entrypoint must have a name. - Entrypoints may accept parameters, which can be of almost any data type that Tezos supports. -Unlike functions and API endpoints, entrypoints do not return a value. -To return a value from a smart contract, see [Views](./views). +Unlike functions and API endpoints, entrypoints do not return a value directly to the caller. +To return a value from a smart contract, you can use one of these methods: -For examples of contracts, see [Examples of contracts](https://opentezos.com/smart-contracts/simplified-contracts/) on opentezos.com. +- Use [Views](./views) +- Include a callback parameter that sends information to another smart contract, as in the `getAllowance`, `getBalance`, and `getTotalSupply` entrypoints of [FA1.2](../architecture/tokens/FA1.2) contracts + +For an example of a simple contract, see the tutorial [Create a smart contract](../tutorials/smart-contract). + +For examples of more complex contracts, see [Examples of contracts](https://opentezos.com/smart-contracts/simplified-contracts/) on opentezos.com. ## Entrypoint logic -An entrypoint may run all kinds of logic based on: +An entrypoint may run logic based on: -- Its storage +- The contract storage - The parameters that senders pass -- Global values such as the address of the caller +- Transaction context values such as the address of the caller - The table of constants -Entrypoints may not access information outside Tezos, such as calling external APIs. +Entrypoints cannot access information outside of Tezos, such as calling external APIs. If an entrypoint needs information from outside Tezos it must use oracles; see [Using and trusting Oracles](https://opentezos.com/smart-contracts/oracles/) on opentezos.com. -The only effects that an entrypoint can have are changes to its storage and new transactions that are run after the entrypoint completes. -For example, an entrypoint can call other entrypoints in its contract or entrypoints in other contracts. +The only effects that an entrypoint can have are changes to its storage and new operations that are run after the entrypoint completes. +An entrypoint can call other entrypoints in its contract or entrypoints in other contracts. ## Example entrypoints -Here is a very basic example of contract with two entrypoints: +The contract in the tutorial [Create a smart contract](../tutorials/smart-contract) has three entrypoints: | Entrypoint | Description | | --- | --- | -| `add` | `add` takes an `int` as a parameter and adds it to the previous value of the storage | -| `reset` | `reset` takes no parameter and replaces the storage with 0 | - - +For example, when you compile the contract in the tutorial [Create a smart contract](../tutorials/smart-contract) to Michelson, its first line defines the parameter type that the contract accepts: +``` +parameter (or (unit %reset) (or (int %decrement) (int %increment))) +``` - - +To call the `reset` entrypoint, clients technically call the default entrypoint and pass the Michelson-encoded parameter `Left Unit`. +This parameter value means that the left value of the parameter type, which is annotated `%reset`, is set to the value `Unit`, which means no value. +In its logic, the compiled Michelson code uses the `IF_LEFT` command to check if the left value of the parameter is defined and if so, it runs the `reset` entrypoint code. +In this way, the following Octez client commands are equivalent; one passes `Unit` to the `reset` entrypoint and the other passes `Left Unit` to the default entrypoint: -## Entrypoint implementation +```bash +octez-client --wait none transfer 0 from myAccount to myContract \ + --entrypoint 'reset' --arg 'Unit' --burn-cap 0.1 +``` -Internally, entrypoints are implemented using a variant data type. -The contract contains a single piece of logic that branches based on the value of that variant. -In Michelson, the different values of the variant are annotated with the name of the corresponding entrypoint. - +```bash +octez-client --wait none transfer 0 from myAccount to myContract \ + --arg 'Left Unit' --burn-cap 0.1 +``` -When calling a smart contract, senders provide either the full parameter as a variant or specify the name of the entrypoint and the corresponding parameter value. +Developers need to know about the default entrypoint only when they encode parameters for smart contracts manually. +Most Tezos clients, including Octez and Taquito, encode parameters automatically. +Working from the previous example, they convert a call to the `increment` entrypoint with the parameter 5 as a call to the default entrypoint with the parameter `Right (Right 5)`. -Other languages have different ways of indicating entrypoints. +Different languages have different ways of indicating entrypoints. For information about coding entrypoints in specific languages, see these links: - Michelson: [Entrypoint](https://tezos.gitlab.io/active/michelson.html#entrypoints) From 0c0753e244c75b41f750a280af95d39184c2db9a Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Mon, 8 Jan 2024 08:41:03 -0500 Subject: [PATCH 02/11] Remove Oxhead Alpha (#233) * Remove references to oxhead alpha * Link to FA2 examples, including OA github --- docs/architecture/tokens/FA2.md | 6 +++++- docs/developing/testnets.md | 10 +++++----- docs/smart-contracts/samples.md | 7 ++++++- docs/tutorials/create-an-nft/nft-taquito.md | 1 - 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/architecture/tokens/FA2.md b/docs/architecture/tokens/FA2.md index 5731160d7..26ffc2424 100644 --- a/docs/architecture/tokens/FA2.md +++ b/docs/architecture/tokens/FA2.md @@ -2,7 +2,7 @@ title: FA2 tokens authors: "Claude Barde, Aymeric Bethencourt, Tim McMackin" last_update: - date: 28 November 2023 + date: 29 December 2023 --- The FA2 standard supports several different token types, including: @@ -19,6 +19,10 @@ If the contract has only one type of token, its ID must be 0, but if it has mult For the full details of the FA2 standard, see [Tezos Improvement Proposal 12 (TZIP-12)](https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-12/tzip-12.md), which defines the standard. +## Examples + +For examples of FA2 contracts, see [Sample smart contracts](../../smart-contracts/samples). + ## Metadata FA2 tokens have metadata that describes what the token represents. diff --git a/docs/developing/testnets.md b/docs/developing/testnets.md index 3fff72896..5bbf22c61 100644 --- a/docs/developing/testnets.md +++ b/docs/developing/testnets.md @@ -2,11 +2,11 @@ title: Using sandboxes and testnets authors: 'Mathias Hiron, Nomadic Labs, Tim McMackin, TriliTech' last_update: - date: 18 October 2023 + date: 29 December 2023 --- :::note -The Tezos testnets are overseen and coordinated by [Oxhead Alpha](https://oxheadalpha.com/). The current testnets and a description of what each is used for are listed at https://teztnets.xyz/. +The current testnets and a description of what each is used for are listed at https://teztnets.xyz/. ::: ## Testing without a node @@ -43,7 +43,7 @@ _Permanent test networks_ are networks that are meant to run indefinitely. In pa For the `Ghostnet` permanent network, the governance is controlled by a single entity that manages the network, with a special upgrade mechanism. -At the moment, the main such network is `Ghostnet`. It follows the currently active protocol on `Mainnet`, and upgrades to the next protocol during the Adoption period on `Mainnet`. That is, after the last round of voting and before it activates on `Mainnet`. The objective is to provide a rehearsal event for `Mainnet` migration. This process is controlled by the Oxhead Alpha team. +At the moment, the main such network is `Ghostnet`. It follows the currently active protocol on `Mainnet`, and upgrades to the next protocol during the Adoption period on `Mainnet`. That is, after the last round of voting and before it activates on `Mainnet`. The objective is to provide a rehearsal event for `Mainnet` migration. For developers, using a permanent network like `Ghostnet` is convenient compared to other public networks, as it makes it possible to keep contracts running for a long time without having to deal with the trouble of setting things up on a new network when the previous one gets shut down. Services such as indexers, explorers, or public nodes also tend to keep running more reliably on `Ghostnet` than on non-permanent networks. @@ -63,7 +63,7 @@ can request some tokens from the [Ghostnet faucet](https://faucet.ghostnet.teztn _Protocol test networks_ are networks that are created specifically for a given version of the protocol. -When an amendment is proposed, a corresponding network is usually created by the Oxhead Alpha team. This network gets joined by more bakers as the proposal is selected and moves through the different periods of the self-amendment process. If the protocol passes the 3 votes of the amendment, joining a test protocol early gives you about 2.5 months to test all the changes that will be made to Mainnet. If the protocol is not adopted, it usually gets discarded. Otherwise, it remains active until a different protocol is adopted. +When an amendment is proposed, a corresponding network is created. This network gets joined by more bakers as the proposal is selected and moves through the different periods of the self-amendment process. If the protocol passes the 3 votes of the amendment, joining a test protocol early gives you about 2.5 months to test all the changes that will be made to Mainnet. If the protocol is not adopted, it usually gets discarded. Otherwise, it remains active until a different protocol is adopted. This means there is usually one or two such running networks: one for the current version of the protocol running on `Mainnet`, and possibly one for the proposed protocol that is going through the amendment process, if there is one. @@ -93,7 +93,7 @@ The two periodic protocols currently are `Mondaynet` and `Dailynet`. ### Public nodes and faucets -To connect to existing public nodes for these networks, or to get some testnet-only tez on these from a faucet, check [https://teztnets.xyz](https://teztnets.xyz/). The faucets and infrastructure for deploying test networks are maintained by [Oxhead Alpha](https://www.oxheadalpha.com/). +To connect to existing public nodes for these networks, or to get some testnet-only tez on these from a faucet, check [https://teztnets.xyz](https://teztnets.xyz/). Other sources of public nodes include: diff --git a/docs/smart-contracts/samples.md b/docs/smart-contracts/samples.md index ab7510e1c..91e8df8f9 100644 --- a/docs/smart-contracts/samples.md +++ b/docs/smart-contracts/samples.md @@ -2,7 +2,7 @@ title: Sample smart contracts author: "Tim McMackin" last_update: - date: 26 December 2023 + date: 29 December 2023 --- Here are some places to find sample smart contacts: @@ -12,3 +12,8 @@ Here are some places to find sample smart contacts: - For examples of contracts in LIGO, see https://packages.ligolang.org/contracts - For examples of contracts in Archetype, see https://archetype-lang.org/docs/templates/overview - For examples of contracts in SmartPy, see https://smartpy.io/guides/examples/ + +For examples of FA2 smart contracts, see: + +- The SmartPy [FA2 library](https://smartpy.io/guides/FA2-lib/overview) +- [oxheadalpha/smart-contracts](https://github.com/oxheadalpha/smart-contracts) diff --git a/docs/tutorials/create-an-nft/nft-taquito.md b/docs/tutorials/create-an-nft/nft-taquito.md index 11f895f9e..9a407f01e 100644 --- a/docs/tutorials/create-an-nft/nft-taquito.md +++ b/docs/tutorials/create-an-nft/nft-taquito.md @@ -123,7 +123,6 @@ The FA2 standard creates a framework for how tokens behave on Tezos, including f It provides a standard API to transfer tokens, check token balances, manage operators (addresses that are permitted to transfer tokens on behalf of the token owner), and manage token metadata. The full details of the smart contract are beyond the scope of this tutorial, but the major parts of the contract have descriptions in comments. -For more examples of smart contracts, see [oxheadalpha/smart-contracts](https://github.com/oxheadalpha/smart-contracts) on GitHub. ### Contract entrypoints From 9936653529c55021a16225bbbf2b704107d7fbfb Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Mon, 8 Jan 2024 08:50:39 -0500 Subject: [PATCH 03/11] Use teztnets.com instead of .xyz (#251) --- docs/architecture/rpc.md | 2 +- docs/developing/octez-client/installing.md | 4 ++-- docs/developing/testnets.md | 6 +++--- docs/developing/wallet-setup.md | 6 +++--- docs/tutorials/build-an-nft-marketplace/part-1.md | 4 ++-- .../build-your-first-app/sending-transactions.md | 4 ++-- docs/tutorials/build-your-first-app/wallets-tokens.md | 10 +++++----- docs/tutorials/create-an-nft/nft-taquito.md | 4 ++-- docs/tutorials/create-an-nft/nft-tznft.md | 4 ++-- docs/tutorials/dapp/part-1.md | 2 +- docs/tutorials/smart-contract/archetype.md | 6 +++--- docs/tutorials/smart-contract/cameligo.mdx | 2 +- docs/tutorials/smart-contract/jsligo.mdx | 2 +- docs/tutorials/smart-contract/smartpy.mdx | 2 +- docs/tutorials/smart-rollup/run.md | 2 +- 15 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/architecture/rpc.md b/docs/architecture/rpc.md index 106fabb6b..536860618 100644 --- a/docs/architecture/rpc.md +++ b/docs/architecture/rpc.md @@ -20,7 +20,7 @@ By default, RPC servers are private and do not accept all requests from every cl When you work with a Tezos client, such as the Octez command-line client or the Taquito SDK, you select a public RPC node to send transactions to, or you can use a private RPC node that you have access to. -If you're using a testnet, you can get a list of public RPC nodes for that network at https://teztnets.xyz. +If you're using a testnet, you can get a list of public RPC nodes for that network at https://teztnets.com. Other sources of public nodes include: diff --git a/docs/developing/octez-client/installing.md b/docs/developing/octez-client/installing.md index 4ec5e9e36..dec378090 100644 --- a/docs/developing/octez-client/installing.md +++ b/docs/developing/octez-client/installing.md @@ -58,12 +58,12 @@ Then, initialize it to use the RPC node of your choice: 1. Set the RPC node to use: 1. Get the URL of a public RPC node or a private node that you have access to. - For example, you can get the URL of a testnet node from https://teztnets.xyz/, such as `https://rpc.ghostnet.teztnets.xyz` for Ghostnet. + For example, you can get the URL of a testnet node from https://teztnets.com/, such as `https://rpc.ghostnet.teztnets.com` for Ghostnet. 1. Set your Octez client to use this node by running this command on the command line, replacing the Ghostnet URL with the URL that you copied: ```bash - octez-client --endpoint https://rpc.ghostnet.teztnets.xyz config update + octez-client --endpoint https://rpc.ghostnet.teztnets.com config update ``` If you are using a testnet, Octez shows a warning that you are not using Mainnet. diff --git a/docs/developing/testnets.md b/docs/developing/testnets.md index 5bbf22c61..53341a819 100644 --- a/docs/developing/testnets.md +++ b/docs/developing/testnets.md @@ -6,7 +6,7 @@ last_update: --- :::note -The current testnets and a description of what each is used for are listed at https://teztnets.xyz/. +The current testnets and a description of what each is used for are listed at https://teztnets.com/. ::: ## Testing without a node @@ -56,7 +56,7 @@ As the protocol on `Ghostnet` migrates to the newly adopted amendment a few days #### Getting tez testnet tokens In order to get tez tokens to use when testing your application on testnet, you can use a faucet. You -can request some tokens from the [Ghostnet faucet](https://faucet.ghostnet.teztnets.xyz/) +can request some tokens from the [Ghostnet faucet](https://faucet.ghostnet.teztnets.com/) ### Protocol test networks @@ -93,7 +93,7 @@ The two periodic protocols currently are `Mondaynet` and `Dailynet`. ### Public nodes and faucets -To connect to existing public nodes for these networks, or to get some testnet-only tez on these from a faucet, check [https://teztnets.xyz](https://teztnets.xyz/). +To connect to existing public nodes for these networks, or to get some testnet-only tez on these from a faucet, check [https://teztnets.com](https://teztnets.com/). Other sources of public nodes include: diff --git a/docs/developing/wallet-setup.md b/docs/developing/wallet-setup.md index 57ca3092e..26609e254 100644 --- a/docs/developing/wallet-setup.md +++ b/docs/developing/wallet-setup.md @@ -26,9 +26,9 @@ On testnets, tokens are free so you don't have to spend real currency to work wi The process for changing the network is different for each wallet type. These steps are for the Temple wallet: -1. Go to https://teztnets.xyz/, which lists Tezos testnets. +1. Go to https://teztnets.com/, which lists Tezos testnets. 1. Click **Ghostnet**. -1. Copy one of the public RPC endpoints for Ghostnet, such as `https://rpc.ghostnet.teztnets.xyz`. +1. Copy one of the public RPC endpoints for Ghostnet, such as `https://rpc.ghostnet.teztnets.com`. These URLs accept Tezos transactions from wallets and other applications. 1. In the Temple app, open the settings and then click **Default node (RPC)**. 1. Click the plus `+` symbol to add an RPC node. @@ -47,7 +47,7 @@ Follow these steps to get testnet tez for the wallet: 1. From your wallet, get the address of your account, which starts with `tz1`. This is the address that applications use to work with your wallet. -1. Go to the Ghostnet faucet at https://faucet.ghostnet.teztnets.xyz. +1. Go to the Ghostnet faucet at https://faucet.ghostnet.teztnets.com. 1. On the faucet page, paste your wallet address into the input field labeled "Or fund any address" and click the button for the amount of tez to add to your wallet. 20 tez is enough to work with, and you can return to the faucet later if you need more tez. diff --git a/docs/tutorials/build-an-nft-marketplace/part-1.md b/docs/tutorials/build-an-nft-marketplace/part-1.md index 0af1c44cf..12e3b940a 100644 --- a/docs/tutorials/build-an-nft-marketplace/part-1.md +++ b/docs/tutorials/build-an-nft-marketplace/part-1.md @@ -318,14 +318,14 @@ Follow these steps to create a contract that is based on the template and implem ``` Then make sure that the account has tez on Ghostnet. - Use the faucet at https://faucet.ghostnet.teztnets.xyz to get tez if you need it. + Use the faucet at https://faucet.ghostnet.teztnets.com to get tez if you need it. - To let Taqueria generate an account for you, follow these steps: 1. Run the command `taq deploy nft.tz -e "testing"`, which will fail because you do not have an account configured in Taqueria. The response includes the address of an account that Taqueria generated for you and added to the `.taq/config.local.testing.json` file automatically. - 1. Fund the account from the faucet at https://faucet.ghostnet.teztnets.xyz. + 1. Fund the account from the faucet at https://faucet.ghostnet.teztnets.com. 1. Compile and deploy the contract to Ghostnet by running this command: diff --git a/docs/tutorials/build-your-first-app/sending-transactions.md b/docs/tutorials/build-your-first-app/sending-transactions.md index d0c4cf7bb..8b47b1732 100644 --- a/docs/tutorials/build-your-first-app/sending-transactions.md +++ b/docs/tutorials/build-your-first-app/sending-transactions.md @@ -370,10 +370,10 @@ Here is the completed code of the `App.svelte` file at the end of this section:

Its balance in tez is {balance}.

To get tez, go to - https://faucet.ghostnet.teztnets.xyz/ + https://faucet.ghostnet.teztnets.com/ .

diff --git a/docs/tutorials/build-your-first-app/wallets-tokens.md b/docs/tutorials/build-your-first-app/wallets-tokens.md index 0003e9989..30a58b033 100644 --- a/docs/tutorials/build-your-first-app/wallets-tokens.md +++ b/docs/tutorials/build-your-first-app/wallets-tokens.md @@ -35,7 +35,7 @@ Ghostnet is a network for testing Tezos applications where tokens are free so yo 1. From your wallet, get the address of your account, which starts with `tz1`. This is the address that applications use to work with your wallet. -1. Go to the Ghostnet faucet page at https://faucet.ghostnet.teztnets.xyz. +1. Go to the Ghostnet faucet page at https://faucet.ghostnet.teztnets.com. 1. On the faucet page, paste your wallet address into the input field labeled "Or fund any address" and click the button for the amount of tez to add to your wallet. 20 tez is enough to work with the tutorial application, and you can return to the faucet later if you need more tez. @@ -138,8 +138,8 @@ If you add more components, you should move these objects to a separate file to {#if wallet}

The address of the connected wallet is {address}.

Its balance in tez is {balance}.

-

To get tez, go to - https://faucet.ghostnet.teztnets.xyz/ +

To get tez, go to + https://faucet.ghostnet.teztnets.com/ .

@@ -206,10 +206,10 @@ The complete `App.svelte` file looks like this:

Its balance in tez is {balance}.

To get tez, go to - https://faucet.ghostnet.teztnets.xyz/ + https://faucet.ghostnet.teztnets.com/ .

diff --git a/docs/tutorials/create-an-nft/nft-taquito.md b/docs/tutorials/create-an-nft/nft-taquito.md index 9a407f01e..16db3cf27 100644 --- a/docs/tutorials/create-an-nft/nft-taquito.md +++ b/docs/tutorials/create-an-nft/nft-taquito.md @@ -302,7 +302,7 @@ The network changes to the Ghostnet testnet, as in this picture: 1. Send funds to the account from the testnet faucet: - 1. Go to the Ghostnet faucet at https://faucet.ghostnet.teztnets.xyz/. + 1. Go to the Ghostnet faucet at https://faucet.ghostnet.teztnets.com/. 1. Put the new account address in the **Or fund any address** field. @@ -639,7 +639,7 @@ To test the application, you must have a Tezos wallet and a small amount of XTZ 1. Send funds to the wallet from the testnet faucet: - 1. Go to the Ghostnet faucet at https://faucet.ghostnet.teztnets.xyz/. + 1. Go to the Ghostnet faucet at https://faucet.ghostnet.teztnets.com/. 1. Put the wallet account address in the **Or fund any address** field. diff --git a/docs/tutorials/create-an-nft/nft-tznft.md b/docs/tutorials/create-an-nft/nft-tznft.md index 72ad7edb9..d18029228 100644 --- a/docs/tutorials/create-an-nft/nft-tznft.md +++ b/docs/tutorials/create-an-nft/nft-tznft.md @@ -78,7 +78,7 @@ Make sure to start Docker Desktop after you install it. tznft init ``` - The resulting file, named `tznft.config`, contains information about the Tezos networks that are available for you to work with, including the [Ghostnet](https://teztnets.xyz/ghostnet-about) test network and the local sandbox that you set up in the next steps. + The resulting file, named `tznft.config`, contains information about the Tezos networks that are available for you to work with, including the [Ghostnet](https://teztnets.com/ghostnet-about) test network and the local sandbox that you set up in the next steps. The `tznft` tool requires this file, so the commands in the following steps work only from the directory that you ran `tznft init` in. 4. Check that the default active network is "sandbox:" @@ -465,7 +465,7 @@ You can do this in either of these two ways: tznft add-alias my-account $TEZOS_PRIVATE_KEY ``` - 1. Add funds to the new wallet by going to the Ghostnet faucet at https://faucet.ghostnet.teztnets.xyz/, pasting the wallet's hash in the "Or fund any address" field, and clicking a button to request tokens. + 1. Add funds to the new wallet by going to the Ghostnet faucet at https://faucet.ghostnet.teztnets.com/, pasting the wallet's hash in the "Or fund any address" field, and clicking a button to request tokens. The wallet needs tokens to pay the fees to create the collection and mint the tokens on Ghostnet. 1. Create the collection on the testnet. diff --git a/docs/tutorials/dapp/part-1.md b/docs/tutorials/dapp/part-1.md index 9df60b6c4..6f33d7091 100644 --- a/docs/tutorials/dapp/part-1.md +++ b/docs/tutorials/dapp/part-1.md @@ -178,7 +178,7 @@ The default Tezos testing testnet is called **Ghostnet**. Warning: the faucet field in network configs has been deprecated and will be ignored. A keypair with public key hash tz1XXXXXXXXXXXXXXXXXXXXXX was generated for you. To fund this account: - 1. Go to https://teztnets.xyz and click "Faucet" of the target testnet. + 1. Go to https://teztnets.com and click "Faucet" of the target testnet. 2. Copy and paste the above key into the 'wallet address field. 3. Request some Tez (Note that you might need to wait for a few seconds for the network to register the funds). No operations performed. diff --git a/docs/tutorials/smart-contract/archetype.md b/docs/tutorials/smart-contract/archetype.md index 710881419..73a694d97 100644 --- a/docs/tutorials/smart-contract/archetype.md +++ b/docs/tutorials/smart-contract/archetype.md @@ -92,9 +92,9 @@ https://github.com/completium/completium-cli/issues/45 Before you deploy your contract to the main Tezos network (referred to as *Mainnet*), you can deploy it to a testnet. Testnets are useful for testing Tezos operations because testnets provide tokens for free so you can work with them without spending real tokens. -Tezos testnets are listed on this site: https://teztnets.xyz/. +Tezos testnets are listed on this site: https://teztnets.com/. -The [Ghostnet](https://teztnets.xyz/ghostnet-about) testnet is a good choice for testing because it is intended to be long-lived, as opposed to shorter-term testnets that allow people to test new Tezos features. +The [Ghostnet](https://teztnets.com/ghostnet-about) testnet is a good choice for testing because it is intended to be long-lived, as opposed to shorter-term testnets that allow people to test new Tezos features. By default, completium-cli uses Ghostnet, but these steps verify the network: @@ -143,7 +143,7 @@ You could use the default accounts that are included in completium-cli, but foll 1. Copy the address for the account, which is labeled as the "public key hash" in the response to the previous command. The address starts with "tz1". -1. On the testnets page at https://teztnets.xyz/, click the faucet link for the Ghostnet testnet, which is at https://faucet.ghostnet.teztnets.xyz. +1. On the testnets page at https://teztnets.com/, click the faucet link for the Ghostnet testnet, which is at https://faucet.ghostnet.teztnets.com. 1. On the faucet page, paste your wallet address into the input field labeled "Or fund any address" and click the button for the amount of XTZ to add to your wallet. 1 XTZ is enough for the tutorial. diff --git a/docs/tutorials/smart-contract/cameligo.mdx b/docs/tutorials/smart-contract/cameligo.mdx index b6e83da2b..dcb19a6d2 100644 --- a/docs/tutorials/smart-contract/cameligo.mdx +++ b/docs/tutorials/smart-contract/cameligo.mdx @@ -70,7 +70,7 @@ Ghostnet is a network for testing Tezos applications where tokens are free so yo 1. From your wallet, get the address of your account, which starts with `tz1`. This is the address that applications use to work with your wallet. -1. Go to the Ghostnet faucet page at https://faucet.ghostnet.teztnets.xyz. +1. Go to the Ghostnet faucet page at https://faucet.ghostnet.teztnets.com. 1. On the faucet page, paste your wallet address into the input field labeled "Or fund any address" and click the button for the amount of tez to add to your wallet. 20 tez is enough to work with the tutorial contract, and you can return to the faucet later if you need more tez. diff --git a/docs/tutorials/smart-contract/jsligo.mdx b/docs/tutorials/smart-contract/jsligo.mdx index 499161510..eeb26626d 100644 --- a/docs/tutorials/smart-contract/jsligo.mdx +++ b/docs/tutorials/smart-contract/jsligo.mdx @@ -70,7 +70,7 @@ Ghostnet is a network for testing Tezos applications where tokens are free so yo 1. From your wallet, get the address of your account, which starts with `tz1`. This is the address that applications use to work with your wallet. -1. Go to the Ghostnet faucet page at https://faucet.ghostnet.teztnets.xyz. +1. Go to the Ghostnet faucet page at https://faucet.ghostnet.teztnets.com. 1. On the faucet page, paste your wallet address into the input field labeled "Or fund any address" and click the button for the amount of tez to add to your wallet. 20 tez is enough to work with the tutorial contract, and you can return to the faucet later if you need more tez. diff --git a/docs/tutorials/smart-contract/smartpy.mdx b/docs/tutorials/smart-contract/smartpy.mdx index f9c66ac23..50da77700 100644 --- a/docs/tutorials/smart-contract/smartpy.mdx +++ b/docs/tutorials/smart-contract/smartpy.mdx @@ -74,7 +74,7 @@ Ghostnet is a network for testing Tezos applications where tokens are free so yo 1. From your wallet, get the address of your account, which starts with `tz1`. This is the address that applications use to work with your wallet. -1. Go to the Ghostnet faucet page at https://faucet.ghostnet.teztnets.xyz. +1. Go to the Ghostnet faucet page at https://faucet.ghostnet.teztnets.com. 1. On the faucet page, paste your wallet address into the input field labeled "Or fund any address" and click the button for the amount of tez to add to your wallet. 20 tez is enough to work with the tutorial contract, and you can return to the faucet later if you need more tez. diff --git a/docs/tutorials/smart-rollup/run.md b/docs/tutorials/smart-rollup/run.md index 678d5802f..b083d8c5e 100644 --- a/docs/tutorials/smart-rollup/run.md +++ b/docs/tutorials/smart-rollup/run.md @@ -111,6 +111,6 @@ To continue your work with Smart Rollups, you can you can explore examples from - [Smart Rollup kernel SDK](https://gitlab.com/tezos/tezos/-/tree/master/src/kernel_sdk) - [Smart Rollup kernel examples](https://gitlab.com/tezos/kernel-gallery/-/tree/main/) - [Tezos Smart Rollups resources](https://airtable.com/shrvwpb63rhHMiDg9/tbl2GNV1AZL4dkGgq) -- [Tezos testnets](https://teztnets.xyz/) +- [Tezos testnets](https://teztnets.com/) - [Originating the installer kernel](https://tezos.stackexchange.com/questions/4784/how-to-originating-a-smart-rollup-with-an-installer-kernel/5794#5794) - [Docker documentation](https://docs.docker.com/get-started/) From 541f6ffe38d987c418972fb9f04bb55f5469190e Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Tue, 9 Jan 2024 08:36:37 -0500 Subject: [PATCH 04/11] Image link checker (#225) * Image link checker * handle modules correctly * simplify * Add tests * run tests as part of build * move to test folder * Test passed file paths * Use Jest in MJS mode * Allow passing a list of files to test * try with mocha and chai * run tests on PRs * use updated action * Run on only md and mdx files * try this way * add check on files content * This seems to work with top-level await * no need to run twice * try with this PR setting * correct paths * remove logging message * can I skip these steps? * try adding a reporter * continue on error so the reporter can do its job * debug why test report file is not available * upload artifact * This didn't work This reverts commit 3722bfeba58802a03c941b157de57b633167c670. * try with quoted path * Restore files that I changed to get errors * Note about testing in readme --- .github/workflows/deploy-staging.yml | 3 + .github/workflows/tests.yml | 38 + .gitignore | 2 + README.md | 6 + package-lock.json | 906 ++++++++++++++++++++- package.json | 19 +- test/images.test.mjs | 140 ++++ test/resources/imageCheckTestResources.mjs | 467 +++++++++++ 8 files changed, 1548 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 test/images.test.mjs create mode 100644 test/resources/imageCheckTestResources.mjs diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index fb98bb586..2f540f9ee 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -18,6 +18,9 @@ jobs: with: node-version: 18 - run: npm ci + - id: files + uses: jitterbit/get-changed-files@v1 + - run: npm run test -- --filesToCheck=${{ steps.files.outputs.added_modified }} - run: npm run build - uses: aws-actions/configure-aws-credentials@v2 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..445dd18b3 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,38 @@ +name: Tests + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + + checkChangedFiles: + runs-on: ubuntu-latest + outputs: + files: ${{ steps.files.outputs.added_modified }} + steps: + - uses: actions/checkout@v2 + - id: files + uses: Ana06/get-changed-files@v1.2 + with: + format: 'csv' + filter: '*.mdx?' + + test: + runs-on: ubuntu-latest + needs: checkChangedFiles + if: ${{ needs.checkChangedFiles.outputs.files != '' }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 18 + - run: npm ci + - run: npm run test -- --reporter JSON --reporter-option output=mocha.json --filesToCheck=${{ needs.checkChangedFiles.outputs.files }} + continue-on-error: true + - run: ls + - uses: dorny/test-reporter@v1 + with: + name: Mocha tests + path: 'mocha.json' + reporter: mocha-json diff --git a/.gitignore b/.gitignore index 85643fb34..633f2680c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ node_modules out build +filesToTest.txt + diff --git a/README.md b/README.md index 47c57b38c..83e84b252 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,12 @@ npm run start Finally, open [http://localhost:3000](http://localhost:3000) in your browser to view the website. +## Tests + +The workflow `.github/workflows/tests.yml` runs automated tests on pull requests. +To run tests locally, run `npm run test -- --filesToCheck=docs/developing.md,docs/architecture.mdx`, where `--filesToCheck` is a comma-separated list of the MD and MDX files to test. + +Docusaurus automatically checks for broken links and markdown-encoded images when you run `npm run build`. ## License diff --git a/package-lock.json b/package-lock.json index 0bd64e73d..c544a6094 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,19 @@ "slick-carousel": "1.8.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.0.1" + "@docusaurus/module-type-aliases": "3.0.1", + "chai": "^5.0.0", + "glob": "^10.3.10", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0", + "minimist": "^1.2.8", + "mocha": "^10.2.0", + "rehype-stringify": "^10.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" }, "engines": { "node": ">=18.0" @@ -3010,6 +3022,50 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -3455,6 +3511,16 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", @@ -4540,6 +4606,15 @@ "node": ">=8" } }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -4608,6 +4683,15 @@ "node": ">=8" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/astring": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz", @@ -4878,6 +4962,12 @@ "node": ">=8" } }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "node_modules/browserslist": { "version": "4.22.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", @@ -5038,6 +5128,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.0.0.tgz", + "integrity": "sha512-HO5p0oEKd5M6HEcwOkNAThAE3j960vIZvVcc0t2tI06Dd0ATu69cEnMB2wOhC5/ZyQ6m67w3ePjU/HzXsSsdBA==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.0.0", + "deep-eql": "^5.0.1", + "loupe": "^3.0.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5097,6 +5203,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/check-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.0.0.tgz", + "integrity": "sha512-tjLAOBHKVxtPoHe/SA7kNOMvhCRdCJ3vETdeY0RuAc9popf+hyaSV6ZEg9hr4cpWF7jmo/JSWEnLDrnijS9Tog==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", @@ -5251,6 +5366,54 @@ "node": ">=8" } }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -6406,6 +6569,18 @@ } } }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/decode-named-character-reference": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", @@ -6443,6 +6618,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz", + "integrity": "sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -7458,6 +7642,34 @@ } } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", @@ -7548,6 +7760,25 @@ "node": ">=10" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -7674,6 +7905,24 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -7710,19 +7959,22 @@ "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==" }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -7744,6 +7996,30 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/global-dirs": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", @@ -8135,6 +8411,30 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.0.tgz", + "integrity": "sha512-IVGhNgg7vANuUA2XKrT6sOIIPgaYZnmLx3l/CCOAK0PtgfoHrZwX7jCSYyFxHTrGmC6S9q8aQQekjp4JPZF+cw==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^9.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", @@ -8940,6 +9240,18 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -8977,6 +9289,24 @@ "node": ">=0.10.0" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -9297,6 +9627,22 @@ "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==" }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -9317,6 +9663,15 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.0.tgz", + "integrity": "sha512-qKl+FrLXUhFuHUoDJG7f8P8gEMHq9NFS0c6ghXG1J0rldmZFQZoNVv/vyirE9qwCIhWZDsvEFd1sbFu3GvRQFg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -12126,32 +12481,267 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, "engines": { - "node": ">=4" + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" } }, - "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, "engines": { - "node": ">=10" + "node": ">=0.3.1" } }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dependencies": { + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, @@ -12622,6 +13212,31 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -12638,6 +13253,15 @@ "node": ">=8" } }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, "node_modules/periscopic": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", @@ -14028,6 +14652,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-stringify": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.0.tgz", + "integrity": "sha512-1TX1i048LooI9QoecrXy7nGFFbFSufxVRAfc6Y9YMRAi56l+oB0zP51mLSV312uRuvVLPV1opSlJmslozR1XHQ==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -14264,6 +14903,15 @@ "entities": "^2.0.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -14364,6 +15012,25 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", @@ -14802,6 +15469,25 @@ "node": ">=4" } }, + "node_modules/shelljs/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -15035,6 +15721,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -15097,6 +15804,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", @@ -16470,6 +17190,12 @@ "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==" }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -16486,6 +17212,44 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -16580,6 +17344,15 @@ "xml-js": "bin/cli.js" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -16593,6 +17366,77 @@ "node": ">= 6" } }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", diff --git a/package.json b/package.json index 9bc70ab2d..c91418d05 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,10 @@ "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" + "write-heading-ids": "docusaurus write-heading-ids", + "test": "mocha", + "writeFilesToTest": "node ./test/writeFilesToTest.mjs", + "testold": "node --experimental-vm-modules node_modules/jest/bin/jest.js" }, "dependencies": { "@docusaurus/core": "3.0.1", @@ -33,7 +36,19 @@ "slick-carousel": "1.8.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.0.1" + "@docusaurus/module-type-aliases": "3.0.1", + "chai": "^5.0.0", + "glob": "^10.3.10", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0", + "minimist": "^1.2.8", + "mocha": "^10.2.0", + "rehype-stringify": "^10.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" }, "browserslist": { "production": [ diff --git a/test/images.test.mjs b/test/images.test.mjs new file mode 100644 index 000000000..ef9c915b1 --- /dev/null +++ b/test/images.test.mjs @@ -0,0 +1,140 @@ +// Check links to images in md and mdx files, in both markdown links and and

tags + +import path, { dirname } from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs'; +import { glob } from 'glob'; +import { unified } from 'unified'; +import remarkParse from 'remark-parse'; +import remarkRehype from 'remark-rehype'; +import rehypeStringify from 'rehype-stringify'; +import { fromMarkdown } from 'mdast-util-from-markdown'; +import { mdxFromMarkdown } from 'mdast-util-mdx' +import { mdxjs } from 'micromark-extension-mdxjs' +import { visit } from 'unist-util-visit'; +import { expect } from 'chai'; +import minimist from 'minimist'; + +const argv = minimist(process.argv.slice(2)); + +import { exampleAstWithBrokenLinks, expectedImagesInAst } from './resources/imageCheckTestResources.mjs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const docsFolder = path.resolve(__dirname, '../docs'); +const imageFolder = path.resolve(__dirname, '../static'); +// Get file names passed in the command or if none were passed, use all files +const getFilePaths = async () => { + if (argv.filesToCheck) { + return argv.filesToCheck + .split(',') + .map((oneFilePath) => + path.resolve(__dirname, '../', oneFilePath) + ); + } else { + return glob(docsFolder + '/**/*.{md,mdx}'); + } +} +const filePaths = await getFilePaths(); + +const availableImagePaths = await glob(imageFolder + '/**/*.{png,jpeg,jpg,gif,svg}'); + +// https://unifiedjs.com/learn/guide/using-unified/ +const processor = unified() + .use(remarkParse) + .use(remarkRehype) + .use(rehypeStringify); + + // Test functions to select nodes that are links to images + const mdxTestFunction = (node) => ['img', 'Figure'].includes(node.name); + const markdownTestFunction = (node) => node.type === 'image'; + + // Get all of the images in an AST, visiting the correct nodes for MD and MDX files. + /* + For MDX, get Figure and img nodes, like this img node: + { + "type": "mdxJsxFlowElement", + "name": "img", + "attributes": [ + { + "type": "mdxJsxAttribute", + "name": "src", + "value": "/img/tezos_smart_contract_content.svg" + }, + { + "type": "mdxJsxAttribute", + "name": "alt", + "value": "hi" + } + ], + "children": [], + "position": {...} + }, + + For MD, get image nodes, like this: + { + "type": "image", + "title": null, + "url": "/img/someimage.jpg", + "alt": "some alt text", + "position": {...} + }, + */ + const getImagesInAst = (ast, /*filePath*/) => { + const imagePathsInFile = []; + // MDX elements + visit(ast, mdxTestFunction, (node) => { + const srcAttribute = node.attributes.find((attr => attr.name === 'src')); + imagePathsInFile.push(srcAttribute.value); + }); + // MD images + visit(ast, markdownTestFunction, (node) => { + imagePathsInFile.push(node.url); + }); + // Filter out external links to files + return imagePathsInFile.filter((oneLink) => + !oneLink.startsWith('http://') && !oneLink.startsWith('https://') + ); + } + +it('Verify that the test gets images from ASTs', () => { + const imagesFoundInAst = getImagesInAst(exampleAstWithBrokenLinks); + expectedImagesInAst.forEach(oneExpectedImage => { + expect(imagesFoundInAst, + 'Image check test failed. getImagesInAst did not find an image it should have:' + oneExpectedImage + ).to.include(oneExpectedImage); + }); +}); + +describe('Test for broken image links', async () => { + if (filePaths.length === 0) { + console.log('No files to test.'); + return; + } + filePaths.forEach((filePath) => { + it('Check image paths in ' + filePath, async () => { + // Get the AST and the links to images in that AST + const ast = await getAst(filePath); + const imagesInAst = getImagesInAst(ast, filePath); + // Find images that are missing + imagesInAst.forEach((oneImageInAst) => + expect(availableImagePaths, + 'Missing image ' + oneImageInAst + ).to.include(imageFolder + oneImageInAst) + ); + }); + }) +}) + +// Convert file to AST, using the correct processors for MD and MDX files +const getAst = async (filePath) => { + const fileContents = await fs.promises.readFile(filePath, 'utf8'); + if (path.extname(filePath) === '.mdx') { + return fromMarkdown(fileContents, { + extensions: [mdxjs()], + mdastExtensions: [mdxFromMarkdown()] + }); + } else if (path.extname(filePath) === '.md'){ + return processor.parse(fileContents); + } +} diff --git a/test/resources/imageCheckTestResources.mjs b/test/resources/imageCheckTestResources.mjs new file mode 100644 index 000000000..453054aea --- /dev/null +++ b/test/resources/imageCheckTestResources.mjs @@ -0,0 +1,467 @@ +// Test resources for imageCheck.mjs + +/* +Generated this AST from this file: +--- +title: hi +--- + +import Figure from '@site/src/components/Figure'; + +
+ +Entering information for the new RPC node + +![UI for Better Call Dev](/img/qwerty.png) +*/ + +export const expectedImagesInAst = [ + "/img/abcde.svg", + "/img/fghij.png", + "/img/qwerty.png", +]; + +export const exampleAstWithBrokenLinks = { + type: "root", + children: [ + { + type: "thematicBreak", + position: { + start: { + line: 1, + column: 1, + offset: 0, + }, + end: { + line: 1, + column: 4, + offset: 3, + }, + }, + }, + { + type: "heading", + depth: 2, + children: [ + { + type: "text", + value: "title: hi", + position: { + start: { + line: 2, + column: 1, + offset: 4, + }, + end: { + line: 2, + column: 10, + offset: 13, + }, + }, + }, + ], + position: { + start: { + line: 2, + column: 1, + offset: 4, + }, + end: { + line: 3, + column: 4, + offset: 17, + }, + }, + }, + { + type: "mdxjsEsm", + value: "import Figure from '@site/src/components/Figure';", + position: { + start: { + line: 5, + column: 1, + offset: 19, + }, + end: { + line: 5, + column: 50, + offset: 68, + }, + }, + data: { + estree: { + type: "Program", + start: 19, + end: 68, + loc: { + start: { + line: 5, + column: 0, + offset: 19, + }, + end: { + line: 5, + column: 49, + offset: 68, + }, + }, + body: [ + { + type: "ImportDeclaration", + start: 19, + end: 68, + loc: { + start: { + line: 5, + column: 0, + offset: 19, + }, + end: { + line: 5, + column: 49, + offset: 68, + }, + }, + specifiers: [ + { + type: "ImportDefaultSpecifier", + start: 26, + end: 32, + loc: { + start: { + line: 5, + column: 7, + offset: 26, + }, + end: { + line: 5, + column: 13, + offset: 32, + }, + }, + local: { + type: "Identifier", + start: 26, + end: 32, + loc: { + start: { + line: 5, + column: 7, + offset: 26, + }, + end: { + line: 5, + column: 13, + offset: 32, + }, + }, + name: "Figure", + range: [ + 26, + 32, + ], + }, + range: [ + 26, + 32, + ], + }, + ], + source: { + type: "Literal", + start: 38, + end: 67, + loc: { + start: { + line: 5, + column: 19, + offset: 38, + }, + end: { + line: 5, + column: 48, + offset: 67, + }, + }, + value: "@site/src/components/Figure", + raw: "'@site/src/components/Figure'", + range: [ + 38, + 67, + ], + }, + range: [ + 19, + 68, + ], + }, + ], + sourceType: "module", + comments: [ + ], + range: [ + 19, + 68, + ], + }, + }, + }, + { + type: "mdxJsxFlowElement", + name: "Figure", + attributes: [ + { + type: "mdxJsxAttribute", + name: "src", + value: "/img/abcde.svg", + }, + { + type: "mdxJsxAttribute", + name: "caption", + value: "figure with broken image link", + }, + ], + children: [ + ], + position: { + start: { + line: 7, + column: 1, + offset: 70, + }, + end: { + line: 7, + column: 72, + offset: 141, + }, + }, + }, + { + type: "mdxJsxFlowElement", + name: "img", + attributes: [ + { + type: "mdxJsxAttribute", + name: "src", + value: "/img/fghij.png", + }, + { + type: "mdxJsxAttribute", + name: "alt", + value: "Entering information for the new RPC node", + }, + { + type: "mdxJsxAttribute", + name: "style", + value: { + type: "mdxJsxAttributeValueExpression", + value: "{width: 300}", + data: { + estree: { + type: "Program", + start: 224, + end: 236, + body: [ + { + type: "ExpressionStatement", + expression: { + type: "ObjectExpression", + start: 224, + end: 236, + loc: { + start: { + line: 9, + column: 81, + offset: 224, + }, + end: { + line: 9, + column: 93, + offset: 236, + }, + }, + properties: [ + { + type: "Property", + start: 225, + end: 235, + loc: { + start: { + line: 9, + column: 82, + offset: 225, + }, + end: { + line: 9, + column: 92, + offset: 235, + }, + }, + method: false, + shorthand: false, + computed: false, + key: { + type: "Identifier", + start: 225, + end: 230, + loc: { + start: { + line: 9, + column: 82, + offset: 225, + }, + end: { + line: 9, + column: 87, + offset: 230, + }, + }, + name: "width", + range: [ + 225, + 230, + ], + }, + value: { + type: "Literal", + start: 232, + end: 235, + loc: { + start: { + line: 9, + column: 89, + offset: 232, + }, + end: { + line: 9, + column: 92, + offset: 235, + }, + }, + value: 300, + raw: "300", + range: [ + 232, + 235, + ], + }, + kind: "init", + range: [ + 225, + 235, + ], + }, + ], + range: [ + 224, + 236, + ], + }, + start: 224, + end: 236, + loc: { + start: { + line: 9, + column: 81, + offset: 224, + }, + end: { + line: 9, + column: 93, + offset: 236, + }, + }, + range: [ + 224, + 236, + ], + }, + ], + sourceType: "module", + comments: [ + ], + loc: { + start: { + line: 9, + column: 81, + offset: 224, + }, + end: { + line: 9, + column: 93, + offset: 236, + }, + }, + range: [ + 224, + 236, + ], + }, + }, + }, + }, + ], + children: [ + ], + position: { + start: { + line: 9, + column: 1, + offset: 143, + }, + end: { + line: 9, + column: 98, + offset: 240, + }, + }, + }, + { + type: "paragraph", + children: [ + { + type: "image", + title: null, + url: "/img/qwerty.png", + alt: "UI for Better Call Dev", + position: { + start: { + line: 11, + column: 1, + offset: 242, + }, + end: { + line: 11, + column: 43, + offset: 284, + }, + }, + }, + ], + position: { + start: { + line: 11, + column: 1, + offset: 242, + }, + end: { + line: 11, + column: 43, + offset: 284, + }, + }, + }, + ], + position: { + start: { + line: 1, + column: 1, + offset: 0, + }, + end: { + line: 11, + column: 43, + offset: 284, + }, + }, +}; From 595b903ce3740265bdb6907f0e71f4ecc98eb7cf Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Tue, 9 Jan 2024 08:40:35 -0500 Subject: [PATCH 05/11] Update nft-taquito tutorial to use contract without main (#226) * Now that it uses separate entrypoint functions, this type is not used * Show an entrypoint function instead of the main function * Got to clear the module name field now for some reason * clarify decimals * frontend application typo * 5 tez is enough * For consistency, use snake case in cameLIGO --- docs/tutorials/create-an-nft/nft-taquito.md | 70 ++++++--------------- 1 file changed, 18 insertions(+), 52 deletions(-) diff --git a/docs/tutorials/create-an-nft/nft-taquito.md b/docs/tutorials/create-an-nft/nft-taquito.md index 16db3cf27..7aad73729 100644 --- a/docs/tutorials/create-an-nft/nft-taquito.md +++ b/docs/tutorials/create-an-nft/nft-taquito.md @@ -2,7 +2,7 @@ title: Create a contract and web app that mints NFTs authors: 'Sol Lederer, Tim McMackin' last_update: - date: 20 September 2023 + date: 5 January 2024 --- This tutorial covers how to set up a decentralized web application (dApp) that allows users to create NFTs on Tezos. @@ -165,18 +165,6 @@ type transfer = } ``` -The type `fa2_entry_points` is a special type that the contract's `main` function uses to define the entrypoints. -It maps the entry points to the type of parameter that they accept: - -```ocaml -type fa2_entry_points = - | Transfer of transfer list - | Balance_of of balance_of_param - | Update_operators of update_operator list - | Mint of mint_params - | Burn of token_id -``` - ### Error messages The contract defines a series of error messages, and comments in the code describe what each error message means. @@ -210,46 +198,16 @@ let get_balance (p, ledger : balance_of_param * ledger) : operation = Tezos.transaction responses 0mutez p.callback ``` -### Main function +### Entrypoint functions -The `main` function is a special function that defines the entrypoints in the contract. -In this case, it accepts the entrypoint that the transaction sender called and the current state of the contract's storage. -Then the function branches based on the entrypoint. - -For example, if the sender calls the `balance_of` entrypoint, the function calls the `get_balance` function and passes the parameters that the sender passed and the current state of the contract's ledger: +Each entrypoint in the contract is a function with the `@entry` annotation. +For example, the `mint` entrypoint calls an internal function to handle minting tokens based on the parameters that are defined in the `mint_params` type: ```ocaml - | Balance_of p -> - let op = get_balance (p, storage.ledger) in - [op], storage -``` - -Here is the complete code of the `main` function: - -```ocaml -let main (param, storage : fa2_entry_points * nft_token_storage) - : (operation list) * nft_token_storage = - match param with - | Transfer txs -> - let (new_ledger, new_reverse_ledger) = transfer - (txs, default_operator_validator, storage.operators, storage.ledger, storage.reverse_ledger) in - let new_storage = { storage with ledger = new_ledger; reverse_ledger = new_reverse_ledger } in - ([] : operation list), new_storage - - | Balance_of p -> - let op = get_balance (p, storage.ledger) in - [op], storage - - | Update_operators updates -> - let new_ops = fa2_update_operators (updates, storage.operators) in - let new_storage = { storage with operators = new_ops; } in - ([] : operation list), new_storage - - | Mint p -> +(** Mint NFT entrypoint *) +[@entry] +let mint (p : mint_params) (storage : nft_token_storage) : return_value = ([]: operation list), mint (p, storage) - - | Burn p -> - ([]: operation list), burn (p, storage) ``` ### Initial storage state @@ -322,6 +280,14 @@ The IDE opens a blank contract file and shows commands for the file on the left- 1. Paste the contents of the `contract/NFTS_contract.mligo` file into the editor. The IDE saves the file automatically. +1. Remove the module name: + + 1. Click **Project Settings**. + + 1. Clear the **Module name (optional)** field. + + 1. Close the Project Settings window. + 1. Click **Compile** and then in the "Compile" window, click **Compile**. The IDE compiles the contract code to Michelson, the base language that all Tezos contracts use. At the bottom of the window, it prints the message `wrote output to .workspaces/NFT tutorial/build/contracts/Contract.tz`. @@ -452,7 +418,7 @@ In these steps, you configure the backend application with your Pinata informati ``` This metadata object includes several standard fields for Tezos tokens, including a name, description, symbol to show in wallets, and URIs to preview pictures and thumbnails. - The `decimals` field is set to 0 because the NFT cannot be divided into decimal amounts like fungible tokens can. + The `decimals` field is set to 0 because there is only one of each NFT. If both of the pins were successful, the endpoint returns the IPFS URIs of the image and metadata to the frontend application: @@ -476,7 +442,7 @@ In these steps, you configure the backend application with your Pinata informati ## Run the frontend application -The front application accepts information about a new NFT from the user, sends the image and metadata to the backend, and calls the smart contract to mint the NFT. +The frontend application accepts information about a new NFT from the user, sends the image and metadata to the backend, and calls the smart contract to mint the NFT. Follow these steps to configure and start the frontend application: 1. In your command-line window, go to the `frontend` folder of the tutorial application. @@ -643,7 +609,7 @@ To test the application, you must have a Tezos wallet and a small amount of XTZ 1. Put the wallet account address in the **Or fund any address** field. - 1. Click the button to request 100 tokens and wait for the browser to complete the operation. + 1. Click the button to request 5 tez and wait for the browser to complete the operation. 1. When you see a message that the tokens are sent to your address, open your wallet and verify that the tokens are there. It can take a few minutes for them to appear. From e1709814fa2ee5a96a049282cdd79f6004ecd0e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 08:44:18 -0500 Subject: [PATCH 06/11] Bump follow-redirects from 1.15.3 to 1.15.4 (#254) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c544a6094..037d12193 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7624,9 +7624,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", From 47072314e9a69752d1b6ed4e9d1968dff5a655b4 Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Tue, 9 Jan 2024 09:07:28 -0500 Subject: [PATCH 07/11] Remove unused scripts (#255) --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index c91418d05..695725b0d 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,7 @@ "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", - "test": "mocha", - "writeFilesToTest": "node ./test/writeFilesToTest.mjs", - "testold": "node --experimental-vm-modules node_modules/jest/bin/jest.js" + "test": "mocha" }, "dependencies": { "@docusaurus/core": "3.0.1", From c24b2522a84592f5d411763fa54456d646392016 Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Tue, 9 Jan 2024 11:47:36 -0500 Subject: [PATCH 08/11] remove tests from staging builds (#256) --- .github/workflows/deploy-staging.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 2f540f9ee..fb98bb586 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -18,9 +18,6 @@ jobs: with: node-version: 18 - run: npm ci - - id: files - uses: jitterbit/get-changed-files@v1 - - run: npm run test -- --filesToCheck=${{ steps.files.outputs.added_modified }} - run: npm run build - uses: aws-actions/configure-aws-credentials@v2 with: From 0fedff7ea864a15653f994639716c649c16836fb Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Thu, 11 Jan 2024 08:26:38 -0500 Subject: [PATCH 09/11] Docs on events (#223) * First draft of a topic on events * How to convert the bytes to an address * Be cler about where the data can go * trim for clarity, because send implies a direct connection * getting info about the event operation --- docs/smart-contracts/entrypoints.md | 7 +- docs/smart-contracts/events.md | 161 ++++++++++++++++++++++++++++ sidebars.js | 1 + 3 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 docs/smart-contracts/events.md diff --git a/docs/smart-contracts/entrypoints.md b/docs/smart-contracts/entrypoints.md index 8a1c5495c..3fa15af5a 100644 --- a/docs/smart-contracts/entrypoints.md +++ b/docs/smart-contracts/entrypoints.md @@ -2,7 +2,7 @@ title: Entrypoints authors: 'Mathias Hiron (Nomadic Labs), Sasha Aldrick (TriliTech), Tim McMackin (TriliTech)' last_update: - date: 26 December 2023 + date: 10 January 2024 --- The entrypoints of a contract represent the different ways that it can be called, similar to a method or function in many programming languages or an endpoint of an API. @@ -13,9 +13,10 @@ The entrypoints in a Tezos smart contract must meet these specifications: - Entrypoints may accept parameters, which can be of almost any data type that Tezos supports. Unlike functions and API endpoints, entrypoints do not return a value directly to the caller. -To return a value from a smart contract, you can use one of these methods: +To return data from a smart contract, you can use one of these methods: -- Use [Views](./views) +- Use [Views](./views) to return data to smart contracts or off-chain applications +- Use [Events](./events) to return data to off-chain applications - Include a callback parameter that sends information to another smart contract, as in the `getAllowance`, `getBalance`, and `getTotalSupply` entrypoints of [FA1.2](../architecture/tokens/FA1.2) contracts For an example of a simple contract, see the tutorial [Create a smart contract](../tutorials/smart-contract). diff --git a/docs/smart-contracts/events.md b/docs/smart-contracts/events.md new file mode 100644 index 000000000..4619a8a51 --- /dev/null +++ b/docs/smart-contracts/events.md @@ -0,0 +1,161 @@ +--- +title: Events +authors: Tim McMackin +last_update: + date: 10 January 2024 +--- + +Events are a type of internal operation on Tezos. +Smart contracts emit events and off-chain applications can listen for events to know when things happen on the chain. + +## Event data + +An event includes data about the call to the smart contract that triggered the event, including the hash of that operation and the level of the block that included the operation. + +The event can also include these optional fields: + +- A tag that can identify the type of event or help clients filter the stream of events. +- A payload of data in Michelson format +- The Michelson data type of the payload + +## Emitting events + +Each high-level language has its own way of creating events. +The compiled Michelson code uses the `EMIT` command to emit the event. + +For example, this SmartPy contract stores a number and emits events when that amount changes: + +```python +import smartpy as sp + +@sp.module +def main(): + class Events(sp.Contract): + def __init__(self, value): + self.data.storedValue = value + + @sp.entrypoint + def add(self, addAmount): + sp.emit(sp.record( + source=sp.source, + addAmount=addAmount + ), tag="add", with_type=True) + self.data.storedValue += addAmount + + @sp.entrypoint + def reset(self): + sp.emit(sp.record( + source=sp.source, + previousValue=self.data.storedValue + ), tag="reset", with_type=True) + self.data.storedValue = 0 + +if "templates" not in __name__: + + @sp.add_test(name="Events") + def test(): + c1 = main.Events(12) + scenario = sp.test_scenario(main) + scenario.h1("Add") + scenario += c1 + c1.add(2).run( + sender = sp.test_account("Alice") + ) + scenario.verify(c1.data.storedValue == 14) +``` + +When a client calls the `reset` entrypoint, it emits an event that is tagged with "reset" and includes the address that called the entrypoint and the amount that the storage was before it was reset to 0. + +## Responding to events + +Smart contracts cannot respond to events. + +Off-chain applications can listen for and respond to events by monitoring the event operations in blocks. + +For example, Taquito includes tools to listen for and respond to events. +For example, this code listens for events from the contract with the address `contractAddress` and the tag `tagName`: + +```javascript +const Tezos = new TezosToolkit(rpcUrl); + +Tezos.setStreamProvider( + Tezos.getFactory(PollingSubscribeProvider)({ + shouldObservableSubscriptionRetry: true, + pollingIntervalMilliseconds: 1500, + }), +); + +try { + const sub = Tezos.stream.subscribeEvent({ + tag: tagName, + address: contractAddress, + }); + + sub.on("data", console.log); +} catch (e) { + console.log(e); +} +``` + +Both the `tag` and `address` parameters are optional in the `subscribeEvent` function, but most clients are interested in events from a specific contract address or tag. + +The event data is in Michelson format, so an event from the `reset` entrypoint of the previous example contract might look like this: + +```json +{ + "opHash": "onw8EwWVnZbx2yBHhL72ECRdCPBbw7z1d5hVCJxp7vzihVELM2m", + "blockHash": "BM1avumf2rXSFYKf4JS7YJePAL3gutRJwmazvqcSAoaqVBPAmTf", + "level": 4908983, + "kind": "event", + "source": "KT1AJ6EjaJHmH6WiExCGc3PgHo3JB5hBMhEx", + "nonce": 0, + "type": { + "prim": "pair", + "args": [ + { + "prim": "int", + "annots": [ + "%previousValue" + ] + }, + { + "prim": "address", + "annots": [ + "%source" + ] + } + ] + }, + "tag": "reset", + "payload": { + "prim": "Pair", + "args": [ + { + "int": "17" + }, + { + "bytes": "000032041dca76bac940b478aae673e362bd15847ed8" + } + ] + }, + "result": { + "status": "applied", + "consumed_milligas": "100000" + } +} +``` + +Note that the address field is returned as a byte value. +To convert the bytes to an address, use the `encodePubKey` function in `@taquito/utils`. + + +You can see the complete content of the event operation by looking up the operation hash in a block explorer. +For example, to see the operation in the previous example, look up the operation `onw8EwWVnZbx2yBHhL72ECRdCPBbw7z1d5hVCJxp7vzihVELM2m`. + +## Implementation details + +- Michelson: [Contract events](https://tezos.gitlab.io/alpha/event.html) +- LIGO: [Events](https://ligolang.org/docs/contract/events) +- SmartPy: [`sp.emit`](https://smartpy.io/manual/syntax/operations#sp.emit) +- Archetype: [Events](https://archetype-lang.org/blog/events/#event) +- Taquito: [Contract Events](https://tezostaquito.io/docs/subscribe_event) diff --git a/sidebars.js b/sidebars.js index 6214b3051..571be4fb2 100644 --- a/sidebars.js +++ b/sidebars.js @@ -167,6 +167,7 @@ const sidebars = { 'smart-contracts/serialization', 'smart-contracts/sapling', 'smart-contracts/views', + 'smart-contracts/events', 'smart-contracts/delegation', // 'smart-contracts/multisig-specialized', // 'smart-contracts/multisig-usage', From d23c40e97ccf92d3cc82a80bbffe520e1a1125a6 Mon Sep 17 00:00:00 2001 From: benjamin fuentes Date: Thu, 11 Jan 2024 20:14:04 +0100 Subject: [PATCH 10/11] Mobile-tutorial (#214) * fist commit to prepare the rest of the tutorial * version ready to be reviewed * fix broken link * fix typo * Added remarks from Tim * Grammar and style for parts 1 and 2 * wip grammar and style for parts 3 and 4 * Grammar and punctuation for parts 3 and 4 * Remove redundant Tezos from title * adding company name * adding author --------- Co-authored-by: Tim McMackin --- docs/tutorials.mdx | 12 +- docs/tutorials/build-an-nft-marketplace.md | 1 + .../build-an-nft-marketplace/part-1.md | 135 +- .../build-an-nft-marketplace/part-2.md | 177 +-- .../build-an-nft-marketplace/part-3.md | 359 ++--- .../build-an-nft-marketplace/part-4.md | 359 ++--- docs/tutorials/dapp.md | 2 +- docs/tutorials/dapp/part-1.md | 90 +- docs/tutorials/dapp/part-2.md | 2 +- docs/tutorials/dapp/part-3.md | 64 +- docs/tutorials/dapp/part-4.md | 92 +- docs/tutorials/mobile.md | 54 + docs/tutorials/mobile/part-1.md | 125 ++ docs/tutorials/mobile/part-2.md | 1153 +++++++++++++++++ docs/tutorials/mobile/part-3.md | 829 ++++++++++++ docs/tutorials/mobile/part-4.md | 134 ++ sidebars.js | 14 + static/img/tutorials/mobile-alice.png | Bin 0 -> 247404 bytes static/img/tutorials/mobile-build.png | Bin 0 -> 231941 bytes static/img/tutorials/mobile-home.png | Bin 0 -> 67111 bytes static/img/tutorials/mobile-kukai.png | Bin 0 -> 73764 bytes static/img/tutorials/mobile-picHOME.png | Bin 0 -> 86021 bytes static/img/tutorials/mobile-run.png | Bin 0 -> 130174 bytes static/img/tutorials/mobile-sign.png | Bin 0 -> 9814 bytes 24 files changed, 2962 insertions(+), 640 deletions(-) create mode 100644 docs/tutorials/mobile.md create mode 100644 docs/tutorials/mobile/part-1.md create mode 100644 docs/tutorials/mobile/part-2.md create mode 100644 docs/tutorials/mobile/part-3.md create mode 100644 docs/tutorials/mobile/part-4.md create mode 100644 static/img/tutorials/mobile-alice.png create mode 100644 static/img/tutorials/mobile-build.png create mode 100644 static/img/tutorials/mobile-home.png create mode 100644 static/img/tutorials/mobile-kukai.png create mode 100644 static/img/tutorials/mobile-picHOME.png create mode 100644 static/img/tutorials/mobile-run.png create mode 100644 static/img/tutorials/mobile-sign.png diff --git a/docs/tutorials.mdx b/docs/tutorials.mdx index ba661e719..8149c111a 100644 --- a/docs/tutorials.mdx +++ b/docs/tutorials.mdx @@ -4,8 +4,8 @@ title: Tutorials These tutorials can help you get started developing different kinds of applications on Tezos in as little as 15 minutes. -import TutorialCard from "@site/src/components/TutorialCard"; -import TutorialCardContainer from "@site/src/components/TutorialCardContainer"; +import TutorialCard from '@site/src/components/TutorialCard'; +import TutorialCardContainer from '@site/src/components/TutorialCardContainer'; ## Beginner @@ -109,4 +109,12 @@ These tutorials are intended for developers who are familiar with Tezos and want link="Start tutorial" /> + + diff --git a/docs/tutorials/build-an-nft-marketplace.md b/docs/tutorials/build-an-nft-marketplace.md index f9375839c..8241e96fa 100644 --- a/docs/tutorials/build-an-nft-marketplace.md +++ b/docs/tutorials/build-an-nft-marketplace.md @@ -1,5 +1,6 @@ --- title: Build an NFT marketplace +authors: 'Benjamin Fuentes (Marigold)' lastUpdated: 8nd November 2023 --- diff --git a/docs/tutorials/build-an-nft-marketplace/part-1.md b/docs/tutorials/build-an-nft-marketplace/part-1.md index 12e3b940a..418247299 100644 --- a/docs/tutorials/build-an-nft-marketplace/part-1.md +++ b/docs/tutorials/build-an-nft-marketplace/part-1.md @@ -1,5 +1,6 @@ --- -title: "Part 1: Minting tokens" +title: 'Part 1: Minting tokens' +authors: 'Benjamin Fuentes (Marigold)' lastUpdated: 8th November 2023 --- @@ -564,17 +565,17 @@ The mint page uses a form that accepts information and an image and sends a tran ```typescript const validationSchema = yup.object({ - name: yup.string().required("Name is required"), - description: yup.string().required("Description is required"), - symbol: yup.string().required("Symbol is required"), + name: yup.string().required('Name is required'), + description: yup.string().required('Description is required'), + symbol: yup.string().required('Symbol is required'), }); const formik = useFormik({ initialValues: { - name: "", - description: "", + name: '', + description: '', token_id: 0, - symbol: "WINE", + symbol: 'WINE', } as TZIP21TokenMetadata, validationSchema: validationSchema, onSubmit: (values) => { @@ -586,7 +587,7 @@ The mint page uses a form that accepts information and an image and sends a tran 1. After this code, add state variables for the image and its URL: ```typescript - const [pictureUrl, setPictureUrl] = useState(""); + const [pictureUrl, setPictureUrl] = useState(''); const [file, setFile] = useState(null); ``` @@ -605,9 +606,9 @@ The mint page uses a form that accepts information and an image and sends a tran const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => { if ( - event.type === "keydown" && - ((event as React.KeyboardEvent).key === "Tab" || - (event as React.KeyboardEvent).key === "Shift") + event.type === 'keydown' && + ((event as React.KeyboardEvent).key === 'Tab' || + (event as React.KeyboardEvent).key === 'Shift') ) { return; } @@ -625,29 +626,29 @@ The mint page uses a form that accepts information and an image and sends a tran //IPFS if (file) { const formData = new FormData(); - formData.append("file", file); + formData.append('file', file); const requestHeaders: HeadersInit = new Headers(); requestHeaders.set( - "pinata_api_key", + 'pinata_api_key', `${import.meta.env.VITE_PINATA_API_KEY}` ); requestHeaders.set( - "pinata_secret_api_key", + 'pinata_secret_api_key', `${import.meta.env.VITE_PINATA_API_SECRET}` ); const resFile = await fetch( - "https://api.pinata.cloud/pinning/pinFileToIPFS", + 'https://api.pinata.cloud/pinning/pinFileToIPFS', { - method: "post", + method: 'post', body: formData, headers: requestHeaders, } ); const responseJson = await resFile.json(); - console.log("responseJson", responseJson); + console.log('responseJson', responseJson); const thumbnailUri = `ipfs://${responseJson.IpfsHash}`; setPictureUrl( @@ -667,13 +668,13 @@ The mint page uses a form that accepts information and an image and sends a tran //close directly the form setFormOpen(false); enqueueSnackbar( - "Wine collection is minting ... it will be ready on next block, wait for the confirmation message before minting another collection", - { variant: "info" } + 'Wine collection is minting ... it will be ready on next block, wait for the confirmation message before minting another collection', + { variant: 'info' } ); await op.confirmation(2); - enqueueSnackbar("Wine collection minted", { variant: "success" }); + enqueueSnackbar('Wine collection minted', { variant: 'success' }); refreshUserContextOnPageReload(); //force all app to refresh the context } @@ -682,7 +683,7 @@ The mint page uses a form that accepts information and an image and sends a tran let tibe: TransactionInvalidBeaconError = new TransactionInvalidBeaconError(error); enqueueSnackbar(tibe.data_message, { - variant: "error", + variant: 'error', autoHideDuration: 10000, }); } @@ -700,7 +701,7 @@ The mint page uses a form that accepts information and an image and sends a tran useEffect(() => { (async () => { if (nftContratTokenMetadataMap && nftContratTokenMetadataMap.size > 0) { - formik.setFieldValue("token_id", nftContratTokenMetadataMap.size); + formik.setFieldValue('token_id', nftContratTokenMetadataMap.size); } })(); }, [nftContratTokenMetadataMap?.size]); @@ -709,8 +710,8 @@ The mint page uses a form that accepts information and an image and sends a tran 1. Replace the imports at the top of the file with these imports: ```typescript - import { AddCircleOutlined, Close } from "@mui/icons-material"; - import OpenWithIcon from "@mui/icons-material/OpenWith"; + import { AddCircleOutlined, Close } from '@mui/icons-material'; + import OpenWithIcon from '@mui/icons-material/OpenWith'; import { Box, Button, @@ -719,18 +720,18 @@ The mint page uses a form that accepts information and an image and sends a tran TextField, Toolbar, useMediaQuery, - } from "@mui/material"; - import Paper from "@mui/material/Paper"; - import Typography from "@mui/material/Typography"; - import { useFormik } from "formik"; - import React, { useEffect, useState } from "react"; - import * as yup from "yup"; - import { TZIP21TokenMetadata, UserContext, UserContextType } from "./App"; - import { useSnackbar } from "notistack"; - import { BigNumber } from "bignumber.js"; - import { address, bytes, nat } from "./type-aliases"; - import { char2Bytes } from "@taquito/utils"; - import { TransactionInvalidBeaconError } from "./TransactionInvalidBeaconError"; + } from '@mui/material'; + import Paper from '@mui/material/Paper'; + import Typography from '@mui/material/Typography'; + import { useFormik } from 'formik'; + import React, { useEffect, useState } from 'react'; + import * as yup from 'yup'; + import { TZIP21TokenMetadata, UserContext, UserContextType } from './App'; + import { useSnackbar } from 'notistack'; + import { BigNumber } from 'bignumber.js'; + import { address, bytes, nat } from './type-aliases'; + import { char2Bytes } from '@taquito/utils'; + import { TransactionInvalidBeaconError } from './TransactionInvalidBeaconError'; ``` 1. Save the file. @@ -790,7 +791,7 @@ Follow these steps to show the tokens that you have minted: 1. In the `MintPage.tsx` file, replace the `"//TODO"` comment with this code: ```typescript - + ( @@ -818,24 +819,24 @@ Follow these steps to show the tokens that you have minted: sx={ isTablet ? { - width: "auto", - marginLeft: "33%", - maxHeight: "50vh", + width: 'auto', + marginLeft: '33%', + maxHeight: '50vh', } - : { width: "100%", maxHeight: "40vh" } + : { width: '100%', maxHeight: '40vh' } } component="img" image={token.thumbnailUri?.replace( - "ipfs://", - "https://gateway.pinata.cloud/ipfs/" + 'ipfs://', + 'https://gateway.pinata.cloud/ipfs/' )} /> - {"ID : " + token_id} - {"Symbol : " + token.symbol} - {"Description : " + token.description} + {'ID : ' + token_id} + {'Symbol : ' + token.symbol} + {'Description : ' + token.description} @@ -893,8 +894,8 @@ Follow these steps to show the tokens that you have minted: 1. Replace the imports at the top of the file with these imports: ```typescript - import SwipeableViews from "react-swipeable-views"; - import OpenWithIcon from "@mui/icons-material/OpenWith"; + import SwipeableViews from 'react-swipeable-views'; + import OpenWithIcon from '@mui/icons-material/OpenWith'; import { Box, Button, @@ -906,26 +907,26 @@ Follow these steps to show the tokens that you have minted: TextField, Toolbar, useMediaQuery, - } from "@mui/material"; - import Card from "@mui/material/Card"; - import CardContent from "@mui/material/CardContent"; + } from '@mui/material'; + import Card from '@mui/material/Card'; + import CardContent from '@mui/material/CardContent'; import { AddCircleOutlined, Close, KeyboardArrowLeft, KeyboardArrowRight, - } from "@mui/icons-material"; - import Paper from "@mui/material/Paper"; - import Typography from "@mui/material/Typography"; - import { useFormik } from "formik"; - import React, { useEffect, useState } from "react"; - import * as yup from "yup"; - import { TZIP21TokenMetadata, UserContext, UserContextType } from "./App"; - import { useSnackbar } from "notistack"; - import { BigNumber } from "bignumber.js"; - import { address, bytes, nat } from "./type-aliases"; - import { char2Bytes } from "@taquito/utils"; - import { TransactionInvalidBeaconError } from "./TransactionInvalidBeaconError"; + } from '@mui/icons-material'; + import Paper from '@mui/material/Paper'; + import Typography from '@mui/material/Typography'; + import { useFormik } from 'formik'; + import React, { useEffect, useState } from 'react'; + import * as yup from 'yup'; + import { TZIP21TokenMetadata, UserContext, UserContextType } from './App'; + import { useSnackbar } from 'notistack'; + import { BigNumber } from 'bignumber.js'; + import { address, bytes, nat } from './type-aliases'; + import { char2Bytes } from '@taquito/utils'; + import { TransactionInvalidBeaconError } from './TransactionInvalidBeaconError'; ``` 1. Open the web page in the browser again and see that the NFT you created is shown, as in this picture: diff --git a/docs/tutorials/build-an-nft-marketplace/part-2.md b/docs/tutorials/build-an-nft-marketplace/part-2.md index 8ba3a96e1..33f7501bc 100644 --- a/docs/tutorials/build-an-nft-marketplace/part-2.md +++ b/docs/tutorials/build-an-nft-marketplace/part-2.md @@ -1,5 +1,6 @@ --- -title: "Part 2: Buying and selling tokens" +title: 'Part 2: Buying and selling tokens' +authors: 'Benjamin Fuentes (Marigold)' last_update: date: 8 November 2023 --- @@ -218,10 +219,10 @@ The contract storage must store the tokens that are offered for sale and their p 1. Open the sale page in the `./src/OffersPage.tsx` file and replace it with this code: ```typescript - import { InfoOutlined } from "@mui/icons-material"; - import SellIcon from "@mui/icons-material/Sell"; + import { InfoOutlined } from '@mui/icons-material'; + import SellIcon from '@mui/icons-material/Sell'; - import * as api from "@tzkt/sdk-api"; + import * as api from '@tzkt/sdk-api'; import { Box, @@ -238,25 +239,25 @@ The contract storage must store the tokens that are offered for sale and their p Tooltip, Typography, useMediaQuery, - } from "@mui/material"; - import Paper from "@mui/material/Paper"; - import BigNumber from "bignumber.js"; - import { useFormik } from "formik"; - import { useSnackbar } from "notistack"; - import React, { Fragment, useEffect, useState } from "react"; - import * as yup from "yup"; - import { UserContext, UserContextType } from "./App"; - import ConnectButton from "./ConnectWallet"; - import { TransactionInvalidBeaconError } from "./TransactionInvalidBeaconError"; - import { address, nat } from "./type-aliases"; + } from '@mui/material'; + import Paper from '@mui/material/Paper'; + import BigNumber from 'bignumber.js'; + import { useFormik } from 'formik'; + import { useSnackbar } from 'notistack'; + import React, { Fragment, useEffect, useState } from 'react'; + import * as yup from 'yup'; + import { UserContext, UserContextType } from './App'; + import ConnectButton from './ConnectWallet'; + import { TransactionInvalidBeaconError } from './TransactionInvalidBeaconError'; + import { address, nat } from './type-aliases'; const itemPerPage: number = 6; const validationSchema = yup.object({ price: yup .number() - .required("Price is required") - .positive("ERROR: The number must be greater than 0!"), + .required('Price is required') + .positive('ERROR: The number must be greater than 0!'), }); type Offer = { @@ -265,7 +266,7 @@ The contract storage must store the tokens that are offered for sale and their p }; export default function OffersPage() { - api.defaults.baseUrl = "https://api.ghostnet.tzkt.io"; + api.defaults.baseUrl = 'https://api.ghostnet.tzkt.io'; const [selectedTokenId, setSelectedTokenId] = React.useState(0); const [currentPageIndex, setCurrentPageIndex] = useState(1); @@ -297,14 +298,14 @@ The contract storage must store the tokens that are offered for sale and their p }, validationSchema: validationSchema, onSubmit: (values) => { - console.log("onSubmit: (values)", values, selectedTokenId); + console.log('onSubmit: (values)', values, selectedTokenId); sell(selectedTokenId, values.price); }, }); const initPage = async () => { if (storage) { - console.log("context is not empty, init page now"); + console.log('context is not empty, init page now'); ownerTokenIds = new Set(); offersTokenIDMap = new Map(); @@ -313,7 +314,7 @@ The contract storage must store the tokens that are offered for sale and their p ).id.toNumber(); const token_ids = await api.bigMapsGetKeys(token_metadataBigMapId, { - micheline: "Json", + micheline: 'Json', active: true, }); @@ -330,35 +331,35 @@ The contract storage must store the tokens that are offered for sale and their p offersTokenIDMap.set(token_idKey.key, ownerOffers); console.log( - "found for " + + 'found for ' + owner + - " on token_id " + + ' on token_id ' + token_idKey.key + - " with balance " + + ' with balance ' + 1 ); } else { - console.log("skip to next token id"); + console.log('skip to next token id'); } }) ); setOwnerTokenIds(new Set(ownerTokenIds)); //force refresh setOffersTokenIDMap(new Map(offersTokenIDMap)); //force refresh } else { - console.log("context is empty, wait for parent and retry ..."); + console.log('context is empty, wait for parent and retry ...'); } }; useEffect(() => { (async () => { - console.log("after a storage changed"); + console.log('after a storage changed'); await initPage(); })(); }, [storage]); useEffect(() => { (async () => { - console.log("on Page init"); + console.log('on Page init'); await initPage(); })(); }, []); @@ -375,14 +376,14 @@ The contract storage must store the tokens that are offered for sale and their p await op?.confirmation(2); enqueueSnackbar( - "Wine collection (token_id=" + + 'Wine collection (token_id=' + token_id + - ") offer for " + + ') offer for ' + 1 + - " units at price of " + + ' units at price of ' + price + - " XTZ", - { variant: "success" } + ' XTZ', + { variant: 'success' } ); refreshUserContextOnPageReload(); //force all app to refresh the context @@ -391,18 +392,18 @@ The contract storage must store the tokens that are offered for sale and their p let tibe: TransactionInvalidBeaconError = new TransactionInvalidBeaconError(error); enqueueSnackbar(tibe.data_message, { - variant: "error", + variant: 'error', autoHideDuration: 10000, }); } }; - const isDesktop = useMediaQuery("(min-width:1100px)"); - const isTablet = useMediaQuery("(min-width:600px)"); + const isDesktop = useMediaQuery('(min-width:1100px)'); + const isTablet = useMediaQuery('(min-width:600px)'); return ( - + Sell my bottles {ownerTokenIds && ownerTokenIds.size != 0 ? ( @@ -430,18 +431,18 @@ The contract storage must store the tokens that are offered for sale and their p : false ) .map(([token_id]) => ( - + - {" "} - {"ID : " + token_id.toString()}{" "} + {' '} + {'ID : ' + token_id.toString()}{' '} - {"Description : " + + {'Description : ' + nftContratTokenMetadataMap.get(token_id) ?.description} @@ -454,14 +455,14 @@ The contract storage must store the tokens that are offered for sale and their p title={nftContratTokenMetadataMap.get(token_id)?.name} /> @@ -469,14 +470,14 @@ The contract storage must store the tokens that are offered for sale and their p {offersTokenIDMap.get(token_id) - ? "Traded : " + + ? 'Traded : ' + 1 + - " (price : " + + ' (price : ' + offersTokenIDMap .get(token_id) ?.price.dividedBy(1000000) + - " Tz)" - : ""} + ' Tz)' + : ''} @@ -496,7 +497,7 @@ The contract storage must store the tokens that are offered for sale and their p ) : (
{ setSelectedTokenId(Number(token_id)); formik.handleSubmit(values); @@ -536,11 +537,11 @@ The contract storage must store the tokens that are offered for sale and their p )} - ))}{" "} + ))}{' '} ) : ( - + Sorry, you don't own any bottles, buy or mint some first )} @@ -586,8 +587,8 @@ In this section, you add a catalog page to show the bottles that are on sale and 1. Open the file `./src/WineCataloguePage.tsx` and replace it with this code: ```typescript - import { InfoOutlined } from "@mui/icons-material"; - import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; + import { InfoOutlined } from '@mui/icons-material'; + import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import { Box, Button, @@ -600,19 +601,19 @@ In this section, you add a catalog page to show the bottles that are on sale and Pagination, Tooltip, useMediaQuery, - } from "@mui/material"; - import Paper from "@mui/material/Paper"; - import Typography from "@mui/material/Typography"; - - import BigNumber from "bignumber.js"; - import { useFormik } from "formik"; - import { useSnackbar } from "notistack"; - import React, { Fragment, useState } from "react"; - import * as yup from "yup"; - import { UserContext, UserContextType } from "./App"; - import ConnectButton from "./ConnectWallet"; - import { TransactionInvalidBeaconError } from "./TransactionInvalidBeaconError"; - import { address, nat } from "./type-aliases"; + } from '@mui/material'; + import Paper from '@mui/material/Paper'; + import Typography from '@mui/material/Typography'; + + import BigNumber from 'bignumber.js'; + import { useFormik } from 'formik'; + import { useSnackbar } from 'notistack'; + import React, { Fragment, useState } from 'react'; + import * as yup from 'yup'; + import { UserContext, UserContextType } from './App'; + import ConnectButton from './ConnectWallet'; + import { TransactionInvalidBeaconError } from './TransactionInvalidBeaconError'; + import { address, nat } from './type-aliases'; const itemPerPage: number = 6; @@ -646,7 +647,7 @@ In this section, you add a catalog page to show the bottles that are on sale and }, validationSchema: validationSchema, onSubmit: (values) => { - console.log("onSubmit: (values)", values, selectedOfferEntry); + console.log('onSubmit: (values)', values, selectedOfferEntry); buy(selectedOfferEntry!); }, }); @@ -668,13 +669,13 @@ In this section, you add a catalog page to show the bottles that are on sale and await op?.confirmation(2); enqueueSnackbar( - "Bought " + + 'Bought ' + 1 + - " unit of Wine collection (token_id:" + + ' unit of Wine collection (token_id:' + selectedOfferEntry[0] + - ")", + ')', { - variant: "success", + variant: 'success', } ); @@ -684,16 +685,16 @@ In this section, you add a catalog page to show the bottles that are on sale and let tibe: TransactionInvalidBeaconError = new TransactionInvalidBeaconError(error); enqueueSnackbar(tibe.data_message, { - variant: "error", + variant: 'error', autoHideDuration: 10000, }); } }; - const isDesktop = useMediaQuery("(min-width:1100px)"); - const isTablet = useMediaQuery("(min-width:600px)"); + const isDesktop = useMediaQuery('(min-width:1100px)'); + const isTablet = useMediaQuery('(min-width:600px)'); return ( - + Wine catalogue @@ -722,24 +723,24 @@ In this section, you add a catalog page to show the bottles that are on sale and : false ) .map(([token_id, offer]) => ( - + - {" "} - {"ID : " + token_id.toString()}{" "} + {' '} + {'ID : ' + token_id.toString()}{' '} - {"Description : " + + {'Description : ' + nftContratTokenMetadataMap.get( token_id.toString() )?.description} - {"Seller : " + offer.owner}{" "} + {'Seller : ' + offer.owner}{' '} } @@ -753,24 +754,24 @@ In this section, you add a catalog page to show the bottles that are on sale and } /> - {" "} - {"Price : " + + {' '} + {'Price : ' + offer.price.dividedBy(1000000) + - " XTZ"} + ' XTZ'} @@ -790,7 +791,7 @@ In this section, you add a catalog page to show the bottles that are on sale and ) : ( { setSelectedOfferEntry([token_id, offer]); formik.handleSubmit(values); @@ -807,7 +808,7 @@ In this section, you add a catalog page to show the bottles that are on sale and ) : ( - + Sorry, there is not NFT to buy yet, you need to mint or sell bottles first diff --git a/docs/tutorials/build-an-nft-marketplace/part-3.md b/docs/tutorials/build-an-nft-marketplace/part-3.md index 4c1f1ff3e..cce516051 100644 --- a/docs/tutorials/build-an-nft-marketplace/part-3.md +++ b/docs/tutorials/build-an-nft-marketplace/part-3.md @@ -1,5 +1,6 @@ --- -title: "Part 3: Managing tokens with quantities" +title: 'Part 3: Managing tokens with quantities' +authors: 'Benjamin Fuentes (Marigold)' last_update: date: 8 November 2023 --- @@ -274,11 +275,11 @@ To use the single-asset template, you must change the code that your smart contr ```typescript const refreshUserContextOnPageReload = async () => { - console.log("refreshUserContext"); + console.log('refreshUserContext'); //CONTRACT try { let c = await Tezos.contract.at(nftContractAddress, tzip12); - console.log("nftContractAddress", nftContractAddress); + console.log('nftContractAddress', nftContractAddress); let nftContrat: NftWalletType = await Tezos.wallet.at( nftContractAddress @@ -289,17 +290,17 @@ To use the single-asset template, you must change the code that your smart contr let tokenMetadata: TZIP21TokenMetadata = (await c .tzip12() .getTokenMetadata(0)) as TZIP21TokenMetadata; - nftContratTokenMetadataMap.set("0", tokenMetadata); + nftContratTokenMetadataMap.set('0', tokenMetadata); setNftContratTokenMetadataMap(new Map(nftContratTokenMetadataMap)); //new Map to force refresh } catch (error) { - console.log("error refreshing nftContratTokenMetadataMap: "); + console.log('error refreshing nftContratTokenMetadataMap: '); } setNftContrat(nftContrat); setStorage(storage); } catch (error) { - console.log("error refreshing nft contract: ", error); + console.log('error refreshing nft contract: ', error); } //USER @@ -310,7 +311,7 @@ To use the single-asset template, you must change the code that your smart contr setUserBalance(balance.toNumber()); } - console.log("refreshUserContext ended."); + console.log('refreshUserContext ended.'); }; ``` @@ -319,7 +320,7 @@ To use the single-asset template, you must change the code that your smart contr 1. Replace the content of the `src/MintPage.tsx` file with this code: ```typescript - import OpenWithIcon from "@mui/icons-material/OpenWith"; + import OpenWithIcon from '@mui/icons-material/OpenWith'; import { Button, CardHeader, @@ -330,29 +331,29 @@ To use the single-asset template, you must change the code that your smart contr TextField, Toolbar, useMediaQuery, - } from "@mui/material"; - import Box from "@mui/material/Box"; - import Card from "@mui/material/Card"; - import CardContent from "@mui/material/CardContent"; - import Paper from "@mui/material/Paper"; - import Typography from "@mui/material/Typography"; - import { BigNumber } from "bignumber.js"; - import { useSnackbar } from "notistack"; - import React, { useEffect, useState } from "react"; - import { TZIP21TokenMetadata, UserContext, UserContextType } from "./App"; - import { TransactionInvalidBeaconError } from "./TransactionInvalidBeaconError"; + } from '@mui/material'; + import Box from '@mui/material/Box'; + import Card from '@mui/material/Card'; + import CardContent from '@mui/material/CardContent'; + import Paper from '@mui/material/Paper'; + import Typography from '@mui/material/Typography'; + import { BigNumber } from 'bignumber.js'; + import { useSnackbar } from 'notistack'; + import React, { useEffect, useState } from 'react'; + import { TZIP21TokenMetadata, UserContext, UserContextType } from './App'; + import { TransactionInvalidBeaconError } from './TransactionInvalidBeaconError'; import { AddCircleOutlined, Close, KeyboardArrowLeft, KeyboardArrowRight, - } from "@mui/icons-material"; - import { char2Bytes } from "@taquito/utils"; - import { useFormik } from "formik"; - import SwipeableViews from "react-swipeable-views"; - import * as yup from "yup"; - import { address, bytes, nat } from "./type-aliases"; + } from '@mui/icons-material'; + import { char2Bytes } from '@taquito/utils'; + import { useFormik } from 'formik'; + import SwipeableViews from 'react-swipeable-views'; + import * as yup from 'yup'; + import { address, bytes, nat } from './type-aliases'; export default function MintPage() { const { userAddress, @@ -362,7 +363,7 @@ To use the single-asset template, you must change the code that your smart contr nftContratTokenMetadataMap, } = React.useContext(UserContext) as UserContextType; const { enqueueSnackbar } = useSnackbar(); - const [pictureUrl, setPictureUrl] = useState(""); + const [pictureUrl, setPictureUrl] = useState(''); const [file, setFile] = useState(null); const [activeStep, setActiveStep] = React.useState(0); @@ -379,21 +380,21 @@ To use the single-asset template, you must change the code that your smart contr setActiveStep(step); }; const validationSchema = yup.object({ - name: yup.string().required("Name is required"), - description: yup.string().required("Description is required"), - symbol: yup.string().required("Symbol is required"), + name: yup.string().required('Name is required'), + description: yup.string().required('Description is required'), + symbol: yup.string().required('Symbol is required'), quantity: yup .number() - .required("Quantity is required") - .positive("ERROR: The number must be greater than 0!"), + .required('Quantity is required') + .positive('ERROR: The number must be greater than 0!'), }); const formik = useFormik({ initialValues: { - name: "", - description: "", + name: '', + description: '', token_id: 0, - symbol: "WINE", + symbol: 'WINE', quantity: 1, } as TZIP21TokenMetadata & { quantity: number }, validationSchema: validationSchema, @@ -419,29 +420,29 @@ To use the single-asset template, you must change the code that your smart contr //IPFS if (file) { const formData = new FormData(); - formData.append("file", file); + formData.append('file', file); const requestHeaders: HeadersInit = new Headers(); requestHeaders.set( - "pinata_api_key", + 'pinata_api_key', `${import.meta.env.VITE_PINATA_API_KEY}` ); requestHeaders.set( - "pinata_secret_api_key", + 'pinata_secret_api_key', `${import.meta.env.VITE_PINATA_API_SECRET}` ); const resFile = await fetch( - "https://api.pinata.cloud/pinning/pinFileToIPFS", + 'https://api.pinata.cloud/pinning/pinFileToIPFS', { - method: "post", + method: 'post', body: formData, headers: requestHeaders, } ); const responseJson = await resFile.json(); - console.log("responseJson", responseJson); + console.log('responseJson', responseJson); const thumbnailUri = `ipfs://${responseJson.IpfsHash}`; setPictureUrl( @@ -461,13 +462,13 @@ To use the single-asset template, you must change the code that your smart contr //close directly the form setFormOpen(false); enqueueSnackbar( - "Wine collection is minting ... it will be ready on next block, wait for the confirmation message before minting another collection", - { variant: "info" } + 'Wine collection is minting ... it will be ready on next block, wait for the confirmation message before minting another collection', + { variant: 'info' } ); await op.confirmation(2); - enqueueSnackbar("Wine collection minted", { variant: "success" }); + enqueueSnackbar('Wine collection minted', { variant: 'success' }); refreshUserContextOnPageReload(); //force all app to refresh the context } @@ -476,7 +477,7 @@ To use the single-asset template, you must change the code that your smart contr let tibe: TransactionInvalidBeaconError = new TransactionInvalidBeaconError(error); enqueueSnackbar(tibe.data_message, { - variant: "error", + variant: 'error', autoHideDuration: 10000, }); } @@ -487,16 +488,16 @@ To use the single-asset template, you must change the code that your smart contr const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => { if ( - event.type === "keydown" && - ((event as React.KeyboardEvent).key === "Tab" || - (event as React.KeyboardEvent).key === "Shift") + event.type === 'keydown' && + ((event as React.KeyboardEvent).key === 'Tab' || + (event as React.KeyboardEvent).key === 'Shift') ) { return; } setFormOpen(open); }; - const isTablet = useMediaQuery("(min-width:600px)"); + const isTablet = useMediaQuery('(min-width:600px)'); return ( @@ -507,21 +508,21 @@ To use the single-asset template, you must change the code that your smart contr } sx={{ p: 1, - position: "absolute", - right: "0", - display: formOpen ? "none" : "block", + position: 'absolute', + right: '0', + display: formOpen ? 'none' : 'block', zIndex: 1, }} onClick={toggleDrawer(!formOpen)} > - {" Mint Form " + + {' Mint Form ' + (storage!.administrators.indexOf(userAddress! as address) < 0 - ? " (You are not admin)" - : "")} + ? ' (You are not admin)' + : '')} ) : ( - "" + '' )} - + Mint a new collection ) : ( - "" + '' )} ) : ( - "" + '' )} - + Mint a new collection ) : ( - "" + '' )}