diff --git a/lib/base/README.md b/lib/base/README.md index 440ab484..acb11abb 100644 --- a/lib/base/README.md +++ b/lib/base/README.md @@ -258,9 +258,9 @@ aws ssm start-session --target $INSTANCE_ID --region $AWS_REGION ```bash sudo su bcuser # Execution client logs: -docker logs --tail 50 node_execution_1 -f +docker logs --tail 50 node-execution-1 -f # Base client logs: -docker logs --tail 50 node_node_1 -f +docker logs --tail 50 node-node-1 -f ``` 2. How to check the logs from the EC2 user-data script? diff --git a/lib/base/app.ts b/lib/base/app.ts index f0e95baf..1e7e9899 100644 --- a/lib/base/app.ts +++ b/lib/base/app.ts @@ -17,12 +17,13 @@ new BaseCommonStack(app, "base-common", { }); new BaseSingleNodeStack(app, "base-single-node", { - stackName: `base-single-node-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, + stackName: `base-single-node-${config.baseNodeConfig.baseClient}-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, env: { account: config.baseConfig.accountId, region: config.baseConfig.region }, instanceType: config.baseNodeConfig.instanceType, instanceCpuType: config.baseNodeConfig.instanceCpuType, baseNetworkId: config.baseNodeConfig.baseNetworkId, + baseClient: config.baseNodeConfig.baseClient, baseNodeConfiguration: config.baseNodeConfig.baseNodeConfiguration, restoreFromSnapshot: config.baseNodeConfig.restoreFromSnapshot, l1ExecutionEndpoint: config.baseNodeConfig.l1ExecutionEndpoint, @@ -32,12 +33,13 @@ new BaseSingleNodeStack(app, "base-single-node", { }); new BaseHANodesStack(app, "base-ha-nodes", { - stackName: `base-ha-nodes-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, + stackName: `base-ha-nodes-${config.baseNodeConfig.baseClient}-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, env: { account: config.baseConfig.accountId, region: config.baseConfig.region }, instanceType: config.baseNodeConfig.instanceType, instanceCpuType: config.baseNodeConfig.instanceCpuType, baseNetworkId: config.baseNodeConfig.baseNetworkId, + baseClient: config.baseNodeConfig.baseClient, baseNodeConfiguration: config.baseNodeConfig.baseNodeConfiguration, restoreFromSnapshot: config.baseNodeConfig.restoreFromSnapshot, l1ExecutionEndpoint: config.baseNodeConfig.l1ExecutionEndpoint, diff --git a/lib/base/lib/assets/base/node-start.sh b/lib/base/lib/assets/base/node-start.sh deleted file mode 100644 index bdd37569..00000000 --- a/lib/base/lib/assets/base/node-start.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -e -export CLIENT=geth - -echo "Script is starting client $CLIENT" -# Start the node -cd /home/bcuser/node -/usr/local/bin/docker-compose -f /home/bcuser/node/docker-compose.yml up -d - -echo "Started" diff --git a/lib/base/lib/assets/base/node-stop.sh b/lib/base/lib/assets/base/node-stop.sh deleted file mode 100644 index 613e58f8..00000000 --- a/lib/base/lib/assets/base/node-stop.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -e -export CLIENT=geth -echo "Script is stopping client $CLIENT" -# Stop the node -cd /home/bcuser/node -/usr/local/bin/docker-compose -f /home/bcuser/node/docker-compose.yml down - -echo "Stopped" diff --git a/lib/base/lib/assets/cfn-hup/cfn-auto-reloader.conf b/lib/base/lib/assets/instance/cfn-hup/cfn-auto-reloader.conf similarity index 100% rename from lib/base/lib/assets/cfn-hup/cfn-auto-reloader.conf rename to lib/base/lib/assets/instance/cfn-hup/cfn-auto-reloader.conf diff --git a/lib/base/lib/assets/cfn-hup/cfn-hup.conf b/lib/base/lib/assets/instance/cfn-hup/cfn-hup.conf similarity index 100% rename from lib/base/lib/assets/cfn-hup/cfn-hup.conf rename to lib/base/lib/assets/instance/cfn-hup/cfn-hup.conf diff --git a/lib/base/lib/assets/cfn-hup/cfn-hup.service b/lib/base/lib/assets/instance/cfn-hup/cfn-hup.service similarity index 100% rename from lib/base/lib/assets/cfn-hup/cfn-hup.service rename to lib/base/lib/assets/instance/cfn-hup/cfn-hup.service diff --git a/lib/base/lib/assets/instance/cfn-hup/setup.sh b/lib/base/lib/assets/instance/cfn-hup/setup.sh new file mode 100755 index 00000000..418811e4 --- /dev/null +++ b/lib/base/lib/assets/instance/cfn-hup/setup.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +if [ -n "$1" ]; then + export STACK_ID=$1 +else + echo "Error: No Stack ID is provided" + echo "Usage: instance/cfn-hup/setup.sh " + exit 1 +fi + +if [ -n "$2" ]; then + export AWS_REGION=$2 +else + echo "Error: No AWS Region is provided" + echo "Usage: instance/cfn-hup/setup.sh " + exit 1 +fi + + echo "Install CloudFormation helper scripts" + mkdir -p /opt/aws/ + pip3 install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz + ln -s /usr/local/init/ubuntu/cfn-hup /etc/init.d/cfn-hup + + echo "Configuring CloudFormation helper scripts" + mkdir -p /etc/cfn/ + mv /opt/instance/cfn-hup/cfn-hup.conf /etc/cfn/cfn-hup.conf + sed -i "s;__AWS_STACK_ID__;\"$STACK_ID\";g" /etc/cfn/cfn-hup.conf + sed -i "s;__AWS_REGION__;\"$AWS_REGION\";g" /etc/cfn/cfn-hup.conf + + mkdir -p /etc/cfn/hooks.d/system + mv /opt/instance/cfn-hup/cfn-auto-reloader.conf /etc/cfn/hooks.d/cfn-auto-reloader.conf + sed -i "s;__AWS_STACK_NAME__;\"$STACK_NAME\";g" /etc/cfn/hooks.d/cfn-auto-reloader.conf + sed -i "s;__AWS_REGION__;\"$AWS_REGION\";g" /etc/cfn/hooks.d/cfn-auto-reloader.conf + + echo "Starting CloudFormation helper scripts as a service" + mv /opt/instance/cfn-hup/cfn-hup.service /etc/systemd/system/cfn-hup.service + + systemctl daemon-reload + systemctl enable --now cfn-hup + systemctl start cfn-hup.service diff --git a/lib/base/lib/assets/instance/storage/restore-from-snapshot.sh b/lib/base/lib/assets/instance/storage/restore-from-snapshot.sh new file mode 100644 index 00000000..53c310e9 --- /dev/null +++ b/lib/base/lib/assets/instance/storage/restore-from-snapshot.sh @@ -0,0 +1,76 @@ +#!/bin/bash +set +e + +source /etc/cdk_environment + +echo "Downloading Snapshot." + +cd /data + +SNAPSHOT_FILE_NAME=snapshot.tar.gz +SNAPSHOT_DIR=/data +SNAPSHOT_DOWNLOAD_STATUS=-1 + +if [ "$SNAPSHOT_URL" == "none" ] || [ -z "${SNAPSHOT_URL}" ]; then + + case $BASE_CLIENT in + "geth") + LATEST_SNAPSHOT_FILE_NAME=$(curl https://$NETWORK_ID-full-snapshots.base.org/latest) + SNAPSHOT_URL=https://$NETWORK_ID-full-snapshots.base.org/$LATEST_SNAPSHOT_FILE_NAME + ;; + "reth") + LATEST_SNAPSHOT_FILE_NAME=$(curl https://$NETWORK_ID-reth-archive-snapshots.base.org/latest) + SNAPSHOT_URL=https://$NETWORK_ID-reth-archive-snapshots.base.org/$LATEST_SNAPSHOT_FILE_NAME + ;; + *) + # Geth + LATEST_SNAPSHOT_FILE_NAME=$(curl https://$NETWORK_ID-full-snapshots.base.org/latest) + SNAPSHOT_URL=https://$NETWORK_ID-full-snapshots.base.org/$LATEST_SNAPSHOT_FILE_NAME + ;; + esac +fi + +while (( SNAPSHOT_DOWNLOAD_STATUS != 0 )) +do + PIDS=$(pgrep wget) + if [ -z "$PIDS" ]; then + wget $SNAPSHOT_URL -P $SNAPSHOT_DIR -O $SNAPSHOT_FILE_NAME + fi + SNAPSHOT_DOWNLOAD_STATUS=$? + pid=$(pidof wget) + wait $pid + echo "wget exit." + case $SNAPSHOT_DOWNLOAD_STATUS in + 8) + echo "Server error." + exit 8 + ;; + 3) + echo "No space left on device." + exit 3 + ;; + *) + continue + ;; + esac +done +echo "Downloading Snapshot script finished" + +sleep 60 + +echo "Starting snapshot decompression ..." + +tar --use-compress-program=unzstd -xvf $SNAPSHOT_DIR/$SNAPSHOT_FILE_NAME -C /data 2>&1 | tee unzip.log && echo "decompressed successfully..." || echo "decompression failed..." >> snapshots-decompression.log + +echo "Decompressed snapshot, cleaning up..." + +mv /data/snapshots/$NETWORK_ID/download/* /data && \ +rm -rf /data/snapshots && \ +rm -rf $SNAPSHOT_DIR/$SNAPSHOT_FILE_NAME + +echo "Snapshot is ready, starting the service.." + +chown -R bcuser:bcuser /data + +sudo systemctl daemon-reload +sudo systemctl enable --now node diff --git a/lib/base/lib/assets/instance/storage/setup.sh b/lib/base/lib/assets/instance/storage/setup.sh new file mode 100755 index 00000000..ca5e6b81 --- /dev/null +++ b/lib/base/lib/assets/instance/storage/setup.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +make_fs () { + # If file system = to ext4 use mkfs.ext4, if xfs use mkfs.xfs + if [ -z "$1" ]; then + echo "Error: No file system type provided." + echo "Usage: make_fs " + exit 1 + fi + + if [ -z "$2" ]; then + echo "Error: No target volume ID provided." + echo "Usage: make_fs " + exit 1 + fi + + local file_system=$1 + local volume_id=$2 + if [ "$file_system" == "ext4" ]; then + mkfs -t ext4 "$volume_id" + return "$?" + else + mkfs.xfs -f "$volume_id" + return "$?" + fi +} + +# We need an nvme disk that is not mounted and not partitioned +get_all_empty_nvme_disks () { + local all_not_mounted_nvme_disks + local all_mounted_nvme_partitions + local unmounted_nvme_disks=() + local sorted_unmounted_nvme_disks + + #The disk will only be mounted when the nvme disk is larger than 100GB to avoid storing blockchain node data directly on the root EBS disk (which is 46GB by default) + all_not_mounted_nvme_disks=$(lsblk -lnb | awk '{if ($7 == "" && $4 > 100000000) {print $1}}' | grep nvme) + all_mounted_nvme_partitions=$(mount | awk '{print $1}' | grep /dev/nvme) + for disk in ${all_not_mounted_nvme_disks[*]}; do + if [[ ! "${all_mounted_nvme_partitions[*]}" =~ $disk ]]; then + unmounted_nvme_disks+=("$disk") + fi + done + # Sort the array + sorted_unmounted_nvme_disks=($(printf '%s\n' "${unmounted_nvme_disks[*]}" | sort)) + echo "${sorted_unmounted_nvme_disks[*]}" +} + +get_next_empty_nvme_disk () { + local sorted_unmounted_nvme_disks + sorted_unmounted_nvme_disks=($(get_all_empty_nvme_disks)) + # Return the first unmounted nvme disk + echo "/dev/${sorted_unmounted_nvme_disks[0]}" +} + +# Add input as command line parameters for name of the directory to mount +if [ -n "$1" ]; then + DIR_NAME=$1 +else + echo "Error: No data file system mount path is provided." + echo "Usage: instance/storage/setup.sh " + echo "Default file system type is ext4" + echo "If you skip , script will try to use the first unformatted volume ID." + echo "Usage example: instance/storage/setup.sh /data ext4 300000000000000" + exit 1 +fi + +# Case input for $2 between ext4 and xfs, use ext4 as default +case $2 in + ext4) + echo "File system set to ext4" + FILE_SYSTEM="ext4" + FS_CONFIG="defaults" + ;; + xfs) + echo "File system set to xfs" + FILE_SYSTEM="xfs" + FS_CONFIG="noatime,nodiratime,nodiscard" # See more: https://cdrdv2-public.intel.com/686417/rocksdb-benchmark-tuning-guide-on-xeon.pdf + ;; + *) + echo "File system set to ext4" + FILE_SYSTEM="ext4" + FS_CONFIG="defaults" + ;; +esac + +if [ -n "$3" ]; then + VOLUME_SIZE=$3 +else + echo "The size of volume for $DIR_NAME is not specified. Will try to guess volume ID." +fi + + echo "Checking if $DIR_NAME is mounted, and dont do anything if it is" + if [ $(df --output=target | grep -c "$DIR_NAME") -lt 1 ]; then + + if [ -n "$VOLUME_SIZE" ]; then + VOLUME_ID=/dev/$(lsblk -lnb | awk -v VOLUME_SIZE_BYTES="$VOLUME_SIZE" '{if ($4== VOLUME_SIZE_BYTES) {print $1}}') + echo "Data volume size defined, use respective volume id: $VOLUME_ID" + else + VOLUME_ID=$(get_next_empty_nvme_disk) + echo "Data volume size undefined, trying volume id: $VOLUME_ID" + fi + + make_fs $FILE_SYSTEM "$VOLUME_ID" + + sleep 10 + VOLUME_UUID=$(lsblk -fn -o UUID "$VOLUME_ID") + VOLUME_FSTAB_CONF="UUID=$VOLUME_UUID $DIR_NAME $FILE_SYSTEM $FS_CONFIG 0 2" + echo "VOLUME_ID=$VOLUME_ID" + echo "VOLUME_UUID=$VOLUME_UUID" + echo "VOLUME_FSTAB_CONF=$VOLUME_FSTAB_CONF" + + # Check if data disc is already in fstab and replace the line if it is with the new disc UUID + echo "Checking fstab for volume $DIR_NAME" + if [ $(grep -c "$DIR_NAME" /etc/fstab) -gt 0 ]; then + SED_REPLACEMENT_STRING="$(grep -n "$DIR_NAME" /etc/fstab | cut -d: -f1)s#.*#$VOLUME_FSTAB_CONF#" + # if file exists, delete it + if [ -f /etc/fstab.bak ]; then + rm /etc/fstab.bak + fi + cp /etc/fstab /etc/fstab.bak + sed -i "$SED_REPLACEMENT_STRING" /etc/fstab + else + echo "$VOLUME_FSTAB_CONF" | tee -a /etc/fstab + fi + + mount -a + chown -R bcuser:bcuser "$DIR_NAME" + else + echo "$DIR_NAME volume is mounted, nothing changed" + fi diff --git a/lib/base/lib/assets/node/node-start.sh b/lib/base/lib/assets/node/node-start.sh new file mode 100644 index 00000000..372cd9f7 --- /dev/null +++ b/lib/base/lib/assets/node/node-start.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -e + +source /etc/cdk_environment + +export NETWORK_ENV=".env.$NETWORK_ID" +export CLIENT="$BASE_CLIENT" +export HOST_DATA_DIR="/data" + +echo "Script is starting client $CLIENT on $NETWORK_ENV" +# Start the node +cd /home/bcuser/node +docker compose -f /home/bcuser/node/docker-compose.yml up -d + +echo "Started" diff --git a/lib/base/lib/assets/node/node-stop.sh b/lib/base/lib/assets/node/node-stop.sh new file mode 100644 index 00000000..85c3fa5c --- /dev/null +++ b/lib/base/lib/assets/node/node-stop.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e +source /etc/cdk_environment + +export NETWORK_ENV=".env.$NETWORK_ID" +export CLIENT=geth + +echo "Script is starting client $CLIENT on $NETWORK_ENV" +# Stop the node +cd /home/bcuser/node +docker compose -f /home/bcuser/node/docker-compose.yml down + +echo "Stopped" diff --git a/lib/base/lib/assets/restore-from-snapshot-archive-s3.sh b/lib/base/lib/assets/restore-from-snapshot-archive-s3.sh deleted file mode 100644 index 2f0cbaff..00000000 --- a/lib/base/lib/assets/restore-from-snapshot-archive-s3.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -set +e - -source /etc/environment - -echo "Downloading Snapshot." - -cd /data - -SNAPSHOT_FILE_NAME=snapshot.tar.gz -SNAPSHOT_DIR=/data - -LATEST_SNAPSHOT_FILE_NAME=$(curl https://$NETWORK_ID-$NODE_CONFIG-snapshots.base.org/latest) && \ -s5cmd --log error cp s3://base-snapshots-$NETWORK_ID-archive/$LATEST_SNAPSHOT_FILE_NAME $SNAPSHOT_DIR/$SNAPSHOT_FILE_NAME && \ -echo "Downloading Snapshot script finished" && \ -sleep 60 &&\ -echo "Starting snapshot decompression ..." && \ -tar --use-compress-program=unzstd -xvf $SNAPSHOT_DIR/$SNAPSHOT_FILE_NAME -C /data 2>&1 | tee unzip.log && echo "decompresed successfully..." || echo "decompression failed..." >> snapshots-decompression.log - -echo "Decompresed snapshot, cleaning up..." - -mv /data/snapshots/$NETWORK_ID/download/* $SNAPSHOT_DIR && \ -rm -rf /data/snapshots && \ -rm -rf $SNAPSHOT_DIR/$SNAPSHOT_FILE_NAME - -echo "Snapshot is ready, starting the service.." - -chown -R bcuser:bcuser $SNAPSHOT_DIR - -sudo systemctl daemon-reload -sudo systemctl enable --now base diff --git a/lib/base/lib/assets/restore-from-snapshot-http.sh b/lib/base/lib/assets/restore-from-snapshot-http.sh deleted file mode 100644 index 5b4c7352..00000000 --- a/lib/base/lib/assets/restore-from-snapshot-http.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -set +e - -source /etc/environment - -echo "Downloading Snapshot." - -cd /data - -SNAPSHOT_FILE_NAME=snapshot.tar.gz -SNAPSHOT_DIR=/data -SNAPSHOT_DOWNLOAD_STATUS=-1 - -if [ "$SNAPSHOT_URL" == "none" ] || [ -z "${SNAPSHOT_URL}" ]; then - LATEST_SNAPSHOT_FILE_NAME=$(curl https://$NETWORK_ID-$NODE_CONFIG-snapshots.base.org/latest) - SNAPSHOT_URL=https://$NETWORK_ID-$NODE_CONFIG-snapshots.base.org/$LATEST_SNAPSHOT_FILE_NAME -fi - -while (( SNAPSHOT_DOWNLOAD_STATUS != 0 )) -do - PIDS=$(pgrep aria2c) - if [ -z "$PIDS" ]; then - aria2c --max-connection-per-server=1 $SNAPSHOT_URL -d $SNAPSHOT_DIR -o $SNAPSHOT_FILE_NAME -l /data/download.log --log-level=notice --allow-overwrite=true --allow-piece-length-change=true - fi - SNAPSHOT_DOWNLOAD_STATUS=$? - pid=$(pidof aria2c) - wait $pid - echo "aria2c exit." - case $SNAPSHOT_DOWNLOAD_STATUS in - 3) - echo "File does not exist." - exit 3 - ;; - 9) - echo "No space left on device." - exit 9 - ;; - *) - continue - ;; - esac -done -echo "Downloading Snapshot script finished" - -sleep 60 - -echo "Starting snapshot decompression ..." - -tar --use-compress-program=unzstd -xvf $SNAPSHOT_DIR/$SNAPSHOT_FILE_NAME -C /data 2>&1 | tee unzip.log && echo "decompresed successfully..." || echo "decompression failed..." >> snapshots-decompression.log - -echo "Decompresed snapshot, cleaning up..." - -mv /data/snapshots/$NETWORK_ID/download/* /data && \ -rm -rf /data/snapshots && \ -rm -rf $SNAPSHOT_DIR/$SNAPSHOT_FILE_NAME - -echo "Snapshot is ready, starting the service.." - -chown -R bcuser:bcuser /data - -sudo systemctl daemon-reload -sudo systemctl enable --now base diff --git a/lib/base/lib/assets/setup-instance-store-volumes.sh b/lib/base/lib/assets/setup-instance-store-volumes.sh deleted file mode 100644 index ee2825d7..00000000 --- a/lib/base/lib/assets/setup-instance-store-volumes.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -source /etc/environment - -if [[ "$DATA_VOLUME_TYPE" == "instance-store" ]]; then - echo "Data volume type is instance store" - export DATA_VOLUME_ID=/dev/$(lsblk -lnb | awk 'max < $4 {max = $4; vol = $1} END {print vol}') -fi - -if [ -n "$DATA_VOLUME_ID" ]; then - if [ $(df --output=target | grep -c "/data") -lt 1 ]; then - echo "Checking fstab for Data volume" - - mkfs.ext4 $DATA_VOLUME_ID - echo "Data volume formatted. Mounting..." - echo "waiting for volume to get UUID" - OUTPUT=0; - while [ "$OUTPUT" = 0 ]; do - DATA_VOLUME_UUID=$(lsblk -fn -o UUID $DATA_VOLUME_ID) - OUTPUT=$(echo $DATA_VOLUME_UUID | grep -c - $2) - echo $OUTPUT - done - DATA_VOLUME_UUID=$(lsblk -fn -o UUID $DATA_VOLUME_ID) - DATA_VOLUME_FSTAB_CONF="UUID=$DATA_VOLUME_UUID /data ext4 defaults 0 2" - echo "DATA_VOLUME_ID="$DATA_VOLUME_ID - echo "DATA_VOLUME_UUID="$DATA_VOLUME_UUID - echo "DATA_VOLUME_FSTAB_CONF="$DATA_VOLUME_FSTAB_CONF - - # Check if data disc is already in fstab and replace the line if it is with the new disc UUID - if [ $(grep -c "data" /etc/fstab) -gt 0 ]; then - SED_REPLACEMENT_STRING="$(grep -n "/data" /etc/fstab | cut -d: -f1)s#.*#$DATA_VOLUME_FSTAB_CONF#" - cp /etc/fstab /etc/fstab.bak - sed -i "$SED_REPLACEMENT_STRING" /etc/fstab - else - echo $DATA_VOLUME_FSTAB_CONF | sudo tee -a /etc/fstab - fi - - sudo mount -a - - chown bcuser:bcuser -R /data - else - echo "Data volume is mounted, nothing changed" - fi -fi diff --git a/lib/base/lib/assets/sync-checker/setup.sh b/lib/base/lib/assets/sync-checker/setup.sh new file mode 100755 index 00000000..96bc10e5 --- /dev/null +++ b/lib/base/lib/assets/sync-checker/setup.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +if [ -n "$1" ]; then + export SYNC_CHECKER_SCRIPT=$1 +else + echo "No path to syncchecker script is provided" + echo "Usage: sync-checker/setup.sh " + echo "Using default: /opt/sync-checker/syncchecker.sh" + export SYNC_CHECKER_SCRIPT="/opt/sync-checker/syncchecker.sh" +fi + +echo "Configuring syncchecker script" +mv $SYNC_CHECKER_SCRIPT /opt/syncchecker.sh +chmod +x /opt/syncchecker.sh + +echo "Setting up sync-checker service" +mv /opt/sync-checker/sync-checker.service /etc/systemd/system/sync-checker.service + +# Run every 5 minutes +echo "Setting up sync-checker timer" +mv /opt/sync-checker/sync-checker.timer /etc/systemd/system/sync-checker.timer + +echo "Starting sync checker timer" +systemctl start sync-checker.timer +systemctl enable sync-checker.timer diff --git a/lib/base/lib/assets/sync-checker/sync-checker.service b/lib/base/lib/assets/sync-checker/sync-checker.service new file mode 100644 index 00000000..9f187ce2 --- /dev/null +++ b/lib/base/lib/assets/sync-checker/sync-checker.service @@ -0,0 +1,5 @@ +[Unit] +Description="Sync checker for blockchain node" + +[Service] +ExecStart=/opt/syncchecker.sh diff --git a/lib/base/lib/assets/sync-checker/sync-checker.timer b/lib/base/lib/assets/sync-checker/sync-checker.timer new file mode 100644 index 00000000..b45ff94e --- /dev/null +++ b/lib/base/lib/assets/sync-checker/sync-checker.timer @@ -0,0 +1,9 @@ +[Unit] +Description="Run Sync checker service every 5 min" + +[Timer] +OnCalendar=*:*:0/5 +Unit=sync-checker.service + +[Install] +WantedBy=multi-user.target diff --git a/lib/base/lib/assets/user-data-alinux.sh b/lib/base/lib/assets/user-data-alinux.sh new file mode 100644 index 00000000..27818b1d --- /dev/null +++ b/lib/base/lib/assets/user-data-alinux.sh @@ -0,0 +1,209 @@ +#!/bin/bash +set +e + +RESOURCE_ID=${_NODE_CF_LOGICAL_ID_} +STACK_NAME=${_STACK_NAME_} +RESTORE_FROM_SNAPSHOT=${_RESTORE_FROM_SNAPSHOT_} + +{ + echo "LIFECYCLE_HOOK_NAME=${_LIFECYCLE_HOOK_NAME_}" + echo "AUTOSCALING_GROUP_NAME=${_AUTOSCALING_GROUP_NAME_}" + echo "ASSETS_S3_PATH=${_ASSETS_S3_PATH_}" + echo "DATA_VOLUME_TYPE=${_DATA_VOLUME_TYPE_}" + echo "DATA_VOLUME_SIZE=${_DATA_VOLUME_SIZE_}" + + echo "AWS_REGION=${_AWS_REGION_}" + echo "NETWORK_ID=${_NETWORK_ID_}" + echo "BASE_CLIENT=${_BASE_CLIENT_}" + echo "NODE_CONFIG=${_NODE_CONFIG_}" + echo "L1_EXECUTION_ENDPOINT=${_L1_EXECUTION_ENDPOINT_}" + echo "L1_CONSENSUS_ENDPOINT=${_L1_CONSENSUS_ENDPOINT_}" + echo "SNAPSHOT_URL=${_SNAPSHOT_URL_}" +} >> /etc/cdk_environment + +source /etc/cdk_environment + +# Export environment variables so calls to `envsubst` inherit the evironment variables. +while read -r line; do export "$line"; done < /etc/cdk_environment + +arch=$(uname -m) + +echo "Architecture detected: $arch" + +if [ "$arch" == "x86_64" ]; then + SSM_AGENT_BINARY_URI=https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm + YQ_URI=https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 +else + SSM_AGENT_BINARY_URI=https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_arm64/amazon-ssm-agent.rpm + YQ_URI=https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64 +fi + +echo "Updating and installing required system packages" +dnf update -y +dnf -y install amazon-cloudwatch-agent collectd jq gcc ncurses-devel telnet aws-cfn-bootstrap cronie zstd git +wget $YQ_URI -O /usr/bin/yq && chmod +x /usr/bin/yq + +sudo systemctl enable crond.service +sudo systemctl start crond.service + +cd /opt + +echo "Downloading assets zip file" +aws s3 cp $ASSETS_S3_PATH ./assets.zip +unzip -q assets.zip + +echo 'Upgrading SSM Agent' +yum install -y $SSM_AGENT_BINARY_URI + +# Base specific setup starts here + +echo "Installing Docker" +dnf remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine +dnf -y install dnf-plugins-core +dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo +sed -i 's/$releasever/9/g' /etc/yum.repos.d/docker-ce.repo +dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +systemctl enable --now docker + +mkdir -p /data + +# Creating run user and making sure it has all necessary permissions +groupadd -g 1002 bcuser +useradd -u 1002 -g 1002 -m -s /bin/bash bcuser +usermod -a -G docker bcuser +usermod -a -G docker ec2-user +chmod -R 755 /home/bcuser + +echo "Starting docker" +service docker start +systemctl enable docker + +echo "Cloning node repo" +cd /home/bcuser +GIT_URL=https://github.com/base-org/node.git +git clone $GIT_URL +cd ./node + +echo "Configuring node" + +case $NETWORK_ID in + "mainnet") + OP_CONFIG_FILE_PATH=/home/bcuser/node/.env.mainnet + ;; + "sepolia") + OP_CONFIG_FILE_PATH=/home/bcuser/node/.env.sepolia + ;; + *) + echo "Network id is not valid." + exit 1 + ;; +esac + +case $NODE_CONFIG in + "full") + echo "OP_GETH_GCMODE=full" >> $OP_CONFIG_FILE_PATH + ;; + "archive") + echo "OP_GETH_GCMODE=archive" >> $OP_CONFIG_FILE_PATH + ;; + *) + echo "Network id is not valid." + exit 1 + ;; +esac + +sed -i "s##$L1_EXECUTION_ENDPOINT#g" $OP_CONFIG_FILE_PATH +sed -i "s##$L1_CONSENSUS_ENDPOINT#g" $OP_CONFIG_FILE_PATH +sed -i "s##$L1_CONSENSUS_ENDPOINT#g" $OP_CONFIG_FILE_PATH +sed -i "s#OP_NODE_L1_TRUST_RPC=\"false\"#OP_NODE_L1_TRUST_RPC=\"true\"#g" $OP_CONFIG_FILE_PATH + +sed -i "s#HOST_DATA_DIR=./$BASE_CLIENT-data#HOST_DATA_DIR=/data#g" /home/bcuser/node/.env + +chown -R bcuser:bcuser /home/bcuser/node + +echo "Configuring and starting sync-checker" +SYNC_CHECKER_FILE_NAME="syncchecker-base.sh" +/opt/sync-checker/setup.sh "/opt/sync-checker/$SYNC_CHECKER_FILE_NAME" + +echo "Configuring node as a service" +mkdir /home/bcuser/bin +mv /opt/node/node-start.sh /home/bcuser/bin/node-start.sh +mv /opt/node/node-stop.sh /home/bcuser/bin/node-stop.sh +chmod 766 /home/bcuser/bin/* +chown -R bcuser:bcuser /home/bcuser + +sudo bash -c 'cat > /etc/systemd/system/node.service < /dev/null + then + echo "cfn-signal could not be found, installing" + /opt/instance/cfn-hup/setup.sh "$STACK_NAME" "$AWS_REGION" + else + echo "cfn-signal is available, skipping installation" + fi + cfn-signal --stack "$STACK_NAME" --resource "$RESOURCE_ID" --region "$AWS_REGION" +fi + +echo "Preparing data volume" + +if [[ "$DATA_VOLUME_TYPE" == "instance-store" ]]; then + echo "Data volume type is instance store" + + (crontab -l; echo "@reboot /opt/instance/storage/setup.sh /data ext4 > /tmp/setup-store-volume-data.log 2>&1") | crontab - + crontab -l + + /opt/instance/storage/setup.sh /data ext4 +else + echo "Data volume type is EBS" + echo "Waiting for EBS volume to become available" + sleep 60 + /opt/instance/storage/setup.sh /data ext4 +fi + +lsblk -d + +chown -R bcuser:bcuser /data +chmod -R 755 /data + +echo 'Configuring CloudWatch Agent' +cp /opt/cw-agent.json /opt/aws/amazon-cloudwatch-agent/etc/custom-amazon-cloudwatch-agent.json + +echo "Starting CloudWatch Agent" +/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \ +-a fetch-config -c file:/opt/aws/amazon-cloudwatch-agent/etc/custom-amazon-cloudwatch-agent.json -m ec2 -s +systemctl restart amazon-cloudwatch-agent + +if [[ "$LIFECYCLE_HOOK_NAME" != "none" ]]; then + echo "Signaling ASG lifecycle hook to complete" + TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") + INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id) + aws autoscaling complete-lifecycle-action --lifecycle-action-result CONTINUE --instance-id $INSTANCE_ID --lifecycle-hook-name "$LIFECYCLE_HOOK_NAME" --auto-scaling-group-name "$AUTOSCALING_GROUP_NAME" --region $AWS_REGION +fi + +if [ "$RESTORE_FROM_SNAPSHOT" == "false" ]; then + echo "Skipping restoration from snapshot. Starting node" + systemctl daemon-reload + systemctl enable --now node +else + echo "Restoring full node from snapshot" + chmod +x /opt/instance/storage/restore-from-snapshot.sh + /opt/instance/storage/restore-from-snapshot.sh +fi + +echo "All Done!!" +set -e diff --git a/lib/base/lib/assets/user-data/node.sh b/lib/base/lib/assets/user-data/node.sh deleted file mode 100644 index 9454be2c..00000000 --- a/lib/base/lib/assets/user-data/node.sh +++ /dev/null @@ -1,313 +0,0 @@ -#!/bin/bash -set +e - -# Set by generic single-node and ha-node CDK components -LIFECYCLE_HOOK_NAME=${_LIFECYCLE_HOOK_NAME_} -AUTOSCALING_GROUP_NAME=${_AUTOSCALING_GROUP_NAME_} -RESOURCE_ID=${_NODE_CF_LOGICAL_ID_} -ASSETS_S3_PATH=${_ASSETS_S3_PATH_} -DATA_VOLUME_TYPE=${_DATA_VOLUME_TYPE_} -DATA_VOLUME_SIZE=${_DATA_VOLUME_SIZE_} -{ - echo "LIFECYCLE_HOOK_NAME=$LIFECYCLE_HOOK_NAME" - echo "AUTOSCALING_GROUP_NAME=$AUTOSCALING_GROUP_NAME" - echo "ASSETS_S3_PATH=$ASSETS_S3_PATH" - echo "DATA_VOLUME_TYPE=$DATA_VOLUME_TYPE" - echo "DATA_VOLUME_SIZE=$DATA_VOLUME_SIZE" -} >> /etc/environment - -arch=$(uname -m) - -echo "Architecture detected: $arch" - -if [ "$arch" == "x86_64" ]; then - SSM_AGENT_BINARY_URI=https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm - AWS_CLI_BINARY_URI=https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip - S5CMD_URI=https://github.com/peak/s5cmd/releases/download/v2.1.0/s5cmd_2.1.0_Linux-64bit.tar.gz - YQ_URI=https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -else - SSM_AGENT_BINARY_URI=https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_arm64/amazon-ssm-agent.rpm - AWS_CLI_BINARY_URI=https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip - S5CMD_URI=https://github.com/peak/s5cmd/releases/download/v2.1.0/s5cmd_2.1.0_Linux-arm64.tar.gz - YQ_URI=https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64 -fi - -echo "Updating and installing required system packages" -yum update -y -yum -y install amazon-cloudwatch-agent collectd jq yq gcc ncurses-devel aws-cfn-bootstrap zstd -wget $YQ_URI -O /usr/bin/yq && chmod +x /usr/bin/yq - -echo " Installing aria2 a p2p downloader" -cd /tmp - -if [ "$arch" == "x86_64" ]; then - wget https://github.com/q3aql/aria2-static-builds/releases/download/v1.36.0/aria2-1.36.0-linux-gnu-64bit-build1.tar.bz2 - tar jxvf aria2-1.36.0-linux-gnu-64bit-build1.tar.bz2 - cd aria2-1.36.0-linux-gnu-64bit-build1/ - make install -else - wget https://github.com/q3aql/aria2-static-builds/releases/download/v1.36.0/aria2-1.36.0-linux-gnu-arm-rbpi-build1.tar.bz2 - tar jxvf aria2-1.36.0-linux-gnu-arm-rbpi-build1.tar.bz2 - cd aria2-1.36.0-linux-gnu-arm-rbpi-build1/ - make install -fi - -echo " Installing s5cmd" -cd /opt -wget -q $S5CMD_URI -O s5cmd.tar.gz -tar -xf s5cmd.tar.gz -chmod +x s5cmd -mv s5cmd /usr/bin -s5cmd version - -cd /opt - -echo "Downloading assets zip file" -aws s3 cp $ASSETS_S3_PATH ./assets.zip -unzip -q assets.zip - -echo 'Uninstalling AWS CLI v1' -yum remove awscli - -echo 'Installing AWS CLI v2' -curl $AWS_CLI_BINARY_URI -o "awscliv2.zip" -unzip -q awscliv2.zip -./aws/install -rm /usr/bin/aws -ln /usr/local/bin/aws /usr/bin/aws - -echo 'Installing SSM Agent' -yum install -y $SSM_AGENT_BINARY_URI - -# Base specific setup starts here - -# Set by Base-specic CDK components and stacks -REGION=${_REGION_} -STACK_NAME=${_STACK_NAME_} -RESTORE_FROM_SNAPSHOT=${_RESTORE_FROM_SNAPSHOT_} -NETWORK_ID=${_NETWORK_ID_} -NODE_CONFIG=${_NODE_CONFIG_} -L1_EXECUTION_ENDPOINT=${_L1_EXECUTION_ENDPOINT_} -L1_CONSENSUS_ENDPOINT=${_L1_CONSENSUS_ENDPOINT_} -SNAPSHOT_URL=${_SNAPSHOT_URL_} - -{ - echo "REGION=$REGION" - echo "NETWORK_ID=$NETWORK_ID" - echo "NODE_CONFIG=$NODE_CONFIG" - echo "L1_EXECUTION_ENDPOINT=$L1_EXECUTION_ENDPOINT" - echo "L1_CONSENSUS_ENDPOINT=$L1_CONSENSUS_ENDPOINT" - echo "SNAPSHOT_URL=$SNAPSHOT_URL" -} >> /etc/environment - -GIT_URL=https://github.com/base-org/node.git -SYNC_CHECKER_FILE_NAME=syncchecker-base.sh - -yum -y install docker python3-pip cronie cronie-anacron gcc python3-devel git -yum -y remove python-requests -pip3 install docker-compose -pip3 install hapless -pip3 uninstall -y urllib3 -pip3 install 'urllib3<2.0' - -echo "Assigning Swap Space" -# Check if a swap file already exists -if [ -f /swapfile ]; then - # Remove the existing swap file - swapoff /swapfile - rm -rf /swapfile -fi - -# Create a new swap file -# Set swap size to fixed 5 GB -swap_size_mb=5120 -unit=M -fallocate -l $swap_size_mb$unit /swapfile -chmod 600 /swapfile -mkswap /swapfile -swapon /swapfile - -# Enable the swap space to persist after reboot. -echo "/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab - -sysctl vm.swappiness=6 -sysctl vm.vfs_cache_pressure=10 -echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf -echo "vm.vfs_cache_pressure=10" | sudo tee -a /etc/sysctl.conf - -free -h - -mkdir -p /data - -# Creating run user and making sure it has all necessary permissions -groupadd -g 1002 bcuser -useradd -u 1002 -g 1002 -m -s /bin/bash bcuser -usermod -a -G docker bcuser -usermod -a -G docker ec2-user -chmod -R 755 /home/bcuser - -echo "Starting docker" -service docker start -systemctl enable docker - -echo "Clonning node repo" -cd /home/bcuser -git clone $GIT_URL -cd ./node - -echo "Configuring node" - -case $NETWORK_ID in - "mainnet") - OP_CONFIG_FILE_PATH=/home/bcuser/node/.env.mainnet - ;; - "sepolia") - OP_CONFIG_FILE_PATH=/home/bcuser/node/.env.sepolia - ;; - *) - echo "Network id is not valid." - exit 1 - ;; -esac - -case $NODE_CONFIG in - "full") - echo "OP_GETH_GCMODE=full" >> $OP_CONFIG_FILE_PATH - ;; - "archive") - echo "OP_GETH_GCMODE=archive" >> $OP_CONFIG_FILE_PATH - ;; - *) - echo "Network id is not valid." - exit 1 - ;; -esac - -case $NETWORK_ID in - "mainnet") - sed -i "s#OP_NODE_L1_ETH_RPC=https://1rpc.io/eth#OP_NODE_L1_ETH_RPC=$L1_EXECUTION_ENDPOINT#g" $OP_CONFIG_FILE_PATH - sed -i '/.env.mainnet/s/^#//g' /home/bcuser/node/docker-compose.yml - sed -i '/OP_NODE_L1_BEACON/s/^#//g' $OP_CONFIG_FILE_PATH - sed -i "s#OP_NODE_L1_BEACON=https://your.mainnet.beacon.node/endpoint-here#OP_NODE_L1_BEACON=$L1_CONSENSUS_ENDPOINT#g" $OP_CONFIG_FILE_PATH - ;; - "sepolia") - sed -i "s#OP_NODE_L1_ETH_RPC=https://rpc.sepolia.org#OP_NODE_L1_ETH_RPC=$L1_EXECUTION_ENDPOINT#g" $OP_CONFIG_FILE_PATH - sed -i "/.env.sepolia/s/^#//g" /home/bcuser/node/docker-compose.yml - sed -i '/OP_NODE_L1_BEACON/s/^#//g' $OP_CONFIG_FILE_PATH - sed -i "s#OP_NODE_L1_BEACON=https://your.sepolia.beacon.node/endpoint-here#OP_NODE_L1_BEACON=$L1_CONSENSUS_ENDPOINT#g" $OP_CONFIG_FILE_PATH - ;; - *) - echo "Network id is not valid." - exit 1 - ;; -esac - -echo "OP_NODE_L1_TRUST_RPC=true" >> $OP_CONFIG_FILE_PATH - -sed -i "s#GETH_HOST_DATA_DIR=./geth-data#GETH_HOST_DATA_DIR=/data/geth#g" /home/bcuser/node/.env - -chown -R bcuser:bcuser /home/bcuser/node - -echo "Configuring syncchecker script" -cp /opt/sync-checker/$SYNC_CHECKER_FILE_NAME /opt/syncchecker.sh -chmod 766 /opt/syncchecker.sh - -echo "*/5 * * * * /opt/syncchecker.sh" | crontab -crontab -l - -echo "Configuring node as a service" -mkdir /home/bcuser/bin -mv /opt/base/node-start.sh /home/bcuser/bin/node-start.sh -mv /opt/base/node-stop.sh /home/bcuser/bin/node-stop.sh -chmod 766 /home/bcuser/bin/* -chown -R bcuser:bcuser /home/bcuser - -sudo bash -c 'cat > /etc/systemd/system/base.service </tmp/setup-instance-store-volumes.log 2>&1") | crontab - - crontab -l - - DATA_VOLUME_ID=/dev/$(lsblk -lnb | awk 'max < $4 {max = $4; vol = $1} END {print vol}') - -else - echo "Data volume type is EBS" - - DATA_VOLUME_ID=/dev/$(lsblk -lnb | awk -v VOLUME_SIZE_BYTES="$DATA_VOLUME_SIZE" '{if ($4== VOLUME_SIZE_BYTES) {print $1}}') -fi - -mkfs -t ext4 $DATA_VOLUME_ID -echo "waiting for volume to get UUID" - OUTPUT=0; - while [ "$OUTPUT" = 0 ]; do - DATA_VOLUME_UUID=$(lsblk -fn -o UUID $DATA_VOLUME_ID) - OUTPUT=$(echo $DATA_VOLUME_UUID | grep -c - $2) - echo $OUTPUT - done -DATA_VOLUME_FSTAB_CONF="UUID=$DATA_VOLUME_UUID /data ext4 defaults 0 2" -echo "DATA_VOLUME_ID="$DATA_VOLUME_ID -echo "DATA_VOLUME_UUID="$DATA_VOLUME_UUID -echo "DATA_VOLUME_FSTAB_CONF="$DATA_VOLUME_FSTAB_CONF -echo $DATA_VOLUME_FSTAB_CONF | tee -a /etc/fstab -mount -a - -lsblk -d - -chown -R bcuser:bcuser /data -chmod -R 755 /data - -echo 'Configuring CloudWatch Agent' -cp /opt/cw-agent.json /opt/aws/amazon-cloudwatch-agent/etc/custom-amazon-cloudwatch-agent.json - -echo "Starting CloudWatch Agent" -/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \ --a fetch-config -c file:/opt/aws/amazon-cloudwatch-agent/etc/custom-amazon-cloudwatch-agent.json -m ec2 -s -systemctl restart amazon-cloudwatch-agent - -if [ "$RESTORE_FROM_SNAPSHOT" == "false" ]; then - echo "Skipping restoration from snapshot. Starting node" - systemctl daemon-reload - systemctl enable --now base -else - echo "Restoring full node from snapshot over http" - chmod +x /opt/restore-from-snapshot-http.sh - echo "/opt/restore-from-snapshot-http.sh" | at now + 1 min -fi - -if [[ "$LIFECYCLE_HOOK_NAME" != "none" ]]; then - echo "Signaling ASG lifecycle hook to complete" - TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") - INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id) - aws autoscaling complete-lifecycle-action --lifecycle-action-result CONTINUE --instance-id $INSTANCE_ID --lifecycle-hook-name "$LIFECYCLE_HOOK_NAME" --auto-scaling-group-name "$AUTOSCALING_GROUP_NAME" --region $REGION -fi - -echo "All Done!!" -set -e diff --git a/lib/base/lib/config/baseConfig.interface.ts b/lib/base/lib/config/baseConfig.interface.ts index b6d0fc3f..29f1f3f8 100644 --- a/lib/base/lib/config/baseConfig.interface.ts +++ b/lib/base/lib/config/baseConfig.interface.ts @@ -1,6 +1,7 @@ import * as configTypes from "../../../constructs/config.interface"; export type BaseNetworkId = "mainnet" | "sepolia"; +export type BaseClient = "geth" | "reth"; export type BaseNodeConfiguration = "full" | "archive"; export {AMBEthereumNodeNetworkId} from "../../../constructs/config.interface"; @@ -16,6 +17,7 @@ export interface BaseBaseConfig extends configTypes.BaseConfig { export interface BaseBaseNodeConfig extends configTypes.BaseNodeConfig { baseNetworkId: BaseNetworkId; + baseClient: BaseClient; baseNodeConfiguration: BaseNodeConfiguration; dataVolume: BaseDataVolumeConfig; restoreFromSnapshot: boolean; diff --git a/lib/base/lib/config/baseConfig.ts b/lib/base/lib/config/baseConfig.ts index 26727b08..557f3fc4 100644 --- a/lib/base/lib/config/baseConfig.ts +++ b/lib/base/lib/config/baseConfig.ts @@ -18,16 +18,28 @@ const parseDataVolumeType = (dataVolumeType: string) => { } } +const getClientConfiguration = (client: configTypes.BaseClient, nodeConfiguration: configTypes.BaseNodeConfiguration) => { + switch (client) { + case "reth": + return "archive"; + case "geth": + return "full"; + default: + return "full"; + } +} + export const baseConfig: configTypes.BaseBaseConfig = { accountId: process.env.AWS_ACCOUNT_ID || "xxxxxxxxxxx", - region: process.env.AWS_REGION || "us-east-2", + region: process.env.AWS_REGION || "us-east-1", } export const baseNodeConfig: configTypes.BaseBaseNodeConfig = { instanceType: new ec2.InstanceType(process.env.BASE_INSTANCE_TYPE ? process.env.BASE_INSTANCE_TYPE : "m7g.2xlarge"), instanceCpuType: process.env.BASE_CPU_TYPE?.toLowerCase() == "x86_64" ? ec2.AmazonLinuxCpuType.X86_64 : ec2.AmazonLinuxCpuType.ARM_64, baseNetworkId: process.env.BASE_NETWORK_ID || "mainnet", - baseNodeConfiguration: process.env.BASE_NODE_CONFIGURATION || "full", + baseClient: process.env.BASE_CLIENT || "geth", + baseNodeConfiguration: getClientConfiguration( process.env.BASE_CLIENT, process.env.BASE_NODE_CONFIGURATION), restoreFromSnapshot: process.env.BASE_RESTORE_FROM_SNAPSHOT?.toLowerCase() == "true" ? true : false, l1ExecutionEndpoint: process.env.BASE_L1_EXECUTION_ENDPOINT || constants.NoneValue, l1ConsensusEndpoint: process.env.BASE_L1_CONSENSUS_ENDPOINT || constants.NoneValue, diff --git a/lib/base/lib/ha-nodes-stack.ts b/lib/base/lib/ha-nodes-stack.ts index d876281a..3edbe53d 100644 --- a/lib/base/lib/ha-nodes-stack.ts +++ b/lib/base/lib/ha-nodes-stack.ts @@ -16,6 +16,7 @@ export interface BaseHANodesStackProps extends cdk.StackProps { instanceType: ec2.InstanceType; instanceCpuType: ec2.AmazonLinuxCpuType; baseNetworkId: configTypes.BaseNetworkId; + baseClient: configTypes.BaseClient, baseNodeConfiguration: configTypes.BaseNodeConfiguration; restoreFromSnapshot: boolean; l1ExecutionEndpoint: string, @@ -31,7 +32,7 @@ export class BaseHANodesStack extends cdk.Stack { constructor(scope: cdkConstructs.Construct, id: string, props: BaseHANodesStackProps) { super(scope, id, props); - const REGION = cdk.Stack.of(this).region; + const AWS_REGION = cdk.Stack.of(this).region; const STACK_NAME = cdk.Stack.of(this).stackName; const lifecycleHookName = STACK_NAME; const autoScalingGroupName = STACK_NAME; @@ -40,6 +41,7 @@ export class BaseHANodesStack extends cdk.Stack { instanceType, instanceCpuType, baseNetworkId, + baseClient, baseNodeConfiguration, restoreFromSnapshot, l1ExecutionEndpoint, @@ -69,17 +71,18 @@ export class BaseHANodesStack extends cdk.Stack { asset.bucket.grantRead(instanceRole); // parsing user data script and injecting necessary variables - const nodeScript = fs.readFileSync(path.join(__dirname, "assets", "user-data", "node.sh")).toString(); + const nodeScript = fs.readFileSync(path.join(__dirname, "assets", "user-data-alinux.sh")).toString(); const dataVolumeSizeBytes = dataVolume.sizeGiB * constants.GibibytesToBytesConversionCoefficient; const modifiedInitNodeScript = cdk.Fn.sub(nodeScript, { - _REGION_: REGION, + _AWS_REGION_: AWS_REGION, _ASSETS_S3_PATH_: `s3://${asset.s3BucketName}/${asset.s3ObjectKey}`, _STACK_NAME_: STACK_NAME, _NODE_CF_LOGICAL_ID_: constants.NoneValue, _DATA_VOLUME_TYPE_: dataVolume.type, _DATA_VOLUME_SIZE_: dataVolumeSizeBytes.toString(), _NETWORK_ID_: baseNetworkId, + _BASE_CLIENT_: baseClient, _NODE_CONFIG_: baseNodeConfiguration, _LIFECYCLE_HOOK_NAME_: lifecycleHookName, _AUTOSCALING_GROUP_NAME_: autoScalingGroupName, @@ -94,9 +97,9 @@ export class BaseHANodesStack extends cdk.Stack { instanceType, dataVolumes: [dataVolume], machineImage: new ec2.AmazonLinuxImage({ - generation: AmazonLinuxGeneration.AMAZON_LINUX_2, - kernel:ec2.AmazonLinuxKernel.KERNEL5_X, - cpuType: instanceCpuType + generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023, + kernel:ec2.AmazonLinuxKernel.KERNEL6_1, + cpuType: instanceCpuType, }), role: instanceRole, vpc, diff --git a/lib/base/lib/single-node-stack.ts b/lib/base/lib/single-node-stack.ts index 8c6eda38..c7115451 100644 --- a/lib/base/lib/single-node-stack.ts +++ b/lib/base/lib/single-node-stack.ts @@ -17,6 +17,7 @@ export interface BaseSingleNodeStackProps extends cdk.StackProps { instanceType: ec2.InstanceType; instanceCpuType: ec2.AmazonLinuxCpuType; baseNetworkId: configTypes.BaseNetworkId; + baseClient: configTypes.BaseClient, baseNodeConfiguration: configTypes.BaseNodeConfiguration; restoreFromSnapshot: boolean; l1ExecutionEndpoint: string, @@ -30,17 +31,18 @@ export class BaseSingleNodeStack extends cdk.Stack { super(scope, id, props); // Setting up necessary environment variables - const REGION = cdk.Stack.of(this).region; + const AWS_REGION = cdk.Stack.of(this).region; const STACK_NAME = cdk.Stack.of(this).stackName; const STACK_ID = cdk.Stack.of(this).stackId; const availabilityZones = cdk.Stack.of(this).availabilityZones; - const chosenAvailabilityZone = availabilityZones.slice(0, 1)[0]; + const chosenAvailabilityZone = availabilityZones.slice(0, 2)[1]; // Getting our config from initialization properties const { instanceType, instanceCpuType, baseNetworkId, + baseClient, baseNodeConfiguration, restoreFromSnapshot, l1ExecutionEndpoint, @@ -82,7 +84,8 @@ export class BaseSingleNodeStack extends cdk.Stack { dataVolumes: [dataVolume], rootDataVolumeDeviceName: "/dev/xvda", machineImage: new ec2.AmazonLinuxImage({ - generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2, + generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023, + kernel:ec2.AmazonLinuxKernel.KERNEL6_1, cpuType: instanceCpuType, }), vpc, @@ -95,17 +98,18 @@ export class BaseSingleNodeStack extends cdk.Stack { }); // Parsing user data script and injecting necessary variables - const nodeStartScript = fs.readFileSync(path.join(__dirname, "assets", "user-data", "node.sh")).toString(); + const nodeStartScript = fs.readFileSync(path.join(__dirname, "assets", "user-data-alinux.sh")).toString(); const dataVolumeSizeBytes = dataVolume.sizeGiB * constants.GibibytesToBytesConversionCoefficient; const modifiedInitNodeScript = cdk.Fn.sub(nodeStartScript, { - _REGION_: REGION, + _AWS_REGION_: AWS_REGION, _ASSETS_S3_PATH_: `s3://${asset.s3BucketName}/${asset.s3ObjectKey}`, _STACK_NAME_: STACK_NAME, _NODE_CF_LOGICAL_ID_: node.nodeCFLogicalId, _DATA_VOLUME_TYPE_: dataVolume.type, _DATA_VOLUME_SIZE_: dataVolumeSizeBytes.toString(), _NETWORK_ID_: baseNetworkId, + _BASE_CLIENT_: baseClient, _NODE_CONFIG_: baseNodeConfiguration, _LIFECYCLE_HOOK_NAME_: constants.NoneValue, _AUTOSCALING_GROUP_NAME_: constants.NoneValue, @@ -122,7 +126,7 @@ export class BaseSingleNodeStack extends cdk.Stack { const dashboardString = cdk.Fn.sub(JSON.stringify(nodeCwDashboard.SyncNodeCWDashboardJSON), { INSTANCE_ID:node.instanceId, INSTANCE_NAME: STACK_NAME, - REGION: REGION, + REGION: AWS_REGION, }) new cw.CfnDashboard(this, 'base-cw-dashboard', { diff --git a/lib/base/sample-configs/.env-sample-archive-mainet b/lib/base/sample-configs/.env-sample-archive-mainet new file mode 100644 index 00000000..a5795fbf --- /dev/null +++ b/lib/base/sample-configs/.env-sample-archive-mainet @@ -0,0 +1,37 @@ +############################################################# +# Example configuration for Base nodes runner app on AWS # +############################################################# + +## Set the AWS account is and region for your environment ## +AWS_ACCOUNT_ID="xxxxxxxx" +AWS_REGION="us-east-1" + +## Common configuration parameters ## +BASE_CLIENT="reth" # All options: "geth", "reth" +BASE_NETWORK_ID="mainnet" # All options: "mainnet", "sepolia" +BASE_NODE_CONFIGURATION="full" # All options: "full", "archive" +BASE_INSTANCE_TYPE="i8g.4xlarge" # Recommended for Insance Store: i8g.4xlarge, "ARM_64" +BASE_CPU_TYPE="ARM_64" # All options: "x86_64", "ARM_64". IMPORTANT: Make sure the CPU type matches the instance type used + +# Data volume configuration +BASE_DATA_VOL_TYPE="instance-store" # Other options: "io1" | "io2" | "gp3" | "instance-store" . IMPORTANT: Use "instance-store" option only with instance types that support that feature, like popular for node im4gn, d3, i3en, and i4i instance families +#BASE_DATA_VOL_SIZE="1000" # Current required data size in GB to keep both snapshot archive and unarchived version of it. For Sepolia 1000 will be sufficient. +#BASE_DATA_VOL_IOPS="5000" # Max IOPS for EBS volumes (not applicable for "instance-store") +#BASE_DATA_VOL_THROUGHPUT="700" # Max throughput for EBS gp3 volumes (not applicable for "io1" | "io2" | "instance-store") +BASE_L1_EXECUTION_ENDPOINT="https://ethereum-rpc.publicnode.com" # Set your own URL to Ethereum L1 node: https://docs.base.org/tools/node-providers +BASE_L1_CONSENSUS_ENDPOINT="https://ethereum-beacon-api.publicnode.com" + +BASE_RESTORE_FROM_SNAPSHOT="true" # Download snapshot to speed up statup time +BASE_SNAPSHOT_URL="none" # Optionally provide the URL to download snpashot: https://docs.base.org/tutorials/run-a-base-node/#snapshots + +# Example for Sepolia: +#BASE_L1_EXECUTION_ENDPOINT=https://ethereum-sepolia-rpc.publicnode.com +#BASE_L1_CONSENSUS_ENDPOINT=https://ethereum-sepolia-beacon-api.publicnode.com +# Example for Mainnet and with Ethereum Blueprint with Geth-Lighthouse client combination and private IP: +#BASE_L1_EXECUTION_ENDPOINT=http://172.31.15.220:8545 +#BASE_L1_CONSENSUS_ENDPOINT=http://172.31.15.220:5052 + +## HA nodes configuration ## +BASE_HA_NUMBER_OF_NODES="2" # Total number of RPC nodes to be provisioned. Default: 2 +BASE_HA_ALB_HEALTHCHECK_GRACE_PERIOD_MIN="500" # Time enough to initialize the instance +BASE_HA_NODES_HEARTBEAT_DELAY_MIN="120" # Time sufficient enough for a node do sync diff --git a/lib/base/sample-configs/.env-sample-archive-sepolia b/lib/base/sample-configs/.env-sample-archive-sepolia index b55c9d91..c94b9637 100644 --- a/lib/base/sample-configs/.env-sample-archive-sepolia +++ b/lib/base/sample-configs/.env-sample-archive-sepolia @@ -7,6 +7,7 @@ AWS_ACCOUNT_ID="xxxxxxxx" AWS_REGION="us-east-1" ## Common configuration parameters ## +BASE_CLIENT="reth" # All options: "geth", "reth" BASE_NETWORK_ID="sepolia" # All options: "mainnet", "sepolia" BASE_NODE_CONFIGURATION="archive" # All options: "full", "archive" BASE_INSTANCE_TYPE="m7g.2xlarge" # Reconneded for Insance Store: i3en.3xlarge, "x86_64" diff --git a/lib/base/sample-configs/.env-sample-full-ha-sepolia b/lib/base/sample-configs/.env-sample-full-ha-sepolia index 62a43370..7ca3cfcb 100644 --- a/lib/base/sample-configs/.env-sample-full-ha-sepolia +++ b/lib/base/sample-configs/.env-sample-full-ha-sepolia @@ -7,6 +7,7 @@ AWS_ACCOUNT_ID="xxxxxxxx" AWS_REGION="us-east-1" ## Common configuration parameters ## +BASE_CLIENT="geth" # All options: "geth", "reth" BASE_NETWORK_ID="sepolia" # All options: "mainnet", "sepolia" BASE_NODE_CONFIGURATION="full" # All options: "full", "archive" BASE_INSTANCE_TYPE="m7g.2xlarge" # Reconneded for Insance Store: i3en.3xlarge, "x86_64" diff --git a/lib/base/sample-configs/.env-sample-full-mainet b/lib/base/sample-configs/.env-sample-full-mainet new file mode 100644 index 00000000..5a15716d --- /dev/null +++ b/lib/base/sample-configs/.env-sample-full-mainet @@ -0,0 +1,37 @@ +############################################################# +# Example configuration for Base nodes runner app on AWS # +############################################################# + +## Set the AWS account is and region for your environment ## +AWS_ACCOUNT_ID="xxxxxxxx" +AWS_REGION="us-east-1" + +## Common configuration parameters ## +BASE_CLIENT="geth" # All options: "geth", "reth" +BASE_NETWORK_ID="mainnet" # All options: "mainnet", "sepolia" +BASE_NODE_CONFIGURATION="full" # All options: "full", "archive" +BASE_INSTANCE_TYPE="i8g.4xlarge" # Reconneded for Insance Store: i3en.3xlarge, "x86_64" +BASE_CPU_TYPE="ARM_64" # All options: "x86_64", "ARM_64". IMPORTANT: Make sure the CPU type matches the instance type used + +# Data volume configuration +BASE_DATA_VOL_TYPE="instance-store" # Other options: "io1" | "io2" | "gp3" | "instance-store" . IMPORTANT: Use "instance-store" option only with instance types that support that feature, like popular for node im4gn, d3, i3en, and i4i instance families +#BASE_DATA_VOL_SIZE="1000" # Current required data size in GB to keep both snapshot archive and unarchived version of it. For Sepolia 1000 will be sufficient. +#BASE_DATA_VOL_IOPS="5000" # Max IOPS for EBS volumes (not applicable for "instance-store") +#BASE_DATA_VOL_THROUGHPUT="700" # Max throughput for EBS gp3 volumes (not applicable for "io1" | "io2" | "instance-store") +BASE_L1_EXECUTION_ENDPOINT="https://ethereum-rpc.publicnode.com" # Set your own URL to Ethereum L1 node: https://docs.base.org/tools/node-providers +BASE_L1_CONSENSUS_ENDPOINT="https://ethereum-beacon-api.publicnode.com" + +BASE_RESTORE_FROM_SNAPSHOT="true" # Download snapshot to speed up statup time +BASE_SNAPSHOT_URL="none" # Optionally provide the URL to download snpashot: https://docs.base.org/tutorials/run-a-base-node/#snapshots + +# Example for Sepolia: +#BASE_L1_EXECUTION_ENDPOINT=https://ethereum-sepolia-rpc.publicnode.com +#BASE_L1_CONSENSUS_ENDPOINT=https://ethereum-sepolia-beacon-api.publicnode.com +# Example for Mainnet and with Ethereum Blueprint with Geth-Lighthouse client combination and private IP: +#BASE_L1_EXECUTION_ENDPOINT=http://172.31.15.220:8545 +#BASE_L1_CONSENSUS_ENDPOINT=http://172.31.15.220:5052 + +## HA nodes configuration ## +BASE_HA_NUMBER_OF_NODES="2" # Total number of RPC nodes to be provisioned. Default: 2 +BASE_HA_ALB_HEALTHCHECK_GRACE_PERIOD_MIN="500" # Time enough to initialize the instance +BASE_HA_NODES_HEARTBEAT_DELAY_MIN="120" # Time sufficient enough for a node do sync diff --git a/lib/base/sample-configs/.env-sample-full-sepolia b/lib/base/sample-configs/.env-sample-full-sepolia index 62a43370..7e55a10a 100644 --- a/lib/base/sample-configs/.env-sample-full-sepolia +++ b/lib/base/sample-configs/.env-sample-full-sepolia @@ -7,7 +7,7 @@ AWS_ACCOUNT_ID="xxxxxxxx" AWS_REGION="us-east-1" ## Common configuration parameters ## -BASE_NETWORK_ID="sepolia" # All options: "mainnet", "sepolia" +BASE_CLIENT="geth" # All options: "geth", "reth" BASE_NODE_CONFIGURATION="full" # All options: "full", "archive" BASE_INSTANCE_TYPE="m7g.2xlarge" # Reconneded for Insance Store: i3en.3xlarge, "x86_64" BASE_CPU_TYPE="ARM_64" # All options: "x86_64", "ARM_64". IMPORTANT: Make sure the CPU type matches the instance type used diff --git a/lib/base/test/.env-test b/lib/base/test/.env-test index 2796c5d3..830dcb72 100644 --- a/lib/base/test/.env-test +++ b/lib/base/test/.env-test @@ -7,6 +7,7 @@ AWS_ACCOUNT_ID="xxxxxxxxxxxx" AWS_REGION="us-east-1" # Regions supported by Amazon Managed Blockchain Access Ethereum: https://docs.aws.amazon.com/general/latest/gr/managedblockchain.html#managedblockchain-access ## Common configuration parameters ## +BASE_CLIENT="geth" # All options: "geth", "reth" BASE_NETWORK_ID="mainnet" # All options: "mainnet" BASE_NODE_CONFIGURATION="full" # All options: "full", "archive" BASE_INSTANCE_TYPE="m7g.4xlarge" diff --git a/lib/base/test/base-single-node.test.ts b/lib/base/test/base-single-node.test.ts index 809efc53..7dae18ba 100644 --- a/lib/base/test/base-single-node.test.ts +++ b/lib/base/test/base-single-node.test.ts @@ -15,18 +15,9 @@ describe("BaseSingleNodeStack", () => { // Create the BaseSingleNodeStack. baseSingleNodeStack = new BaseSingleNodeStack(app, "base-single-node", { - stackName: `base-single-node-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, + stackName: `base-single-node-${config.baseNodeConfig.baseClient}-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, env: { account: config.baseConfig.accountId, region: config.baseConfig.region }, - - instanceType: config.baseNodeConfig.instanceType, - instanceCpuType: config.baseNodeConfig.instanceCpuType, - baseNetworkId: config.baseNodeConfig.baseNetworkId, - baseNodeConfiguration: config.baseNodeConfig.baseNodeConfiguration, - restoreFromSnapshot: config.baseNodeConfig.restoreFromSnapshot, - l1ExecutionEndpoint: config.baseNodeConfig.l1ExecutionEndpoint, - l1ConsensusEndpoint: config.baseNodeConfig.l1ConsensusEndpoint, - snapshotUrl: config.baseNodeConfig.snapshotUrl, - dataVolume: config.baseNodeConfig.dataVolume, + ...config.baseNodeConfig }); template = Template.fromStack(baseSingleNodeStack); @@ -147,7 +138,7 @@ describe("BaseSingleNodeStack", () => { "Fn::Join": [ "", [ - "base-single-node-full-mainnet-", + "base-single-node-geth-full-mainnet-", { "Ref": Match.anyValue() } diff --git a/lib/base/test/ha-nodes-stack.test.ts b/lib/base/test/ha-nodes-stack.test.ts index 09a2473e..10344794 100644 --- a/lib/base/test/ha-nodes-stack.test.ts +++ b/lib/base/test/ha-nodes-stack.test.ts @@ -12,22 +12,11 @@ describe("BaseHANodesStack", () => { // Create the BaseHANodesStack. const baseHANodesStack = new BaseHANodesStack(app, "base-sync-node", { - stackName: `base-ha-nodes-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, + stackName: `base-ha-nodes-${config.baseNodeConfig.baseClient}-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, env: { account: config.baseConfig.accountId, region: config.baseConfig.region }, - instanceType: config.baseNodeConfig.instanceType, - instanceCpuType: config.baseNodeConfig.instanceCpuType, - baseNetworkId: config.baseNodeConfig.baseNetworkId, - baseNodeConfiguration: config.baseNodeConfig.baseNodeConfiguration, - restoreFromSnapshot: config.baseNodeConfig.restoreFromSnapshot, - l1ExecutionEndpoint: config.baseNodeConfig.l1ExecutionEndpoint, - l1ConsensusEndpoint: config.baseNodeConfig.l1ConsensusEndpoint, - snapshotUrl: config.baseNodeConfig.snapshotUrl, - dataVolume: config.baseNodeConfig.dataVolume, - - albHealthCheckGracePeriodMin: config.haNodeConfig.albHealthCheckGracePeriodMin, - heartBeatDelayMin: config.haNodeConfig.heartBeatDelayMin, - numberOfNodes: config.haNodeConfig.numberOfNodes + ...config.baseNodeConfig, + ...config.haNodeConfig }); // Prepare the stack for assertions. @@ -158,7 +147,7 @@ describe("BaseHANodesStack", () => { // Has Auto Scaling Group. template.hasResourceProperties("AWS::AutoScaling::AutoScalingGroup", { - AutoScalingGroupName: `base-ha-nodes-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, + AutoScalingGroupName: `base-ha-nodes-${config.baseNodeConfig.baseClient}-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, HealthCheckGracePeriod: config.haNodeConfig.albHealthCheckGracePeriodMin * 60, HealthCheckType: "ELB", DefaultInstanceWarmup: 60, @@ -173,7 +162,7 @@ describe("BaseHANodesStack", () => { template.hasResourceProperties("AWS::AutoScaling::LifecycleHook", { DefaultResult: "ABANDON", HeartbeatTimeout: config.haNodeConfig.heartBeatDelayMin * 60, - LifecycleHookName: `base-ha-nodes-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, + LifecycleHookName: `base-ha-nodes-${config.baseNodeConfig.baseClient}-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}`, LifecycleTransition: "autoscaling:EC2_INSTANCE_LAUNCHING", }); @@ -216,7 +205,7 @@ describe("BaseHANodesStack", () => { }, { Key: "access_logs.s3.prefix", - Value: `base-ha-nodes-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}` + Value: `base-ha-nodes-${config.baseNodeConfig.baseClient}-${config.baseNodeConfig.baseNodeConfiguration}-${config.baseNodeConfig.baseNetworkId}` } ], Scheme: "internal", diff --git a/lib/bitcoin-core/README.md b/lib/bitcoin-core/README.md new file mode 100644 index 00000000..2787e188 --- /dev/null +++ b/lib/bitcoin-core/README.md @@ -0,0 +1,439 @@ +## Sample AWS Blockchain Node Runner app for Bitcoin Nodes + +| Contributed by | +|:--------------------------------:| +| [Simon Goldberg](https://github.com/racket2000)| + +### Overview + +This guide walks you through deploying a Bitcoin Core mainnet node in a **Virtual Private Cloud (VPC)** using **Docker**, leveraging **AWS Secrets Manager** for secure credential handling. This configuration ensures robust security and performance while optimizing data transfer costs. + +--- + +## Well-Architected + +
+Review pros and cons of this solution. + +### Well-Architected Checklist + +This is the Well-Architected checklist for **Bitcoin Core node implementation** of the AWS Blockchain Node Runner app. This checklist takes into account questions from the [AWS Well-Architected Framework](https://aws.amazon.com/architecture/well-architected/) which are relevant to this workload. Please feel free to add more checks from the framework if required for your workload. + +| Pillar | Control | Question/Check | Remarks | +|:------------------------|:----------------------------------|:---------------------------------------------------------------------------------|:-----------------| +| Security | Network protection | Are there unnecessary open ports in security groups? | Port 8332 (RPC) is restricted to the VPC. | +| | | Traffic inspection | Optional: VPC Flow Logs or traffic mirroring can be enabled for deeper inspection. | +| | Compute protection | Reduce attack surface | This solution uses Amazon Linux 2 AMI. No SSH access is enabled; SSM is used. | +| | | Enable people to perform actions at a distance | This solution uses AWS Systems Manager Session Manager. | +| | Data protection at rest | Use encrypted Amazon Elastic Block Store (Amazon EBS) volumes | Encrypted Amazon EBS volumes are used. | +| | Data protection in transit | Use TLS | The AWS Application Load balancer currently uses HTTP listener. Create HTTPS listener with self signed certificate if TLS is desired. | +| | Authorization and access control | Use instance profile with Amazon Elastic Compute Cloud (Amazon EC2) instances | AWS IAM role is attached to the EC2 instance. | +| | | Following principle of least privilege access | IAM privileges are scoped down to what is necessary. | +| | Application security | Security focused development practices | `cdk-nag` is used with appropriate suppressions. | +| Cost optimization | Service selection | Use cost effective resources | Cost efficient T3 instances provide a baseline level of CPU performance with the ability to burst CPU usage at any time for as long as required. T3 instances are designed for applications with moderate CPU usage that experience temporary spikes in use. | +| Reliability | Resiliency implementation | Withstand component failures | Single node deployment. Can be extended with backup nodes and monitoring. | +| | Resource monitoring | How are workload resources monitored? | Amazon CloudWatch Dashboards track CPU, memory, disk, network, and block height. | +| Performance efficiency | Compute selection | How is compute solution selected? | Compute solution is selected based on performance needs and budget. | +| | Storage selection | How is storage solution selected? | EBS volumes (e.g. gp3 or io2) are selected for consistent throughput and IOPS. | +| Operational excellence | Workload health | How is health of workload determined? | Health is tracked using CloudWatch custom metrics including block height. | +| Sustainability | Hardware & services | Select most efficient hardware for your workload | T3A instances offer efficient memory utilization, reducing power and cost. | + +
+ +### Getting Started + +#### Open AWS CloudShell + +To begin, ensure you login to your AWS account with permissions to create and modify resources in IAM, EC2, EBS, VPC, S3, KMS, and Secrets Manager. + +From the AWS Management Console, open the [AWS CloudShell](https://docs.aws.amazon.com/cloudshell/latest/userguide/welcome.html), a web-based shell environment. If unfamiliar, review the [2-minute YouTube video](https://youtu.be/fz4rbjRaiQM) for an overview and check out [CloudShell with VPC environment](https://docs.aws.amazon.com/cloudshell/latest/userguide/creating-vpc-environment.html) that we'll use to test nodes API from internal IP address space. + +Once ready, you can run the commands to deploy and test blueprints in the CloudShell. + +#### Cloning the Repository + +First, clone the repository and install the dependencies: + +``` +git clone https://github.com/aws-samples/aws-blockchain-node-runners.git +cd aws-blockchain-node-runners +npm install +``` + +Before proceeding, ensure you have the AWS CLI installed and configured. + +### Configuration + +1. Make sure you are in the root directory of the cloned repository. + +2. If you have deleted or don't have the default VPC, create default VPC + +``` +aws ec2 create-default-vpc +``` +> **NOTE:** *You may see the following error if the default VPC already exists: `An error occurred (DefaultVpcAlreadyExists) when calling the CreateDefaultVpc operation: A Default VPC already exists for this account in this region.`. That means you can just continue with the following steps.* + +3. Create your own copy of `.env` file and edit it to update with your AWS Account ID and Region: + +```bash +cd lib/bitcoin-core +cp ./sample-configs/.env-sample-bitcoin-mainnet .env +vim .env +``` + +4. Deploy common components such as IAM role: + +```bash +npx cdk deploy BitcoinCommonStack +``` + +### Generating RPC Authentication + +To interact with the Bitcoin Core RPC endpoint within your isolated VPC environment, run the following command before deploying the Bitcoin Node via CDK: + +``` +# Make sure you are in aws-blockchain-node-runners/lib/bitcoin-core +node generateRPCAuth.js +``` + +For a deeper dive and an overview of credential rotation, see [RPC Authentication -- Deep Dive](#rpc-authentication----deep-dive). + + +### Deploying the Node + +To deploy a single node setup, use the following command: + +``` +npx cdk deploy SingleNodeBitcoinCoreStack --outputs-file single-node-outputs.json +``` + +For High Availability (HA) node deployment, use: + +``` +npx cdk deploy HABitcoinCoreNodeStack --outputs-file ha-nodes-outputs.json +``` + +### Deployment Architectures for Bitcoin Nodes + +#### Single Node Setup +![Single Node Deployment](./doc/assets/Bitcoin-Single-Node-Arch.png) + +- A **Bitcoin node** deployed in a **public subnet** continuously synchronizes with the Bitcoin network. +- Outbound peer-to-peer (P2P) communication flows through an **Internet Gateway (IGW)**. +- The node's security group permits incoming P2P connections on port 8333. +- The node's RPC methods can be accessed from within the VPC. +- The Bitcoin node sends various monitoring metrics to Amazon CloudWatch. + +#### High Availability (HA) Setup +![HA Node Deployment](./doc/assets/Bitcoin-HA-Nodes-Arch.png) + +- Deploying **multiple Bitcoin nodes** in an **Auto Scaling Group** enhances fault tolerance and availability. +- The nodes' RPC endpoints are exposed through an **Application Load Balancer (ALB)**. The ALB implements session persistence using a "stickiness cookie". This ensures that subsequent requests from the same client are consistently routed to the same node, maintaining session continuity. The stickiness duration is set to 90 minutes but can be configured for up to 7 days. Note: The Bitcoin Core nodes in the HA setup do not share state (e.g., wallet, mempool) +- HA nodes do not expose the RPC endpoint to the public internet. This endpoint can be accessed from within the VPC. + +--- + +### Accessing and Using bitcoin-cli on a Bitcoin Core Instance + +To interact with your Bitcoin Core instance, you'll need to use AWS Systems Manager, as direct SSH access is not available. + +Bitcoin Core supports cookie-based authentication by default, so interacting with the `bitcoin-cli` from the node itself does not require credentials. + +From your CloudShell terminal, run the following command to connect to your node via Systems Manager: + +``` +export INSTANCE_ID=$(jq -r '.SingleNodeBitcoinCoreStack.BitcoinNodeInstanceId' single-node-outputs.json) +echo "INSTANCE_ID="$INSTANCE_ID +aws ssm start-session --target $INSTANCE_ID --region $AWS_REGION +``` + +**Note**: You can alternatively connect to your node via Systems Manager in the AWS Console with the following steps: + - Open the AWS Console and navigate to EC2 Instances. + - Locate and select the instance named `SingleNodeBitcoinCoreStack/BitcoinSingleNode`. + - Click the "Connect" button. + - Choose "Session Manager" from the connection options. + - Select "Connect" to establish a session. + +**Execute an RPC Call:** +Once connected, you can interact with the Bitcoin Core node using Docker commands. + +To test the RPC interface, use the following command: + +``` +sudo docker exec -it bitcoind bitcoin-cli getblockchaininfo +``` + + This command executes the `getblockchaininfo` RPC method, which returns current state information about the blockchain. + +**Interpreting Results:** + - The output will provide detailed information about the current state of the blockchain, including the current block height, difficulty, and other relevant data. + - You can use similar commands to execute other RPC methods supported by Bitcoin Core. + +--- +### Secure RPC Access with AWS Secrets Manager + +For a client to securely interact with the Bitcoin Core RPC endpoint from a subnet within your VPC environment, AWS Secrets Manager is leveraged for credential storage and retrieval. + +#### Retrieving Credentials +First, retrieve the RPC credentials from AWS Secrets Manager in your CloudShell tab: + +``` +export BTC_RPC_AUTH=$(aws secretsmanager get-secret-value --secret-id bitcoin_rpc_credentials --query SecretString --output text) +echo "BTC_RPC_AUTH=$BTC_RPC_AUTH" +``` + +#### Single node RPC Call using credentials +To make an RPC call to a single Bitcoin node, run the following command to retrieve the private IP address of your Bitcoin node: + +``` +export BITCOIN_NODE_IP=$(jq -r '.SingleNodeBitcoinCoreStack.BitcoinNodePrivateIP' single-node-outputs.json) +echo "BITCOIN_NODE_IP=$BITCOIN_NODE_IP" +``` +Copy output from the last `echo` command with `BITCOIN_NODE_IP=` and open [CloudShell tab with VPC environment](https://docs.aws.amazon.com/cloudshell/latest/userguide/creating-vpc-environment.html) to access internal IP address space. Paste `BITCOIN_NODE_IP=` into the new CloudShell tab. + +Additionally, copy the output from the first `echo` command with `BTC_RPC_AUTH=` into the CloudShell VPC environment. + +Then query the node: + +``` +curl --user "$BTC_RPC_AUTH" \ + --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockchaininfo", "params": []}' \ + -H 'content-type: text/plain;' http://$BITCOIN_NODE_IP:8332/ +``` + +#### High Availability (HA) RPC Call using credentials + +Use the following command from your CloudShell terminal to retrieve your load balancer's DNS name: + +``` +export LOAD_BALANCER_DNS=$(jq -r '.HABitcoinCoreNodeStack.LoadBalancerDNS' ha-nodes-outputs.json) +echo LOAD_BALANCER_DNS=$LOAD_BALANCER_DNS +``` +Copy output from the last `echo` command with `RPC_ABL_URL=` and open [CloudShell tab with VPC environment](https://docs.aws.amazon.com/cloudshell/latest/userguide/creating-vpc-environment.html) to access internal IP address space. Paste `RPC_ABL_URL=` into the new CloudShell tab. + +Note: Make sure that you pasted `BTC_RPC_AUTH=` into the CloudShell VPC environment as well. + + Execute the following command to make an RPC request to your HA node setup: + +``` +curl --user "$BTC_RPC_AUTH" \ + --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockchaininfo", "params": []}' \ + -H 'content-type: text/plain;' \ + $LOAD_BALANCER_DNS +``` + +--- + + +### **Bitcoin Core: Creating an Encrypted Wallet for Payments** + +This guide covers how to create an encrypted Bitcoin Core wallet specifically designed for receiving and managing payments in a secure and efficient way. + +Note: Make sure that you run the following commands after accessing the node via Systems Manager. + +--- + +#### **1. Create an Encrypted Payment Wallet** + +To create a wallet specifically for handling payments, use the following command: + +``` +sudo docker exec -it bitcoind bitcoin-cli createwallet "payments" false false "my_secure_passphrase" +``` + +- **payments:** The wallet name, indicating its purpose. +- **passphrase:** A secure, memorable phrase to protect your funds. + +##### **Why Encrypt?** +- Protects against unauthorized access. +- Ensures funds are safe even if the server is compromised. + +--- + +#### **2. Generate a Receiving Address** + +To receive payments, generate a new address. You do not need to unlock the wallet for this step: + +``` +sudo docker exec -it bitcoind bitcoin-cli -rpcwallet="payments" getnewaddress "customer1" "bech32" +``` + +- **customer1:** Label to identify payments from this customer. +- **bech32:** Generates a SegWit address for lower transaction fees. + +**Example Output:** +``` +bc1qxyzabc123... (Bech32 address) +``` + +--- + +#### **3. Monitor Incoming Payments** + +To check the balance and verify received payments: + +``` +sudo docker exec -it bitcoind bitcoin-cli -rpcwallet="payments" getbalance +``` + +- Displays the total balance held in the wallet. + +To view detailed transactions: + +``` +sudo docker exec -it bitcoind bitcoin-cli -rpcwallet="payments" listtransactions +``` + +--- + +#### **4. Sending Payments (Requires Unlocking)** + +When making a payout or transferring funds, you need to unlock the wallet: + +``` +sudo docker exec -it bitcoind bitcoin-cli -rpcwallet="payments" walletpassphrase "my_secure_passphrase" 600 +``` + +- Unlocks the wallet for **600 seconds (10 minutes)**. + +#### **Send Bitcoin to a specified address:** + +``` +sudo docker exec -it bitcoind bitcoin-cli -rpcwallet="payments" sendtoaddress "bc1qrecipientaddress" 0.01 "Payment for service" +``` + +- Sends **0.01 BTC** with an optional label for record-keeping. + + +#### **5. Lock the Wallet After Use** + +For enhanced security, immediately lock the wallet after transactions: + +``` +sudo docker exec -it bitcoind bitcoin-cli -rpcwallet="payments" walletlock +``` + + + +#### **6. Backup the Wallet** + +To protect your payment data, back up the encrypted wallet regularly: + +``` +sudo docker exec -it bitcoind bitcoin-cli -rpcwallet="payments" backupwallet "/path/to/backup/payments.dat" +``` + + +#### **Security Tips for Payment Wallets** +- Use strong passphrases and store them securely offline. +- Regularly backup your wallet after creating new addresses or receiving payments. +- Consider setting up automated wallet backups to ensure data integrity. + +--- +### RPC Authentication -- Deep Dive + +The `generateRPCAuth.js` script is responsible for generating secure authentication credentials for your Bitcoin node. This script creates a randomly generated **username** and **password** along with a **salt**. The password and salt are then combined and hashed using the **SHA256** algorithm to produce a secure **hash**. This hash is combined with the username to generate the final **rpcauth** parameter that is appended to the `bitcoin.conf` file. + +The final `rpcauth` line in `bitcoin.conf` looks like this: + +``` +rpcauth=user_258:c220c5f38690bf880f0dd177547e55f7$77c6ec2dd90e792d60450b01a84cc8c2563a7fb1d0fbd73de49be818fde4b407 +``` + +- The **rpcauth** part consists of a **username**, **salt**, and a **hashed password**, providing robust protection in the case that your `bitcoin.conf` is accessed by an unauthorized entity. +- The randomly generated **username** and **password** are securely stored in **AWS Secrets Manager**. + +By using this script, it ensures that your node has unique and secure credentials. + +### Rotating RPC Secrets + +To maintain security, rotate RPC credentials periodically using the `generateRPCAuth.js` script: + +``` +node generateRPCAuth.js +``` + +This will update the value of your credentials in Secrets Manager. + +**Replacing the Credentials and Restarting the Node to Apply Updates** + +- Replace the old `rpcauth` value from the `bitcoin.conf` file with the new one. Make sure that you change the placeholder value for `[new rpcauth string with escape char]` (this is printed to the terminal after running the `generateRPCAuth` script): + + ``` + sudo docker exec -it bitcoind sh -c "sed -i 's/^rpcauth=.*/rpcauth=[new rpcauth string with escape char]/' /root/.bitcoin/bitcoin.conf" + ``` +- Restart the Bitcoin node to apply changes: + ``` + sudo docker restart bitcoind + ``` + +#### Verifying the Credential Rotation + +Make an RPC call to ensure the new credentials are active: + +``` +curl --user "$BTC_RPC_AUTH" \ + --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockchaininfo", "params": []}' \ + -H 'content-type: text/plain;' http://:8332/ +``` + +--- + +### Monitoring and Troubleshooting + +Keep your node healthy by monitoring logs and configurations. + +These can be run after accessing the node via Systems Manager: + +``` +export INSTANCE_ID=$(jq -r '.SingleNodeBitcoinCoreStack.BitcoinNodeInstanceId' single-node-outputs.json) +echo "INSTANCE_ID="$INSTANCE_ID +aws ssm start-session --target $INSTANCE_ID --region $AWS_REGION +``` + +- Check recent Bitcoin logs: + ``` + sudo docker logs -f --tail 100 bitcoind + ``` + +- Check first 100 Bitcoin logs: + ``` + sudo docker logs bitcoind | head -n 100 + ``` + +- View the configuration file: + ``` + sudo docker exec -it bitcoind cat /root/.bitcoin/bitcoin.conf + ``` +- View user data logs: + ``` + sudo cat /var/log/cloud-init-output.log + ``` + + +--- + +### Additional Tips and Best Practices + +- Regularly rotate secrets and always remove old `rpcauth` entries before restarting the node. +- Use **CloudWatch** to monitor node performance and detect issues promptly. + +--- + +### Cleaning up +To destroy the single node and HA configurations, you can run the following commands: + +``` +#Delete Single Node Infra +cdk destroy SingleNodeBitcoinCoreStack + +#Delete HA Infra +cdk destroy HABitcoinCoreNodeStack +``` + + +--- + +### Conclusion + +Deploying and managing a Bitcoin node on AWS requires careful configuration to ensure security, cost efficiency, and high availability. By following the best practices outlined in this guide, you can maintain a robust and secure node while minimizing costs. Stay proactive with monitoring and regularly update credentials to keep your node running smoothly. diff --git a/lib/bitcoin-core/app.js b/lib/bitcoin-core/app.js new file mode 100644 index 00000000..f94074fb --- /dev/null +++ b/lib/bitcoin-core/app.js @@ -0,0 +1,28 @@ +#!/usr/bin/env node +require('dotenv').config(); // Load .env first +const cdk = require('aws-cdk-lib'); +const { Aspects } = require('aws-cdk-lib'); +const { AwsSolutionsChecks } = require('cdk-nag'); +const { BitcoinCommonStack } = require('./lib/common-infra'); +const { SingleNodeBitcoinCoreStack } = require('./lib/single-node-stack'); +const { HABitcoinCoreNodeStack } = require('./lib/ha-node-stack'); + +const app = new cdk.App(); + +Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true })); + +const env = { + account: process.env.AWS_ACCOUNT_ID, + region: process.env.AWS_REGION, +}; + +const commonStack = new BitcoinCommonStack(app, 'BitcoinCommonStack', { env }); +new SingleNodeBitcoinCoreStack(app, 'SingleNodeBitcoinCoreStack', { + env, + instanceRole: commonStack.instanceRole, +}); + +new HABitcoinCoreNodeStack(app, 'HABitcoinCoreNodeStack', { + env, + instanceRole: commonStack.instanceRole, +}); diff --git a/lib/bitcoin-core/cdk.json b/lib/bitcoin-core/cdk.json new file mode 100644 index 00000000..694731ca --- /dev/null +++ b/lib/bitcoin-core/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "node app.js" +} diff --git a/lib/bitcoin-core/doc/assets/Bitcoin-HA-Nodes-Arch.png b/lib/bitcoin-core/doc/assets/Bitcoin-HA-Nodes-Arch.png new file mode 100644 index 00000000..0aa8a7df Binary files /dev/null and b/lib/bitcoin-core/doc/assets/Bitcoin-HA-Nodes-Arch.png differ diff --git a/lib/bitcoin-core/doc/assets/Bitcoin-Single-Node-Arch.png b/lib/bitcoin-core/doc/assets/Bitcoin-Single-Node-Arch.png new file mode 100644 index 00000000..6c2f9d97 Binary files /dev/null and b/lib/bitcoin-core/doc/assets/Bitcoin-Single-Node-Arch.png differ diff --git a/lib/bitcoin-core/generateRPCAuth.js b/lib/bitcoin-core/generateRPCAuth.js new file mode 100644 index 00000000..c3d59f50 --- /dev/null +++ b/lib/bitcoin-core/generateRPCAuth.js @@ -0,0 +1,100 @@ +const crypto = require('crypto'); +const base64url = require('base64url'); +const fs = require('fs'); +const { SecretsManagerClient, CreateSecretCommand, PutSecretValueCommand } = require('@aws-sdk/client-secrets-manager'); + +// Set up AWS SDK client +const client = new SecretsManagerClient({ region: 'us-east-1' }); // Change region if needed + +// Create size byte hex salt +function genSalt(size = 16) { + const buffer = crypto.randomBytes(size); + return buffer.toString('hex'); +} + +// Create 32 byte b64 password +function genPass(size = 32) { + const buffer = crypto.randomBytes(size); + return base64url.fromBase64(buffer.toString('base64')); +} + +function genUser() { + return 'user_' + Math.round(Math.random() * 1000); +} + +function genHash(password, salt) { + const hash = crypto + .createHmac('sha256', salt) + .update(password) + .digest('hex'); + return hash; +} + +function genRpcAuth(username = genUser(), password = genPass(), salt = genSalt()) { + const hash = genHash(password, salt); + return { username, password, salt, hash }; +} + +function writeRpcAuthToConf(rpcauthStr) { + const confPath = 'lib/bitcoin.conf'; + try { + fs.writeFileSync(confPath, rpcauthStr + '\n', { flag: 'a' }); + console.log(`Successfully wrote to ${confPath}`); + } catch (error) { + console.error(`Error writing to ${confPath}:`, error); + } +} + +async function storeCredentialsInAWS(username, password) { + const secretName = 'bitcoin_rpc_credentials'; + const secretValue = `${username}:${password}`; + + try { + const createCommand = new CreateSecretCommand({ + Name: secretName, + SecretString: secretValue, + }); + await client.send(createCommand); + console.log(`Successfully stored credentials in AWS Secrets Manager: ${secretName}`); + } catch (error) { + if (error.name === 'ResourceExistsException') { + const updateCommand = new PutSecretValueCommand({ + SecretId: secretName, + SecretString: secretValue, + }); + await client.send(updateCommand); + console.log(`Successfully updated existing secret in AWS Secrets Manager: ${secretName}`); + } else { + console.error(`Error storing credentials in AWS Secrets Manager:`, error); + } + } +} + +async function genRpcAuthStr(username, password, salt) { + const rpcauth = genRpcAuth(username, password, salt); + const str = `rpcauth=${rpcauth.username}:${rpcauth.salt}$${rpcauth.hash}`; + const strEscapeCharacter = `${rpcauth.username}:${rpcauth.salt}\\$${rpcauth.hash}`; + console.log(`Username: ${rpcauth.username}`); + console.log("Password generated securely and stored in Secrets Manager"); + console.log(`rpcauth string with escape character: ${strEscapeCharacter}`); // Print the rpcauth string + + // Write to bitcoin.conf + writeRpcAuthToConf(str); + + // Store in AWS Secrets Manager + await storeCredentialsInAWS(rpcauth.username, rpcauth.password); + + return str; +} + +// Example usage +genRpcAuthStr(); + +module.exports = { + genSalt, + genPass, + genUser, + genHash, + genRpcAuth, + genRpcAuthStr, +}; diff --git a/lib/bitcoin-core/lib/assets/bitcoin-setup.sh b/lib/bitcoin-core/lib/assets/bitcoin-setup.sh new file mode 100755 index 00000000..fc0724b6 --- /dev/null +++ b/lib/bitcoin-core/lib/assets/bitcoin-setup.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# This script is used to set up a mainnet Bitcoin Core node on an Amazon Linux 2 instance. +yum update -y +amazon-linux-extras install docker -y +service docker start +mkdir -p /home/bitcoin/.bitcoin +echo "${BITCOIN_CONF}" > /home/bitcoin/.bitcoin/bitcoin.conf +docker run -d --name bitcoind -v /home/bitcoin/.bitcoin:/root/.bitcoin -p 8333:8333 -p 8332:8332 bitcoin/bitcoin:latest bash -c "chown -R bitcoin:bitcoin /root/.bitcoin && bitcoind" diff --git a/lib/bitcoin-core/lib/assets/blockheight-cron.sh b/lib/bitcoin-core/lib/assets/blockheight-cron.sh new file mode 100755 index 00000000..fcc7f192 --- /dev/null +++ b/lib/bitcoin-core/lib/assets/blockheight-cron.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This script is used to set up a cron job to send the Bitcoin block height to Amazon CloudWatch every 5 minutes. +REGION=${AWS_REGION} +(crontab -l 2>/dev/null; echo "*/5 * * * * sudo /usr/bin/docker exec bitcoind bitcoin-cli getblockcount | xargs -I {} sudo /usr/bin/aws cloudwatch put-metric-data --metric-name BlockHeight --namespace Bitcoin --unit Count --value {} --region $REGION") | crontab - diff --git a/lib/bitcoin-core/lib/assets/cloudwatch-setup.sh b/lib/bitcoin-core/lib/assets/cloudwatch-setup.sh new file mode 100755 index 00000000..831788bb --- /dev/null +++ b/lib/bitcoin-core/lib/assets/cloudwatch-setup.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# This script is used to set up the Amazon CloudWatch agent on an Amazon Linux 2 instance. + +yum install -y amazon-cloudwatch-agent +mkdir -p /opt/aws/amazon-cloudwatch-agent/etc +cat < /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json +{ + "metrics": { + "metrics_collected": { + "disk": { + "measurement": ["used_percent", "inodes_free"], + "resources": ["*"], + "ignore_file_system_types": ["sysfs", "devtmpfs"] + }, + "mem": { + "measurement": ["mem_used_percent"] + }, + "cpu": { + "measurement": ["cpu_usage_idle", "cpu_usage_user", "cpu_usage_system"] + }, + "net": { + "measurement": ["net_bytes_sent", "net_bytes_recv"], + "resources": ["eth0"] + } + } + } +} +EOF + +/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s diff --git a/lib/bitcoin-core/lib/bitcoin.conf b/lib/bitcoin-core/lib/bitcoin.conf new file mode 100644 index 00000000..c0b7a72e --- /dev/null +++ b/lib/bitcoin-core/lib/bitcoin.conf @@ -0,0 +1,5 @@ +server=1 +rpcallowip=0.0.0.0/0 +txindex=1 +rpcbind=0.0.0.0:8332 +datadir=/home/bitcoin/.bitcoin diff --git a/lib/bitcoin-core/lib/common-infra.js b/lib/bitcoin-core/lib/common-infra.js new file mode 100644 index 00000000..a737513d --- /dev/null +++ b/lib/bitcoin-core/lib/common-infra.js @@ -0,0 +1,39 @@ +const cdk = require("aws-cdk-lib"); +const iam = require("aws-cdk-lib/aws-iam"); +const nag = require("cdk-nag"); + +class BitcoinCommonStack extends cdk.Stack { + constructor(scope, id, props) { + super(scope, id, props); + + this.instanceRole = new iam.Role(this, "BitcoinNodeRole", { + assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"), + iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy"), + ], + }); + + new cdk.CfnOutput(this, "InstanceRoleArn", { + value: this.instanceRole.roleArn, + exportName: "BitcoinNodeInstanceRoleArn", + }); + + // cdk-nag suppressions + nag.NagSuppressions.addResourceSuppressions( + this.instanceRole, + [ + { + id: "AwsSolutions-IAM4", + reason: "AmazonSSMManagedInstanceCore and CloudWatchAgentServerPolicy are sufficient for this use case.", + }, + { + id: "AwsSolutions-IAM5", + reason: "Managed policies and wildcard usage are acceptable for this limited-scope Bitcoin node role.", + }, + ] + ); + } +} + +module.exports = { BitcoinCommonStack }; diff --git a/lib/bitcoin-core/lib/constructs/bitcoin-mainnet-security-group.js b/lib/bitcoin-core/lib/constructs/bitcoin-mainnet-security-group.js new file mode 100644 index 00000000..5231ccb3 --- /dev/null +++ b/lib/bitcoin-core/lib/constructs/bitcoin-mainnet-security-group.js @@ -0,0 +1,20 @@ +const { Construct } = require('constructs'); +const ec2 = require('aws-cdk-lib/aws-ec2'); + +class BitcoinSecurityGroup extends Construct { + constructor(scope, id, vpc) { + super(scope, id); + + const sg = new ec2.SecurityGroup(this, 'BitcoinSG', { + vpc, + allowAllOutbound: true, + }); + + sg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8333), 'Bitcoin P2P'); + sg.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(8332), 'Bitcoin RPC from VPC'); + + this.securityGroup = sg; + } +} + +module.exports = { BitcoinSecurityGroup }; diff --git a/lib/bitcoin-core/lib/ha-node-stack.js b/lib/bitcoin-core/lib/ha-node-stack.js new file mode 100644 index 00000000..da1f48f2 --- /dev/null +++ b/lib/bitcoin-core/lib/ha-node-stack.js @@ -0,0 +1,217 @@ +const cdk = require('aws-cdk-lib'); +const ec2 = require('aws-cdk-lib/aws-ec2'); +const iam = require('aws-cdk-lib/aws-iam'); +const autoscaling = require('aws-cdk-lib/aws-autoscaling'); +const elbv2 = require('aws-cdk-lib/aws-elasticloadbalancingv2'); +const path = require('path'); +const fs = require('fs'); +const { NagSuppressions } = require('cdk-nag'); +const { BitcoinSecurityGroup } = require('./constructs/bitcoin-mainnet-security-group.js'); +require('dotenv').config(); + +//Parse env variables +const { + INSTANCE_CLASS, + INSTANCE_SIZE, + EBS_VOLUME_SIZE, + EBS_VOLUME_TYPE, + ASG_MIN_CAPACITY, + ASG_MAX_CAPACITY, + ASG_DESIRED_CAPACITY, + GP3_THROUGHPUT, + GP3_IOPS, + CPU_ARCHITECTURE, + AWS_REGION +} = process.env; + + +class HABitcoinCoreNodeStack extends cdk.Stack { + constructor(scope, id, props) { + super(scope, id, props); + + // Create VPC + const vpc = ec2.Vpc.fromLookup(this, 'DefaultVPC', { + isDefault: true, + }); + + // Security Group for the Load Balancer + const lbSg = new ec2.SecurityGroup(this, 'BitcoinLBSG', { + vpc, + allowAllOutbound: true, + }); + lbSg.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(80), 'Bitcoin RPC'); + + // Security Group for EC2 instances + const sgConstruct = new BitcoinSecurityGroup(this, 'BitcoinSecurityGroup', vpc); + const ec2Sg = sgConstruct.securityGroup; + + // IAM Role for EC2 + const role = props.instanceRole; + + // Load user data script from /assets/ + const bitcoinSetup = fs.readFileSync(path.join(__dirname, 'assets', 'bitcoin-setup.sh'), 'utf8'); + + // Load bitcoin.conf file + const bitcoinConfPath = path.join(__dirname, 'bitcoin.conf'); + const bitcoinConfContent = fs.readFileSync(bitcoinConfPath, 'utf8'); + + // User data for EC2 instance + const userData = ec2.UserData.forLinux(); + userData.addCommands( + `export AWS_REGION='${AWS_REGION}'`, + `export BITCOIN_CONF='${bitcoinConfContent}'`, + bitcoinSetup + ); + + // Determine CPU architecture + const arch = CPU_ARCHITECTURE === 'ARM64' ? ec2.AmazonLinuxCpuType.ARM_64 : ec2.AmazonLinuxCpuType.X86_64; + const machineImage = ec2.MachineImage.latestAmazonLinux2({ cpuType: arch }); + // Application Load Balancer + const lb = new elbv2.ApplicationLoadBalancer(this, 'BitcoinLB', { + vpc, + internetFacing: false, + securityGroup: lbSg, + }); + + // Create target group with health check configuration + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'BitcoinTG', { + vpc, + port: 8332, + protocol: elbv2.ApplicationProtocol.HTTP, + targetType: elbv2.TargetType.INSTANCE, + healthCheck: { + protocol: elbv2.Protocol.HTTP, + port: '8332', + path: '/', + healthyHttpCodes: '200-499', + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + }, + stickinessCookieDuration: cdk.Duration.hours(1.5), + stickinessCookieName: 'BitcoinStickySession', + stickinessEnabled: true, + }); + + // Add listener to the load balancer with forwarding rule + lb.addListener('RPCListener', { + port: 80, + open: false, + protocol: elbv2.ApplicationProtocol.HTTP, + defaultAction: elbv2.ListenerAction.forward([targetGroup]), + }); + + + + // Configure block devices + const blockDevices = [{ + deviceName: '/dev/xvda', + volume: ec2.BlockDeviceVolume.ebs( + Number(EBS_VOLUME_SIZE), + { + volumeType: ec2.EbsDeviceVolumeType[EBS_VOLUME_TYPE], + encrypted: true, + iops: Number(GP3_IOPS), + throughput: Number(GP3_THROUGHPUT) + } + ), + }]; + + // Create Launch Template + const launchTemplate = new ec2.LaunchTemplate(this, 'BitcoinLaunchTemplate', { + instanceType: ec2.InstanceType.of( + ec2.InstanceClass[INSTANCE_CLASS], + ec2.InstanceSize[INSTANCE_SIZE] + ), + machineImage, + userData, + role, + securityGroup: ec2Sg, + blockDevices, + }); + + + // Auto Scaling Group with Launch Template + const asg = new autoscaling.AutoScalingGroup(this, 'BitcoinASG', { + vpc, + launchTemplate: launchTemplate, + minCapacity: Number(ASG_MIN_CAPACITY), + maxCapacity: Number(ASG_MAX_CAPACITY), + desiredCapacity: Number(ASG_DESIRED_CAPACITY), + }); + + // Attach the ASG to the Target Group + targetGroup.addTarget(asg); + + new cdk.CfnOutput(this, 'LoadBalancerDNS', { + value: lb.loadBalancerDnsName, + description: 'DNS name of the Load Balancer', + exportName: 'BitcoinLoadBalancerDNS', + }); + + // Suppress VPC Flow Log warning + NagSuppressions.addResourceSuppressions( + vpc, + [ + { + id: 'AwsSolutions-VPC7', + reason: 'Flow logs are not required for this specific setup as it is a high-availability Bitcoin node stack where logging may add unnecessary costs and complexity.', + }, + ], + ); + + // Suppress Load Balancer Security Group warning + NagSuppressions.addResourceSuppressions( + lbSg, + [ + { + id: 'AwsSolutions-EC23', + reason: 'CDK Nag validation failure due to intrinsic function reference, which is expected behavior.', + }, + ], + ); + + // Suppress EC2 Security Group warning + NagSuppressions.addResourceSuppressions( + ec2Sg, + [ + { + id: 'AwsSolutions-EC23', + reason: 'Inbound access is required for Bitcoin P2P communication, which relies on open access for peer connections.', + }, + ], + ); + + + // Suppress Load Balancer logging warning + NagSuppressions.addResourceSuppressions( + lb, + [ + { + id: 'AwsSolutions-ELB2', + reason: 'Access logging is not required for this application to minimize operational costs.', + }, + ], + ); + + // Suppress Auto Scaling Group warnings + NagSuppressions.addResourceSuppressions( + asg, + [ + { + id: 'AwsSolutions-AS3', + reason: 'Auto Scaling Group does not require notifications for scaling events in this non-critical application.', + }, + { + id: 'AwsSolutions-AS3', + reason: 'Setting the desired capacity is intentional and necessary for the stability and reliability of the application.', + }, + ], + ); + + } +} + + +module.exports = { HABitcoinCoreNodeStack }; diff --git a/lib/bitcoin-core/lib/single-node-stack.js b/lib/bitcoin-core/lib/single-node-stack.js new file mode 100644 index 00000000..af7afa33 --- /dev/null +++ b/lib/bitcoin-core/lib/single-node-stack.js @@ -0,0 +1,166 @@ +const cdk = require('aws-cdk-lib'); +const ec2 = require('aws-cdk-lib/aws-ec2'); +const iam = require('aws-cdk-lib/aws-iam'); +const cloudwatch = require('aws-cdk-lib/aws-cloudwatch'); +const path = require('path'); +const fs = require('fs'); +const { NagSuppressions } = require('cdk-nag'); +const { BitcoinSecurityGroup } = require('./constructs/bitcoin-mainnet-security-group.js'); +require('dotenv').config(); + +//Parse env variables +const { + INSTANCE_CLASS, + INSTANCE_SIZE, + EBS_VOLUME_SIZE, + EBS_VOLUME_TYPE, + GP3_THROUGHPUT, + GP3_IOPS, + CPU_ARCHITECTURE, + USE_INSTANCE_STORE +} = process.env; + +class SingleNodeBitcoinCoreStack extends cdk.Stack { + constructor(scope, id, props) { + super(scope, id, props); + + // Retrieve region + const region = cdk.Stack.of(this).region; + + // Create VPC + const vpc = ec2.Vpc.fromLookup(this, 'DefaultVPC', { + isDefault: true, + }); + + const sgConstruct = new BitcoinSecurityGroup(this, 'BitcoinSecurityGroup', vpc); + const sg = sgConstruct.securityGroup; + + // IAM Role for EC2 + const role = props.instanceRole; + + // Load scripts from /assets/ + const bitcoinSetup = fs.readFileSync(path.join(__dirname, 'assets', 'bitcoin-setup.sh'), 'utf8'); + const cloudwatchSetup = fs.readFileSync(path.join(__dirname, 'assets', 'cloudwatch-setup.sh'), 'utf8'); + const blockheightCron = fs.readFileSync(path.join(__dirname, 'assets', 'blockheight-cron.sh'), 'utf8'); + + // Load bitcoin.conf file + const bitcoinConfPath = path.join(__dirname, 'bitcoin.conf'); + const bitcoinConfContent = fs.readFileSync(bitcoinConfPath, 'utf8'); + + // User data for EC2 instance + const userData = ec2.UserData.forLinux(); + userData.addCommands( + `export AWS_REGION='${region}'`, + `export BITCOIN_CONF='${bitcoinConfContent}'`, + bitcoinSetup, + cloudwatchSetup, + blockheightCron + ); + + // Determine CPU architecture + const arch = CPU_ARCHITECTURE === 'ARM64' ? ec2.AmazonLinuxCpuType.ARM_64 : ec2.AmazonLinuxCpuType.X86_64; + const machineImage = ec2.MachineImage.latestAmazonLinux2({ cpuType: arch }); + + + // configure EBS block devices + const blockDevices = [ + { + deviceName: '/dev/xvda', + volume: ec2.BlockDeviceVolume.ebs( + Number(EBS_VOLUME_SIZE), + { + volumeType: ec2.EbsDeviceVolumeType[EBS_VOLUME_TYPE], + encrypted: true, + iops: Number(GP3_IOPS), + throughput: Number(GP3_THROUGHPUT), + } + ), + }, + ]; + + + // EC2 Instance + const instance = new ec2.Instance(this, 'BitcoinSingleNode', { + vpc, + instanceType: ec2.InstanceType.of( + ec2.InstanceClass[INSTANCE_CLASS], + ec2.InstanceSize[INSTANCE_SIZE] + ), + machineImage, + role, + securityGroup: sg, + blockDevices, + userData, + }); + + new cdk.CfnOutput(this, 'BitcoinNodePrivateIP', { + value: instance.instancePrivateIp, + description: 'Private IP of the Bitcoin Node', + }); + + new cdk.CfnOutput(this, 'BitcoinNodeInstanceId', { + value: instance.instanceId, + description: 'Instance ID of the Bitcoin Node (used for SSM)', + }); + + // CloudWatch Dashboard + const dashboard = new cloudwatch.Dashboard(this, 'BitcoinNodeDashboard', { dashboardName: 'BitcoinNodeMetrics' }); + const cpuWidget = new cloudwatch.GraphWidget({ title: 'CPU Usage', left: [new cloudwatch.Metric({ namespace: 'AWS/EC2', metricName: 'CPUUtilization', dimensionsMap: { InstanceId: instance.instanceId }, statistic: 'Average', period: cdk.Duration.minutes(5) })] }); + const diskUsageWidget = new cloudwatch.GraphWidget({ title: 'Disk Usage (%)', left: [new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'disk_used_percent', dimensionsMap: { host: instance.instancePrivateDnsName, device: 'nvme0n1p1', path: '/', fstype: 'xfs' }, statistic: 'Average', period: cdk.Duration.minutes(5) })] }); + const memoryWidget = new cloudwatch.GraphWidget({ title: 'Memory Usage', left: [new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'mem_used_percent', dimensionsMap: { host: instance.instancePrivateDnsName }, statistic: 'Average', period: cdk.Duration.minutes(5) })] }); + const networkWidget = new cloudwatch.GraphWidget({ title: 'Network Bytes In/Out', left: [new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'net_bytes_sent', dimensionsMap: { host: instance.instancePrivateDnsName, interface: 'eth0' }, statistic: 'Sum', period: cdk.Duration.minutes(5) }), new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'net_bytes_recv', dimensionsMap: { host: instance.instancePrivateDnsName, interface: 'eth0' }, statistic: 'Sum', period: cdk.Duration.minutes(5) })] }); + const blockHeightWidget = new cloudwatch.GraphWidget({ title: 'Bitcoin Block Height', left: [new cloudwatch.Metric({ namespace: 'Bitcoin', metricName: 'BlockHeight', statistic: 'Average', period: cdk.Duration.minutes(5) })] }); + dashboard.addWidgets(cpuWidget, diskUsageWidget, memoryWidget, networkWidget, blockHeightWidget); + + // Suppress VPC Flow Log warning + NagSuppressions.addResourceSuppressions( + vpc, + [ + { + id: 'AwsSolutions-VPC7', + reason: 'Flow logs are not required for this specific setup.', + }, + ], + ); + + // Suppress Security Group warning about open ingress + NagSuppressions.addResourceSuppressions( + sg, + [ + { + id: 'AwsSolutions-EC23', + reason: 'Inbound access is needed for Bitcoin P2P communication.', + }, + ], + ); + + // Suppress EC2 instance monitoring and ASG warnings + NagSuppressions.addResourceSuppressions( + instance, + [ + { + id: 'AwsSolutions-EC28', + reason: 'Detailed monitoring is not required for this application.', + }, + { + id: 'AwsSolutions-EC29', + reason: 'The EC2 instance is standalone and not part of an ASG, as this is a single-node Bitcoin core setup.', + }, + ], + ); + + NagSuppressions.addResourceSuppressions( + instance, + [ + { + id: 'AwsSolutions-EC26', + reason: 'EBS encryption is not required for this specific application.', + }, + ], + ); + + + } +} + +module.exports = { SingleNodeBitcoinCoreStack }; diff --git a/lib/bitcoin-core/package.json b/lib/bitcoin-core/package.json new file mode 100644 index 00000000..2f11f272 --- /dev/null +++ b/lib/bitcoin-core/package.json @@ -0,0 +1,11 @@ +{ + "name": "aws-blockchain-node-runners-bitcoin-core", + "version": "0.1.0", + "scripts": { + "build": "npx tsc", + "watch": "npx tsc -w", + "test": "npx jest", + "cdk": "npx cdk", + "scan-cdk": "npx cdk synth" + } +} diff --git a/lib/bitcoin-core/sample-configs/.env-sample-bitcoin-mainnet b/lib/bitcoin-core/sample-configs/.env-sample-bitcoin-mainnet new file mode 100644 index 00000000..33360014 --- /dev/null +++ b/lib/bitcoin-core/sample-configs/.env-sample-bitcoin-mainnet @@ -0,0 +1,21 @@ +AWS_ACCOUNT_ID=123456789101 +AWS_REGION=us-east-1 + +# The following configuration has been extensively tested and is recommended for use. +# The instance type is T3A, which is a cost-effective option for running Bitcoin Core. +# The instance size is LARGE, which provides a good balance of CPU and memory resources. +# The EBS volume size is set to 1000 GB, which is sufficient for storing the Bitcoin blockchain. +# The EBS volume type is GP3, which offers a good balance of performance and cost. +INSTANCE_CLASS=T3A +INSTANCE_SIZE=LARGE +EBS_VOLUME_SIZE=1000 +EBS_VOLUME_TYPE=GP3 + +ASG_MIN_CAPACITY=2 +ASG_MAX_CAPACITY=4 +ASG_DESIRED_CAPACITY=2 + +# The following configuration has been tested and proven effective for a Bitcoin Core node to achieve full synchronization without incurring additional costs: +GP3_IOPS=3000 +GP3_THROUGHPUT=125 +CPU_ARCHITECTURE=X86_64 # Options: X86_64 or ARM64 diff --git a/lib/bitcoin-core/test/ha-node-stack.test.js b/lib/bitcoin-core/test/ha-node-stack.test.js new file mode 100644 index 00000000..d08691b9 --- /dev/null +++ b/lib/bitcoin-core/test/ha-node-stack.test.js @@ -0,0 +1,83 @@ +const { Match, Template } = require('aws-cdk-lib/assertions'); +const cdk = require('aws-cdk-lib'); +const ec2 = require('aws-cdk-lib/aws-ec2'); +const iam = require('aws-cdk-lib/aws-iam'); +const { HABitcoinCoreNodeStack } = require('../lib/ha-node-stack'); + +describe('HABitcoinCoreNodeStack', () => { + test('synthesizes the way we expect', () => { + const app = new cdk.App(); + + // Create a mock stack for context and shared resources like IAM role + const mockStack = new cdk.Stack(app, 'MockStack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + // Create a mock IAM role to pass into the HA stack + const testRole = new iam.Role(mockStack, 'TestInstanceRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + }); + + // Instantiate the HA stack using the default VPC and injected role + const haStack = new HABitcoinCoreNodeStack(app, 'ha-bitcoin-node', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + instanceRole: testRole, + }); + + const template = Template.fromStack(haStack); + + // IAM Role should not be present in this stack (provided externally), but for completeness: + template.resourceCountIs('AWS::IAM::Role', 0); + + // Launch Template should exist + template.resourceCountIs('AWS::EC2::LaunchTemplate', 1); + + // Auto Scaling Group + template.resourceCountIs('AWS::AutoScaling::AutoScalingGroup', 1); + + // Application Load Balancer + template.resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); + + // Target Group + template.resourceCountIs('AWS::ElasticLoadBalancingV2::TargetGroup', 1); + + // Listener + template.resourceCountIs('AWS::ElasticLoadBalancingV2::Listener', 1); + + // Confirm the ASG has correct capacity + template.hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { + MinSize: Match.anyValue(), + MaxSize: Match.anyValue(), + DesiredCapacity: Match.anyValue(), + }); + + + // Confirm LaunchTemplate references role ARN + template.hasResourceProperties('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + IamInstanceProfile: Match.anyValue(), + ImageId: Match.anyValue(), + InstanceType: Match.anyValue(), + SecurityGroupIds: Match.anyValue(), + } + }); + + // Load Balancer Listener config + template.hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + Port: 80, + Protocol: 'HTTP', + }); + + // Target Group config + template.hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { + Port: 8332, + Protocol: 'HTTP', + }); + }); +}); diff --git a/lib/bitcoin-core/test/single-node-stack.test.js b/lib/bitcoin-core/test/single-node-stack.test.js new file mode 100644 index 00000000..4d143ed8 --- /dev/null +++ b/lib/bitcoin-core/test/single-node-stack.test.js @@ -0,0 +1,78 @@ +const { Match, Template } = require('aws-cdk-lib/assertions'); +const cdk = require('aws-cdk-lib'); +const { SingleNodeBitcoinCoreStack } = require('../lib/single-node-stack'); + + +describe('SingleNodeBitcoinCoreStack', () => { + test('synthesizes the way we expect', () => { + const app = new cdk.App(); + + const bitcoinNodeStack = new SingleNodeBitcoinCoreStack(app, 'bitcoin-single-node', { + env: { + account: '123456789012', // use dummy account + region: 'us-east-1', // or your preferred region + }, + }); + + + // Prepare the stack for assertions + const template = Template.fromStack(bitcoinNodeStack); + + + // Has EC2 instance security group + template.hasResourceProperties('AWS::EC2::SecurityGroup', { + VpcId: Match.anyValue(), + SecurityGroupEgress: [ + { + CidrIp: '0.0.0.0/0', + IpProtocol: '-1', + }, + ], + SecurityGroupIngress: [ + { + CidrIp: '0.0.0.0/0', + FromPort: 8333, + IpProtocol: 'tcp', + ToPort: 8333, + }, + { + CidrIp: Match.stringLikeRegexp('.*'), + FromPort: 8332, + IpProtocol: 'tcp', + ToPort: 8332, + }, + ], + }); + + // Has EC2 instance with node configuration + template.hasResourceProperties('AWS::EC2::Instance', { + InstanceType: Match.stringLikeRegexp('.*'), // accept any value including 'undefined.undefined' + BlockDeviceMappings: [{ + DeviceName: '/dev/xvda', + Ebs: Match.objectLike({ // loosen strict match on EBS + Encrypted: true, + }), + }], + SecurityGroupIds: Match.anyValue(), + SubnetId: Match.anyValue(), + UserData: Match.anyValue(), + }); + + + // Has IAM Role with necessary permissions + template.hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'ec2.amazonaws.com' }, + }], + }, + }); + + // Has CloudWatch dashboard + template.hasResourceProperties('AWS::CloudWatch::Dashboard', { + DashboardBody: Match.anyValue(), + }); + }); +}); diff --git a/lib/bsc/README.md b/lib/bsc/README.md index 9c218249..56e07f0f 100644 --- a/lib/bsc/README.md +++ b/lib/bsc/README.md @@ -110,7 +110,7 @@ pwd cp ./sample-configs/.env-sample-full .env nano .env ``` -> **IMPORTANT**: *By default we use the latest Geth Fullnode snapshot from [48 Club](https://github.com/48Club/bsc-snapshots/blob/main/data.json) If you want to set your own `BSC_SNAPSHOTS_URI`, check this GitHub: https://github.com/48Club/bsc-snapshots, and use Geth full node link.* +> **IMPORTANT**: *By default we use the latest Geth Fullnode snapshot from [48 Club](https://github.com/48Club/bsc-snapshots/blob/develop/data.json) If you want to set your own `BSC_SNAPSHOTS_URI`, check this GitHub: https://github.com/48Club/bsc-snapshots, and use Geth full node link.* 4. Deploy common components such as IAM role diff --git a/lib/bsc/lib/assets/download-snapshot.sh b/lib/bsc/lib/assets/download-snapshot.sh index 1f50663f..a518adfe 100644 --- a/lib/bsc/lib/assets/download-snapshot.sh +++ b/lib/bsc/lib/assets/download-snapshot.sh @@ -12,7 +12,7 @@ BSC_SNAPSHOTS_DIR=/data/ BSC_SNAPSHOTS_DOWNLOAD_STATUS=-1 if [ "$BSC_SNAPSHOTS_URI" == "none" ]; then - BSC_SNAPSHOTS_URI=$(curl https://raw.githubusercontent.com/48Club/bsc-snapshots/main/data.json | jq -r .geth.local.link) + BSC_SNAPSHOTS_URI=$(curl https://raw.githubusercontent.com/48Club/bsc-snapshots/develop/data.json | jq -r .geth.local.link) fi # take about 1 hour to download the bsc snapshot @@ -50,7 +50,8 @@ zstd -cd geth.tar.zst | pv | tar xvf - 2>&1 | tee unzip.log && echo "decompressi echo "Decompression BSC snapshot success ..." mv /data/geth.full/geth /data/ -sudo rm -rf /data/geth.full +mv /data/full/geth /data/ +sudo rm -rf /data/full sudo rm -rf /data/geth.tar.zst echo "BSC snapshot is ready !!!" diff --git a/lib/fantom/.gitignore b/lib/fantom/.gitignore deleted file mode 100644 index f60797b6..00000000 --- a/lib/fantom/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -*.js -!jest.config.js -*.d.ts -node_modules - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/lib/fantom/README.md b/lib/fantom/README.md deleted file mode 100644 index 101d1335..00000000 --- a/lib/fantom/README.md +++ /dev/null @@ -1,346 +0,0 @@ -# Sample AWS Blockchain Node Runner app for Fantom Blockchain Nodes - -| Contributed by | -|:--------------------:| -| [@ngl-aws](https://github.com/ngl-aws), [@frbrkoala](https://github.com/frbrkoala) | - -[Fantom](https://fantom.foundation/) is a permissionless blockchain platform that supports EVM-compatible smart contracts and applications. It utilizes Delegated Proof of Stake (DPoS) and directed acyclic graphs (DAG) of event blocks to achieve fast transaction confirmation with asynchronous Byzantine fault tolerance (aBFT) guarantee. - -This blueprint is designed to assist in deploying a single node or a Highly Available (HA) [Fantom read-only node](https://docs.fantom.foundation/node/tutorials/run-a-read-only-node) on AWS. It is intended for use in development, testing, or Proof of Concept purposes. - -## Overview of Deployment Architectures - -### Single Node setup -![Single Nodes Deployment](./doc/assets/Architecture-Single-FANTOM-Node-Runners.drawio.png) - -1. The AWS Cloud Development Kit (CDK) is used to deploy a single node. The CDK application stores assets like scripts and config files in S3 bucket to copy them to the EC2 instance when launching a Fantom Node. -2. A single RPC Fantom Fullnode is deployed within the [Default VPC](https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html) and continuously synchronizes with the rest of nodes on Fantom Blockchain Network through an [Internet Gateway](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html). -3. The Fantom node is accessed by dApps or development tools internally. JSON RPC API is not exposed to the Internet to protect the node from unauthorized access. dApps need to handle user authentication and API protection, like [in this example for dApps on AWS](https://aws.amazon.com/blogs/architecture/dapp-authentication-with-amazon-cognito-and-web3-proxy-with-amazon-api-gateway/). -4. The Fantom node sends various monitoring metrics for both the EC2 instance and the Fantom client to Amazon CloudWatch. - -### Highly Available setup - -![Highly Available Nodes Deployment](./doc/assets/Architecture-HA-FANTOM-Node-Runners.drawio.png) - -1. The CDK is used to deploy a highly available (HA) architecture. An S3 bucket is utilized to store [User data](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html) and other scripts and configuration files required when launching EC2 as the Fantom Node. -2. A set of RPC Fantom Fullnodes that are deployed within the [Auto Scaling Group](https://docs.aws.amazon.com/autoscaling/ec2/userguide/auto-scaling-groups.html) in the [Default VPC](https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html) continuously synchronize with the rest of the nodes on Fantom Blockchain Network through an [Internet Gateway](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html). -3. The Fantom nodes are accessed by dApps or development tools internally through an [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html). JSON RPC API is not exposed to the Internet to protect nodes from unauthorized access. dApps need to handle user authentication and API protection, like [in this example for dApps on AWS](https://aws.amazon.com/blogs/architecture/dapp-authentication-with-amazon-cognito-and-web3-proxy-with-amazon-api-gateway/). -4. The Fantom nodes send various monitoring metrics for both the EC2 and the Fantom nodes to Amazon CloudWatch. - -## Additional materials - -
- -Well-Architected Checklist - -This is the Well-Architected checklist for Fantom nodes implementation of the AWS Blockchain Node Runner app. This checklist takes into account questions from the [AWS Well-Architected Framework](https://aws.amazon.com/architecture/well-architected/) which are relevant to this workload. Please feel free to add more checks from the framework if required for your workload. - -| Pillar | Control | Question/Check | Remarks | -|:------------------------|:----------------------------------|:---------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Security | Network protection | Are there unnecessary open ports in security groups? | Please note that ports 5050 (TCP/UDP) for Fantom are open to public to support P2P protocols. | -| | | Traffic inspection | Traffic protection is not used in the solution. [AWS Web Applications Firewall (WAF)](https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html) could be implemented for traffic over HTTP(S), [AWS Shield](https://docs.aws.amazon.com/waf/latest/developerguide/shield-chapter.html) provides Distributed Denial of Service (DDoS) protection. Additional charges will apply. | -| | Compute protection | Reduce attack surface | This solution uses Ubuntu 22.04 AMI(`Ubuntu 22.04.4`). You may choose to run hardening scripts on it. | -| | | Enable people to perform actions at a distance | This solution uses [AWS Systems Manager for terminal session](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html#start-sys-console), not ssh ports. | -| | Data protection at rest | Use encrypted Amazon Elastic Block Store (Amazon EBS) volumes | This solution uses encrypted Amazon EBS volumes. | -| | Data protection in transit | Use TLS | The AWS Application Load balancer currently uses HTTP listener. Create HTTPS listener with self signed certificate if TLS is desired. | -| | Authorization and access control | Use instance profile with Amazon Elastic Compute Cloud (Amazon EC2) instances | This solution uses AWS Identity and Access Management (AWS IAM) role instead of IAM user. | -| | | Following principle of least privilege access | In all node types, root user is not used (using special user "bcuser" instead). | -| | Application security | Security focused development practices | cdk-nag is being used with appropriate suppressions. | -| Cost optimization | Service selection | Use cost effective resources | 1/ We use x86-based binaries. We recommend using the `m6a.2xlarge` EC2 instance type to optimize computational costs. 2/ Cost-effective EBS gp3 are used instead of io2. | -| | Cost awareness | Estimate costs | Single RPC node with `m6a.2xlarge` EBS gp3 volumes about 4000 GB(1000 IOPS, 700 MBps/s throughput) with On-Demand pricing will cost around US$854.54 per month in the US East (N. Virginia) region. More cost-optimal option with 3 year EC2 Instance Savings plan the cost goes down to $594.15 USD. To create your own estimate use [AWS Pricing Calculator](https://calculator.aws/#/) | -| Reliability | Resiliency implementation | Withstand component failures | This solution uses AWS Application Load Balancer with RPC nodes for high availability. Newly provisioned Fantom nodes triggered by Auto Scaling get up and running in about 300 minutes. | -| | Data backup | How is data backed up? | Considering blockchain data is replicated by nodes automatically and Fantom nodes sync from start within an hour, we don't use any additional mechanisms to backup the data. | -| | Resource monitoring | How are workload resources monitored? | Resources are being monitored using Amazon CloudWatch dashboards. Amazon CloudWatch custom metrics are being pushed via CloudWatch Agent. | -| Performance efficiency | Compute selection | How is compute solution selected? | Compute solution is selected based on best price-performance, i.e. AWS Graviton-based Amazon EC2 instances. | -| | Storage selection | How is storage solution selected? | Storage solution is selected based on best price-performance, i.e. gp3 Amazon EBS volumes with optimal IOPS and throughput. | -| | Architecture selection | How is the best performance architecture selected? | We used a combination of recommendations from the Fantom community and our own testing. | -| Operational excellence | Workload health | How is health of workload determined? | Health of workload is determined via AWS Application Load Balancer Target Group Health Checks, on port 8845. | -| Sustainability | Hardware & services | Select most efficient hardware for your workload | The solution uses Graviton-powered instances. There is a potential to use AWS Graviton-based Amazon EC2 instances which offer the best performance per watt of energy use in Amazon EC2. | -
- -
- -Recommended Infrastructure - - -| Usage pattern | Ideal configuration | Primary option on AWS | Config reference | -|---------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------|-------------------------------------------------------| -| 1/ Fullnode | 8 vCPU, 32 GB RAM, Data volume: EBS gp3 2TB, 7K IOPS, 400 MB/s throughput | `m6a.2xlarge` EBS gp3 volumes about 2000 GB(7000 IOPS, 400 MBps/s throughput) | [.env-sample-full](./sample-configs/.env-sample-full) | -
- -## Setup Instructions - -### Open AWS CloudShell -To begin, ensure you login to your AWS account with permissions to create and modify resources in IAM, EC2, EBS, VPC, S3, KMS, and Secrets Manager. - -From the AWS Management Console, open the [AWS CloudShell](https://docs.aws.amazon.com/cloudshell/latest/userguide/welcome.html), a web-based shell environment. If unfamiliar, review the [2-minute YouTube video](https://youtu.be/fz4rbjRaiQM) for an overview and check out [CloudShell with VPC environment](https://docs.aws.amazon.com/cloudshell/latest/userguide/creating-vpc-environment.html) that we'll use to test nodes API from internal IP address space. - -Once ready, you can run the commands to deploy and test blueprints in the CloudShell. - -### Clone this repository and install dependencies - -```bash -git clone https://github.com/aws-samples/aws-blockchain-node-runners.git -cd aws-blockchain-node-runners -npm install -``` - -### Prepare to deploy nodes - -1. Make sure you are in the root directory of the cloned repository - -2. If you have deleted or don't have the default VPC, create default VPC - -```bash -aws ec2 create-default-vpc -``` - -> **NOTE**: *You may see the following error if the default VPC already exists: `An error occurred (DefaultVpcAlreadyExists) when calling the CreateDefaultVpc operation: A Default VPC already exists for this account in this region.`. That means you can just continue with the following steps.* - - -3. Configure the CDK app - - Create your own copy of `.env` file and edit it to update with your AWS Account ID, AWS Region, and optionally the Fantom SNAPSHOTS URI: - -```bash -cd lib/fantom -pwd -# Make sure you are in aws-blockchain-node-runners/lib/fantom -cp ./sample-configs/.env-sample-read .env -nano .env -``` -> **IMPORTANT**: *By default we use the latest Fantom snapshot from [Fantom](https://snapshot.fantom.network/files/snapsync/latest/listtgzfiles.txt)* - -4. Deploy common components such as IAM role - -```bash -pwd -# Make sure you are in aws-blockchain-node-runners/lib/fantom -npx cdk deploy fantom-common -``` - -> **IMPORTANT**: *All AWS CDK v2 deployments use dedicated AWS resources to hold data during deployment. Therefore, your AWS account and Region must be [bootstrapped](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html) to create these resources before you can deploy. If you haven't already bootstrapped, issue the following command:* -> ```bash -> cdk bootstrap aws://ACCOUNT-NUMBER/REGION -> ``` - -### Option 1: Single RPC Node - -1. The inital deployment of a Fantom Fullnode and downloading its snapshot typically takes about 3-12 hours. The Full node uses snapshots data, and downloading and decompressing the data takes time. You can grab a cup of coffee☕️ and patiently wait during this process. Maybe two. After deployment, you'll need to wait for the node to synchronize with the Fantom Blockchain Network (next step). - -```bash -pwd -# Make sure you are in aws-blockchain-node-runners/lib/fantom -npx cdk deploy fantom-single-node --json --outputs-file single-node-deploy.json -``` - -2. After the node is initialised from the snapshot you need to wait from another half a day to a day for the inital syncronization process to complete. The time depends on how fresh the snapshot was. You can use Amazon CloudWatch to track the progress. There is a script that publishes CloudWatch metrics every 5 minutes, where you can watch `sync distance` for consensus client and `blocks behind` for execution client. When the node is fully synced those two metrics shold show 0. To see them: - - - Navigate to [CloudWatch service](https://console.aws.amazon.com/cloudwatch/) (make sure you are in the region you have specified for `AWS_REGION`) - - Open `Dashboards` and select `fantom-single-node---` from the list of dashboards. - -Alternatively, you can manually check [Geth Syncing Status](https://geth.ethereum.org/docs/fundamentals/logs#syncing). Run the following query from within the same VPC and against the private IP of the single RPC node you deployed: - -```bash -INSTANCE_ID=$(cat single-node-deploy.json | jq -r '..|.singleinstanceid? | select(. != null)') -NODE_INTERNAL_IP=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID --query 'Reservations[*].Instances[*].PrivateIpAddress' --output text --region us-east-1) -echo "NODE_INTERNAL_IP=$NODE_INTERNAL_IP" -``` - -Copy output from the last `echo` command with `NODE_INTERNAL_IP=` and open [CloudShell tab with VPC environment](https://docs.aws.amazon.com/cloudshell/latest/userguide/creating-vpc-environment.html) to access internal IP address space. Paste `NODE_INTERNAL_IP=` into the new CloudShell tab. Then query the API: - -``` bash -# IMPORTANT: Run from CloudShell VPC environment tab -curl http://$NODE_INTERNAL_IP:18545 -X POST -H "Content-Type: application/json" \ ---data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' -``` - - It will return `false` if the node is in sync. If `eth_syncing` returns anything other than false it has not finished syncing. Generally, if syncing is still ongoing, `eth_syncing` will return block info that looks as follows: - -```javascript - { - jsonrpc: "2.0", - id: 1, - result: { - currentBlock: 37676547, - currentBlockHash: "0x0001ab120000187fd8069d3a4f6501d48ad4800778f40a76d79cf02469272a43", - currentBlockTime: "0x16ec7a4b9a82ebfe", - currentEpoch: "0x1ab13", - highestBlock: 80196141, - highestEpoch: "0x44343", - knownStates: 0, - pulledStates: 0, - startingBlock: 0 - } - } -``` - -3. Once the initial synchronization is done, you should be able to access the RPC API of that node from within the same VPC. The RPC port is not exposed to the Internet. Run the following query against the private IP of the single RPC node you deployed: - -```bash -INSTANCE_ID=$(cat single-node-deploy.json | jq -r '..|.singleinstanceid? | select(. != null)') -NODE_INTERNAL_IP=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID --query 'Reservations[*].Instances[*].PrivateIpAddress' --output text) -echo "NODE_INTERNAL_IP=$NODE_INTERNAL_IP" -``` - -Copy output from the last `echo` command with `NODE_INTERNAL_IP=` and open [CloudShell tab with VPC environment](https://docs.aws.amazon.com/cloudshell/latest/userguide/creating-vpc-environment.html) to access internal IP address space. Paste `NODE_INTERNAL_IP=` into the new CloudShell tab. Then query the API: - -``` bash -# IMPORTANT: Run from CloudShell VPC environment tab -# We query token balance of one of the system contracts: https://ftmscan.com/address/0x0000000000000000000000000000000000000001 -curl http://$NODE_INTERNAL_IP:18545 -X POST -H "Content-Type: application/json" \ ---data '{"method":"eth_getBalance","params":["0x0000000000000000000000000000000000000001", "latest"],"id":1,"jsonrpc":"2.0"}' -``` -You will get a response similar to this: - -```json -{"jsonrpc":"2.0","id":1,"result":"0xbe9fa76042c4daf622"} -``` - -### Option 2: Highly Available RPC Nodes - -1. The inital deployment of a Fantom Full node and downloading its snapshot typically takes about 6 hours. The Full node uses snapshots data, and downloading and decompressing the data takes time. You can grab a cup of coffee☕️ and patiently wait during this process. Maybe two. After deployment, you'll need to wait for hour or more for your nodes to synchronize with the Fantom Blockchain Network, depending on how fresh the snapshot was. - -```bash -pwd -# Make sure you are in aws-blockchain-node-runners/lib/fantom -npx cdk deploy fantom-ha-nodes --json --outputs-file ha-nodes-deploy.json -``` - -2. Give the new RPC nodes about few hours to initialize and then run the following query against the load balancer behind the RPC node created - -```bash -export RPC_ALB_URL=$(cat ha-nodes-deploy.json | jq -r '..|.alburl? | select(. != null)') -echo RPC_ALB_URL=$RPC_ALB_URL -``` - -Periodically check [Fantom Syncing Status](https://docs.chainstack.com/reference/fantom-syncing). Run the following query from within the same VPC and against the private IP of the load balancer fronting your nodes: - -``` bash -# IMPORTANT: Run from CloudShell VPC environment tab -curl http://$RPC_ALB_URL:18545 -X POST -H "Content-Type: application/json" \ ---data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' -``` - - It will return `false` if the node is in sync. If `eth_syncing` returns anything other than false it has not finished syncing. Generally, if syncing is still ongoing, `eth_syncing` will return block info that looks as follows: - -```javascript - { - jsonrpc: "2.0", - id: 1, - result: { - currentBlock: 37676547, - currentBlockHash: "0x0001ab120000187fd8069d3a4f6501d48ad4800778f40a76d79cf02469272a43", - currentBlockTime: "0x16ec7a4b9a82ebfe", - currentEpoch: "0x1ab13", - highestBlock: 80196141, - highestEpoch: "0x44343", - knownStates: 0, - pulledStates: 0, - startingBlock: 0 - } - } -``` - -> **NOTE:** *By default and for security reasons the load balancer is available only from inside the default VPC in the region where it is deployed. It is not available from the Internet and is not open for external connections. Before you consider opening it up to the internet, **please make sure you protect your RPC APIs**.* - -3. Once the initial synchronization is done, you should be able to access the RPC API of that node from within the same VPC. The RPC port is not exposed to the Internet. Run the following query against the private IP of the single RPC node you deployed: - -```bash -export RPC_ALB_URL=$(cat ha-nodes-deploy.json | jq -r '..|.alburl? | select(. != null)') -echo RPC_ALB_URL=$RPC_ALB_URL -``` - -``` bash -# IMPORTANT: Run from CloudShell VPC environment tab -# We query token balance of one of the system contracts: https://ftmscan.com/address/0x0000000000000000000000000000000000000001 -curl http://$RPC_ALB_URL:18545 -X POST -H "Content-Type: application/json" \ ---data '{"method":"eth_getBalance","params":["0x0000000000000000000000000000000000000001", "latest"],"id":1,"jsonrpc":"2.0"}' -``` - You will get a response similar to this: - -```json -{"jsonrpc":"2.0","id":1,"result":"0xbe9fa76042c4daf622"} -``` - -### Clearing up and undeploy everything - -Destroy HA Nodes, Single Nodes and Common stacks - -```bash -# Setting the AWS account id and region in case local .env file is lost -export AWS_ACCOUNT_ID= -export AWS_REGION= - -pwd -# Make sure you are in aws-blockchain-node-runners/lib/fantom - -# Destroy Single Node -cdk destroy fantom-single-node - -# Destroy HA Nodes -cdk destroy fantom-ha-nodes - - # Delete all common components like IAM role and Security Group -cdk destroy fantom-common -``` - -### FAQ - -1. How to check the logs of the clients running on my sync node? - -Please enter the [AWS Management Console - EC2 Instances](https://us-east-2.console.aws.amazon.com/ec2/home?region=us-east-2#Instances:instanceState=running), choose the correct region, copy the instance ID you need to query. - - > **NOTE:** *In this tutorial we chose not to use SSH and use Session Manager instead. That allows you to log all sessions in AWS CloudTrail to see who logged into the server and when. If you receive an error similar to `SessionManagerPlugin is not found`, [install Session Manager plugin for AWS CLI](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html)* - -```bash -pwd -# Make sure you are in aws-blockchain-node-runners/lib/fantom - -export INSTANCE_ID="i-**************" -echo "INSTANCE_ID=" $INSTANCE_ID -aws ssm start-session --target $INSTANCE_ID --region $AWS_REGION -sudo su ec2-user -sudo journalctl -o cat -fu fantom -``` -2. How to check the logs from the EC2 user-data script? - -Please enter the [AWS Management Console - EC2 Instances](https://us-east-2.console.aws.amazon.com/ec2/home?region=us-east-2#Instances:instanceState=running), choose the correct region, copy the instance ID you need to query. - -```bash -pwd -# Make sure you are in aws-blockchain-node-runners/lib/fantom - -export INSTANCE_ID="i-**************" -echo "INSTANCE_ID=" $INSTANCE_ID -aws ssm start-session --target $INSTANCE_ID --region $AWS_REGION -sudo cat /var/log/user-data.log -``` - -3. How can I restart the Fantom service? - -Please enter the [AWS Management Console - EC2 Instances](https://us-east-2.console.aws.amazon.com/ec2/home?region=us-east-2#Instances:instanceState=running), choose the correct region, copy the instance ID you need to query. - -```bash -pwd -# Make sure you are in aws-blockchain-node-runners/lib/fantom - -export INSTANCE_ID="i-**************" -echo "INSTANCE_ID=" $INSTANCE_ID - -aws ssm start-session --target $INSTANCE_ID --region $AWS_REGION -sudo systemctl stop fantom && sleep 20 && sudo systemctl start fantom -``` -> **NOTE:** *You can also try the following command to obtain more information* -> - *Check the Fantom service status* -> - `sudo systemctl status fantom` -> - *View Fantom service configuration* -> - `cat /etc/systemd/system/fantom.service` - -5. Where can I find more information about Fantom? - -Please refer to the [Fantom Developer Documentation](https://docs.fantom.foundation/) - -## Upgrades - -When nodes need to be upgraded or downgraded, [use blue/green pattern to do it](https://aws.amazon.com/blogs/devops/performing-bluegreen-deployments-with-aws-codedeploy-and-auto-scaling-groups/). This is not yet automated and contributions are welcome! diff --git a/lib/fantom/app.ts b/lib/fantom/app.ts deleted file mode 100644 index 82e36cc1..00000000 --- a/lib/fantom/app.ts +++ /dev/null @@ -1,55 +0,0 @@ -import 'dotenv/config' -import "source-map-support/register"; -import * as cdk from "aws-cdk-lib"; -import * as config from "./lib/config/fantomConfig"; -import * as configTypes from "./lib/config/fantomConfig.interface"; -import { FantomCommonStack } from "./lib/common-stack"; -import { FantomSingleNodeStack } from "./lib/single-node-stack"; -import { FantomHANodesStack } from "./lib/ha-nodes-stack"; -import * as nag from "cdk-nag"; - -const app = new cdk.App(); -cdk.Tags.of(app).add("Project", "AWS_FANTOM"); - -new FantomCommonStack(app, "fantom-common", { - stackName: `fantom-nodes-common`, - env: { account: config.baseConfig.accountId, region: config.baseConfig.region } -}); - -new FantomSingleNodeStack(app, "fantom-single-node", { - stackName: `fantom-single-node-${config.baseNodeConfig.nodeConfiguration}-${config.baseNodeConfig.fantomNetwork}`, - - env: { account: config.baseConfig.accountId, region: config.baseConfig.region }, - nodeRole: "single-node", - instanceType: config.baseNodeConfig.instanceType, - instanceCpuType: config.baseNodeConfig.instanceCpuType, - fantomNetwork: config.baseNodeConfig.fantomNetwork, - nodeConfiguration: config.baseNodeConfig.nodeConfiguration, - snapshotsUrl:config.baseNodeConfig.snapshotsUrl, - dataVolume: config.baseNodeConfig.dataVolume, -}); - -new FantomHANodesStack(app, "fantom-ha-nodes", { - stackName: `fantom-ha-nodes-${config.baseNodeConfig.nodeConfiguration}-${config.baseNodeConfig.fantomNetwork}`, - env: { account: config.baseConfig.accountId, region: config.baseConfig.region }, - nodeRole: "rpc-node", - instanceType: config.baseNodeConfig.instanceType, - instanceCpuType: config.baseNodeConfig.instanceCpuType, - fantomNetwork: config.baseNodeConfig.fantomNetwork, - nodeConfiguration: config.baseNodeConfig.nodeConfiguration, - snapshotsUrl:config.baseNodeConfig.snapshotsUrl, - dataVolume: config.baseNodeConfig.dataVolume, - - albHealthCheckGracePeriodMin: config.haNodeConfig.albHealthCheckGracePeriodMin, - heartBeatDelayMin: config.haNodeConfig.heartBeatDelayMin, - numberOfNodes: config.haNodeConfig.numberOfNodes -}); - -// Security Check -cdk.Aspects.of(app).add( - new nag.AwsSolutionsChecks({ - verbose: false, - reports: true, - logIgnores: false - }) -); diff --git a/lib/fantom/cdk.json b/lib/fantom/cdk.json deleted file mode 100644 index 7714e8c2..00000000 --- a/lib/fantom/cdk.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "app": "npx ts-node --prefer-ts-exts app.ts", - "watch": { - "include": [ - "**" - ], - "exclude": [ - "README.md", - "cdk*.json", - "**/*.d.ts", - "**/*.js", - "tsconfig.json", - "package*.json", - "yarn.lock", - "node_modules", - "test" - ] - }, - "context": { - "@aws-cdk/aws-lambda:recognizeLayerVersion": true, - "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": [ - "aws", - "aws-cn" - ], - "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, - "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, - "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, - "@aws-cdk/aws-iam:minimizePolicies": true, - "@aws-cdk/core:validateSnapshotRemovalPolicy": true, - "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, - "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, - "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, - "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, - "@aws-cdk/core:enablePartitionLiterals": true, - "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, - "@aws-cdk/aws-iam:standardizedServicePrincipals": true, - "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, - "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, - "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, - "@aws-cdk/aws-route53-patters:useCertificate": true, - "@aws-cdk/customresources:installLatestAwsSdkDefault": false, - "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, - "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, - "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, - "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, - "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, - "@aws-cdk/aws-redshift:columnId": true, - "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, - "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, - "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, - "@aws-cdk/aws-kms:aliasNameRef": true, - "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, - "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, - "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true - } -} diff --git a/lib/fantom/doc/assets/Architecture-HA-FANTOM-Node-Runners.drawio b/lib/fantom/doc/assets/Architecture-HA-FANTOM-Node-Runners.drawio deleted file mode 100644 index d6e8669e..00000000 --- a/lib/fantom/doc/assets/Architecture-HA-FANTOM-Node-Runners.drawio +++ /dev/null @@ -1,195 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/fantom/doc/assets/Architecture-HA-FANTOM-Node-Runners.drawio.png b/lib/fantom/doc/assets/Architecture-HA-FANTOM-Node-Runners.drawio.png deleted file mode 100644 index c352fee9..00000000 Binary files a/lib/fantom/doc/assets/Architecture-HA-FANTOM-Node-Runners.drawio.png and /dev/null differ diff --git a/lib/fantom/doc/assets/Architecture-Single-FANTOM-Node-Runners.drawio.png b/lib/fantom/doc/assets/Architecture-Single-FANTOM-Node-Runners.drawio.png deleted file mode 100644 index fd801403..00000000 Binary files a/lib/fantom/doc/assets/Architecture-Single-FANTOM-Node-Runners.drawio.png and /dev/null differ diff --git a/lib/fantom/ha-node-deploy.json b/lib/fantom/ha-node-deploy.json deleted file mode 100644 index 6b0fa634..00000000 --- a/lib/fantom/ha-node-deploy.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "fantom-ha-nodes-read-mainnet": { - "alburl": "internal-fantom-rpcno-MlhmELLUiozb-1571569363.us-east-1.elb.amazonaws.com" - } -} diff --git a/lib/fantom/jest.config.js b/lib/fantom/jest.config.js deleted file mode 100644 index 08263b89..00000000 --- a/lib/fantom/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - testEnvironment: 'node', - roots: ['/test'], - testMatch: ['**/*.test.ts'], - transform: { - '^.+\\.tsx?$': 'ts-jest' - } -}; diff --git a/lib/fantom/lib/assets/cfn-hup/cfn-auto-reloader.conf b/lib/fantom/lib/assets/cfn-hup/cfn-auto-reloader.conf deleted file mode 100644 index 3cd32a0a..00000000 --- a/lib/fantom/lib/assets/cfn-hup/cfn-auto-reloader.conf +++ /dev/null @@ -1,4 +0,0 @@ -[cfn-auto-reloader-hook] -triggers=post.update -path=Resources.WebServerHost.Metadata.AWS::CloudFormation::Init -action=/opt/aws/bin/cfn-init -v --stack __AWS_STACK_NAME__ --resource WebServerHost --region __AWS_REGION__ diff --git a/lib/fantom/lib/assets/cfn-hup/cfn-hup.conf b/lib/fantom/lib/assets/cfn-hup/cfn-hup.conf deleted file mode 100644 index 2163b37a..00000000 --- a/lib/fantom/lib/assets/cfn-hup/cfn-hup.conf +++ /dev/null @@ -1,5 +0,0 @@ -[main] -stack=__AWS_STACK_ID__ -region=__AWS_REGION__ -# The interval used to check for changes to the resource metadata in minutes. Default is 15 -interval=2 diff --git a/lib/fantom/lib/assets/cfn-hup/cfn-hup.service b/lib/fantom/lib/assets/cfn-hup/cfn-hup.service deleted file mode 100644 index 2660ea46..00000000 --- a/lib/fantom/lib/assets/cfn-hup/cfn-hup.service +++ /dev/null @@ -1,8 +0,0 @@ -[Unit] -Description=cfn-hup daemon -[Service] -Type=simple -ExecStart=/usr/local/bin/cfn-hup -Restart=always -[Install] -WantedBy=multi-user.target diff --git a/lib/fantom/lib/assets/cw-agent.json b/lib/fantom/lib/assets/cw-agent.json deleted file mode 100644 index 28833017..00000000 --- a/lib/fantom/lib/assets/cw-agent.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "agent": { - "metrics_collection_interval": 60, - "run_as_user": "root" - }, - "metrics": { - "aggregation_dimensions": [ - [ - "InstanceId" - ] - ], - "append_dimensions": { - "InstanceId": "${aws:InstanceId}" - }, - "metrics_collected": { - "cpu": { - "measurement": [ - "cpu_usage_idle", - "cpu_usage_iowait", - "cpu_usage_user", - "cpu_usage_system" - ], - "metrics_collection_interval": 60, - "resources": [ - "*" - ], - "totalcpu": false - }, - "disk": { - "measurement": [ - "used_percent" - ], - "metrics_collection_interval": 60, - "resources": [ - "*" - ] - }, - "diskio": { - "measurement": [ - "io_time", - "write_bytes", - "read_bytes", - "writes", - "reads", - "write_time", - "read_time", - "iops_in_progress" - ], - "metrics_collection_interval": 60, - "resources": [ - "*" - ] - }, - "mem": { - "measurement": [ - "mem_used_percent", - "mem_cached" - ], - "metrics_collection_interval": 60 - }, - "netstat": { - "measurement": [ - "tcp_established", - "tcp_time_wait" - ], - "metrics_collection_interval": 60 - }, - "swap": { - "measurement": [ - "swap_used_percent" - ], - "metrics_collection_interval": 60 - } - } - } -} diff --git a/lib/fantom/lib/assets/download-snapshot.sh b/lib/fantom/lib/assets/download-snapshot.sh deleted file mode 100644 index e36a7c63..00000000 --- a/lib/fantom/lib/assets/download-snapshot.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -set +e - -source /etc/environment - -echo "Downloading FANTOM snapshot." - -cd /data - -if [[ -n ${FANTOM_SNAPSHOTS_URI} && ${FANTOM_SNAPSHOTS_URI} != "none" ]]; then - for FILE in `curl ${FANTOM_SNAPSHOTS_URI}`; do - echo $FILE; - axel -n 20 ${FANTOM_SNAPSHOTS_URI%%/latest/listtgzfiles.txt}/$FILE && \ - tar --use-compress-program="pigz -d" -xvf ${FILE##*/} && \ - rm ${FILE##*/} || \ - echo "Problem with downloading or expanding file $FILE" - done -fi - -echo "Downloading FANTOM snapshot finished" - -echo "FANTOM snapshot is ready !!!" diff --git a/lib/fantom/lib/assets/fantom-checker/syncchecker-fantom.sh b/lib/fantom/lib/assets/fantom-checker/syncchecker-fantom.sh deleted file mode 100644 index d5c0df2d..00000000 --- a/lib/fantom/lib/assets/fantom-checker/syncchecker-fantom.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -FANTOM_SYNC_STATS=$(su bcuser -c '/home/bcuser/go-opera/build/opera attach --datadir=/data --exec "ftm.syncing"') -# Syncing status results: -# -#{ -# currentBlock: 37676547, -# currentBlockHash: "0x0001ab120000187fd8069d3a4f6501d48ad4800778f40a76d79cf02469272a43", -# currentBlockTime: "0x16ec7a4b9a82ebfe", -# currentEpoch: "0x1ab13", -# highestBlock: 82410549, -# highestEpoch: "0x45f22", -# knownStates: 0, -# pulledStates: 0, -# startingBlock: 0 -#} -# -# Synced status results: -# -# false -# -# TODO: if a node falls behind, does it revert to syncing? - -# If false, then get current block number: -if [ -n "$FANTOM_SYNC_STATS" ] && [ "$FANTOM_SYNC_STATS" != "false" ]; then - FANTOM_SYNC_BLOCK=$(su bcuser -c '/home/bcuser/go-opera/build/opera attach --datadir=/data --exec "ftm.syncing.currentBlock"') - FANTOM_HIGHEST_BLOCK=$(su bcuser -c '/home/bcuser/go-opera/build/opera attach --datadir=/data --exec "ftm.syncing.highestBlock"') - - FANTOM_BLOCKS_BEHIND="$((FANTOM_HIGHEST_BLOCK-FANTOM_SYNC_BLOCK))" - -else - FANTOM_SYNC_BLOCK=$(su bcuser -c '/home/bcuser/go-opera/build/opera attach --datadir=/data --exec "ftm.blockNumber"') - FANTOM_BLOCKS_BEHIND=0 -fi - -# Sending data to CloudWatch -TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") -INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id) -REGION=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/dynamic/instance-identity/document | jq .region -r) -TIMESTAMP=$(date +"%Y-%m-%dT%H:%M:%S%:z") - -aws cloudwatch put-metric-data --metric-name fantom_sync_block --namespace CWAgent --value $FANTOM_SYNC_BLOCK --timestamp $TIMESTAMP --dimensions InstanceId=$INSTANCE_ID --region $REGION -aws cloudwatch put-metric-data --metric-name fantom_blocks_behind --namespace CWAgent --value $FANTOM_BLOCKS_BEHIND --timestamp $TIMESTAMP --dimensions InstanceId=$INSTANCE_ID --region $REGION diff --git a/lib/fantom/lib/assets/fantom/read-template.sh b/lib/fantom/lib/assets/fantom/read-template.sh deleted file mode 100644 index b3bcc7c4..00000000 --- a/lib/fantom/lib/assets/fantom/read-template.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -set -e -echo "Script is starting..." -ulimit -n 500000 - -# Get local IP -TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") -export EC2_INTERNAL_IP=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s "http://169.254.169.254/latest/meta-data/local-ipv4") - -# Start read node -/home/bcuser/go-opera/build/opera --genesis /data/genesis.g \ - --datadir /data \ - --maxpeers 110 \ - --cache 24000 \ - --nousb \ - --db.preset ldb-1 \ - --syncmode snap \ - --http --http.port=18545 --http.corsdomain="*" \ - --http.addr="${EC2_INTERNAL_IP}" \ - --http.api=eth,web3,net,txpool,ftm - -echo "Script is still running..." diff --git a/lib/fantom/lib/assets/node-cw-dashboard.ts b/lib/fantom/lib/assets/node-cw-dashboard.ts deleted file mode 100644 index 3e4e0089..00000000 --- a/lib/fantom/lib/assets/node-cw-dashboard.ts +++ /dev/null @@ -1,235 +0,0 @@ -export const SyncNodeCWDashboardJSON = { - "widgets": [ - { - "height": 5, - "width": 6, - "y": 0, - "x": 0, - "type": "metric", - "properties": { - "view": "timeSeries", - "stat": "Average", - "period": 300, - "stacked": false, - "yAxis": { - "left": { - "min": 0 - } - }, - "region": "${REGION}", - "metrics": [ - [ "AWS/EC2", "CPUUtilization", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] - ], - "title": "CPU utilization (%)" - } - }, - { - "height": 5, - "width": 6, - "y": 5, - "x": 18, - "type": "metric", - "properties": { - "view": "timeSeries", - "stat": "Average", - "period": 300, - "stacked": false, - "yAxis": { - "left": { - "min": 0 - } - }, - "region": "${REGION}", - "metrics": [ - [ "AWS/EC2", "NetworkIn", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] - ], - "title": "Network in (bytes)" - } - }, - { - "height": 5, - "width": 6, - "y": 0, - "x": 18, - "type": "metric", - "properties": { - "view": "timeSeries", - "stat": "Average", - "period": 300, - "stacked": false, - "yAxis": { - "left": { - "min": 0 - } - }, - "region": "${REGION}", - "metrics": [ - [ "AWS/EC2", "NetworkOut", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] - ], - "title": "Network out (bytes)" - } - }, - { - "height": 5, - "width": 6, - "y": 10, - "x": 0, - "type": "metric", - "properties": { - "view": "timeSeries", - "stacked": false, - "region": "${REGION}", - "stat": "Average", - "period": 300, - "metrics": [ - [ "CWAgent", "mem_used_percent", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] - ], - "title": "Mem Used (%)" - } - }, - { - "height": 5, - "width": 6, - "y": 5, - "x": 0, - "type": "metric", - "properties": { - "view": "timeSeries", - "stacked": false, - "region": "${REGION}", - "stat": "Average", - "period": 300, - "metrics": [ - [ "CWAgent", "cpu_usage_iowait", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] - ], - "title": "CPU Usage IO wait (%)" - } - }, - { - "height": 5, - "width": 6, - "y": 0, - "x": 6, - "type": "metric", - "properties": { - "metrics": [ - [ { "expression": "m7/PERIOD(m7)", "label": "Read", "id": "e7" } ], - [ "CWAgent", "diskio_reads", "InstanceId", "${INSTANCE_ID}", "name", "nvme1n1", { "id": "m7", "visible": false, "stat": "Sum", "period": 60 } ], - [ { "expression": "m8/PERIOD(m8)", "label": "Write", "id": "e8" } ], - [ "CWAgent", "diskio_writes", "InstanceId", "${INSTANCE_ID}", "name", "nvme1n1", { "id": "m8", "visible": false, "stat": "Sum", "period": 60 } ] - ], - "view": "timeSeries", - "stacked": false, - "region": "${REGION}", - "stat": "Sum", - "period": 60, - "title": "nvme1n1 Volume Read/Write (IO/sec)" - } - }, - { - "height": 4, - "width": 6, - "y": 0, - "x": 12, - "type": "metric", - "properties": { - "metrics": [ - [ "CWAgent", "fantom_sync_block", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] - ], - "sparkline": true, - "view": "timeSeries", - "stacked": false, - "region": "${REGION}", - "stat": "Maximum", - "period": 60, - "title": "FANTOM Client Block Height" - } - }, - { - "height": 4, - "width": 6, - "y": 4, - "x": 12, - "type": "metric", - "properties": { - "sparkline": true, - "view": "timeSeries", - "stacked": false, - "region": "${REGION}", - "stat": "Maximum", - "period": 60, - "metrics": [ - [ "CWAgent", "fantom_blocks_behind", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] - ], - "title": "FANTOM Client Blocks Behind" - } - }, - { - "height": 5, - "width": 6, - "y": 5, - "x": 6, - "type": "metric", - "properties": { - "view": "timeSeries", - "stat": "Sum", - "period": 60, - "stacked": false, - "yAxis": { - "left": { - "min": 0 - } - }, - "region": "${REGION}", - "metrics": [ - [ { "expression": "IF(m7_2 !=0, (m7_1 / m7_2), 0)", "label": "Read", "id": "e7" } ], - [ "CWAgent", "diskio_read_time", "InstanceId", "${INSTANCE_ID}", "name", "nvme1n1", { "id": "m7_1", "visible": false, "stat": "Sum", "period": 60 } ], - [ "CWAgent", "diskio_reads", "InstanceId", "${INSTANCE_ID}", "name", "nvme1n1", { "id": "m7_2", "visible": false, "stat": "Sum", "period": 60 } ], - [ { "expression": "IF(m7_4 !=0, (m7_3 / m7_4), 0)", "label": "Write", "id": "e8" } ], - [ "CWAgent", "diskio_write_time", "InstanceId", "${INSTANCE_ID}", "name", "nvme1n1", { "id": "m7_3", "visible": false, "stat": "Sum", "period": 60 } ], - [ "CWAgent", "diskio_writes", "InstanceId", "${INSTANCE_ID}", "name", "nvme1n1", { "id": "m7_4", "visible": false, "stat": "Sum", "period": 60 } ] - ], - "title": "nvme1n1 Volume Read/Write latency (ms/op)" - } - }, - { - "height": 5, - "width": 6, - "y": 10, - "x": 6, - "type": "metric", - "properties": { - "metrics": [ - [ { "expression": "(m2/1048576)/PERIOD(m2)", "label": "Read", "id": "e2", "period": 60, "region": "${REGION}" } ], - [ "CWAgent", "diskio_read_bytes", "InstanceId", "${INSTANCE_ID}", "name", "nvme1n1", { "id": "m2", "stat": "Sum", "visible": false, "period": 60 } ], - [ { "expression": "(m3/1048576)/PERIOD(m3)", "label": "Write", "id": "e3", "period": 60, "region": "${REGION}" } ], - [ "CWAgent", "diskio_write_bytes", "InstanceId", "${INSTANCE_ID}", "name", "nvme1n1", { "id": "m3", "stat": "Sum", "visible": false, "period": 60 } ] - ], - "view": "timeSeries", - "stacked": false, - "region": "${REGION}", - "stat": "Average", - "period": 60, - "title": "nvme1n1 Volume Read/Write throughput (MiB/sec)" - } - }, - { - "height": 3, - "width": 6, - "y": 15, - "x": 6, - "type": "metric", - "properties": { - "metrics": [ - [ "CWAgent", "disk_used_percent", "path", "/data", "InstanceId", "${INSTANCE_ID}", "device", "nvme1n1", "fstype", "xfs", { "region": "${REGION}", "label": "/data" } ] - ], - "sparkline": true, - "view": "singleValue", - "region": "${REGION}", - "title": "nvme1n1 Disk Used (%)", - "period": 60, - "stat": "Average" - } - } - ] -} diff --git a/lib/fantom/lib/assets/setup-instance-store-volumes.sh b/lib/fantom/lib/assets/setup-instance-store-volumes.sh deleted file mode 100644 index 667e872a..00000000 --- a/lib/fantom/lib/assets/setup-instance-store-volumes.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -source /etc/environment - -if [[ "$DATA_VOLUME_TYPE" == "instance-store" ]]; then - echo "Data volume type is instance store" - export DATA_VOLUME_ID=/dev/$(lsblk -lnb | awk 'max < $4 {max = $4; vol = $1} END {print vol}') -fi - -if [ -n "$DATA_VOLUME_ID" ]; then - if [ $(df --output=target | grep -c "/data") -lt 1 ]; then - echo "Checking fstab for Data volume" - - sudo mkfs.xfs -f $DATA_VOLUME_ID - sleep 10 - DATA_VOLUME_UUID=$(lsblk -fn -o UUID $DATA_VOLUME_ID) - DATA_VOLUME_FSTAB_CONF="UUID=$DATA_VOLUME_UUID /data xfs defaults 0 2" - echo "DATA_VOLUME_ID="$DATA_VOLUME_ID - echo "DATA_VOLUME_UUID="$DATA_VOLUME_UUID - echo "DATA_VOLUME_FSTAB_CONF="$DATA_VOLUME_FSTAB_CONF - - # Check if data disc is already in fstab and replace the line if it is with the new disc UUID - if [ $(grep -c "data" /etc/fstab) -gt 0 ]; then - SED_REPLACEMENT_STRING="$(grep -n "/data" /etc/fstab | cut -d: -f1)s#.*#$DATA_VOLUME_FSTAB_CONF#" - sudo cp /etc/fstab /etc/fstab.bak - sudo sed -i "$SED_REPLACEMENT_STRING" /etc/fstab - else - echo $DATA_VOLUME_FSTAB_CONF | sudo tee -a /etc/fstab - fi - - sudo mount -a - - chown bcuser:bcuser -R /data - else - echo "Data volume is mounted, nothing changed" - fi -fi diff --git a/lib/fantom/lib/assets/user-data/node.sh b/lib/fantom/lib/assets/user-data/node.sh deleted file mode 100644 index 2282ffb7..00000000 --- a/lib/fantom/lib/assets/user-data/node.sh +++ /dev/null @@ -1,240 +0,0 @@ -#!/bin/bash -set +e - -{ echo "AWS_REGION=${_AWS_REGION_}" - echo "ASSETS_S3_PATH=${_ASSETS_S3_PATH_}" - echo "FANTOM_SNAPSHOTS_URI=${_FANTOM_SNAPSHOTS_URI_}" - echo "STACK_NAME=${_STACK_NAME_}" - echo "STACK_ID=${_STACK_ID_}" - echo "RESOURCE_ID=${_NODE_CF_LOGICAL_ID_}" - echo "DATA_VOLUME_TYPE=${_DATA_VOLUME_TYPE_}" - echo "DATA_VOLUME_SIZE=${_DATA_VOLUME_SIZE_}" - echo "FANTOM_NODE_TYPE=${_FANTOM_NODE_TYPE_}" - echo "FANTOM_NETWORK=${_FANTOM_NETWORK_}" - echo "LIFECYCLE_HOOK_NAME=${_LIFECYCLE_HOOK_NAME_}" - echo "AUTOSCALING_GROUP_NAME=${_AUTOSCALING_GROUP_NAME_}" - echo "NODE_ROLE=${_NODE_ROLE_}" - } >> /etc/environment - -source /etc/environment - -exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1 - -sleep 10 - -apt -yqq update -apt -yqq install awscli unzip jq python3-pip axel pigz build-essential git - -echo "Assigning Swap Space" -# Check if a swap file already exists -if [ -f /swapfile ]; then - # Remove the existing swap file - swapoff /swapfile - rm -rf /swapfile -fi - -# Create a new swap file -total_mem=$(grep MemTotal /proc/meminfo | awk '{print $2}') -# Calculate the swap size -swap_size=$((total_mem / 3)) -# Convert the swap size to MB -swap_size_mb=$((swap_size / 1024)) -unit=M -fallocate -l $swap_size_mb$unit /swapfile -chmod 600 /swapfile -mkswap /swapfile -swapon /swapfile - -# Enable the swap space to persist after reboot. -echo "/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab - -sysctl vm.swappiness=6 -sysctl vm.vfs_cache_pressure=10 -echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf -echo "vm.vfs_cache_pressure=10" | sudo tee -a /etc/sysctl.conf - -free -h - - - -# Download golang -mkdir -p temp && cd temp -wget https://go.dev/dl/go1.19.3.linux-amd64.tar.gz -sudo tar -xvf go1.19.3.linux-amd64.tar.gz -sudo rm -rf /usr/local/go -sudo mv go /usr/local/ -rm go1.19.3.linux-amd64.tar.gz - -# Setup golang environment variables -echo 'export GOROOT=/usr/local/go' > ~/.bash_aliases -echo 'export GOPATH=$HOME/go' >> ~/.bash_aliases -echo 'export PATH=$GOPATH/bin:$GOROOT/bin:$PATH' >> ~/.bash_aliases -source ~/.bash_aliases - -echo 'export GOROOT=/usr/local/go -export GOPATH=$HOME/go -export PATH=$GOPATH/bin:$GOROOT/bin:$PATH' > /etc/profile.d/custom-path.sh - -cd /opt - -echo "Downloading assets zip file" -aws s3 cp $ASSETS_S3_PATH ./assets.zip --region $AWS_REGION -unzip -q assets.zip - -echo "Install and configure CloudWatch agent" -wget -q https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb -sudo dpkg -i -E amazon-cloudwatch-agent.deb - -echo 'Configuring CloudWatch Agent' -mkdir -p /opt/aws/amazon-cloudwatch-agent/etc/ -cp /opt/cw-agent.json /opt/aws/amazon-cloudwatch-agent/etc/custom-amazon-cloudwatch-agent.json - -echo "Starting CloudWatch Agent" -/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \ --a fetch-config -c file:/opt/aws/amazon-cloudwatch-agent/etc/custom-amazon-cloudwatch-agent.json -m ec2 -s -systemctl status amazon-cloudwatch-agent - -echo 'Adding bcuser user and group' -sudo groupadd -g 1002 bcuser -sudo useradd -u 1002 -g 1002 -m -s /bin/bash bcuser -sudo usermod -aG sudo bcuser - -echo "Install FANTOM client" - -sudo su -l bcuser -c "git clone https://github.com/Fantom-foundation/go-opera.git && \ - cd go-opera/ && \ - git checkout release/1.1.3-rc.5 && \ - make" - - -echo 'Configuring FANTOM Node service as a system service' -# Copy startup script to correct location -if [[ "$FANTOM_NODE_TYPE" == "read" ]]; then - sudo mkdir /home/bcuser/bin - sudo mv /opt/fantom/read-template.sh /home/bcuser/bin/node.sh -fi - -sudo chmod +x /home/bcuser/bin/node.sh -sudo chown bcuser:bcuser -R /home/bcuser/ - - -if [[ "$STACK_ID" != "none" ]]; then - echo "Install CloudFormation helper scripts" - mkdir -p /opt/aws/ - pip3 install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz - sudo ln -s /usr/local/init/ubuntu/cfn-hup /etc/init.d/cfn-hup - - echo "Configuring CloudFormation helper scripts" - mkdir -p /etc/cfn/ - mv /opt/cfn-hup/cfn-hup.conf /etc/cfn/cfn-hup.conf - sed -i "s;__AWS_STACK_ID__;\"$STACK_ID\";g" /etc/cfn/cfn-hup.conf - sed -i "s;__AWS_REGION__;\"$AWS_REGION\";g" /etc/cfn/cfn-hup.conf - - mkdir -p /etc/cfn/hooks.d/ - mv /opt/cfn-hup/cfn-auto-reloader.conf /etc/cfn/hooks.d/cfn-auto-reloader.conf - sed -i "s;__AWS_STACK_NAME__;\"$STACK_NAME\";g" /etc/cfn/hooks.d/cfn-auto-reloader.conf - sed -i "s;__AWS_REGION__;\"$AWS_REGION\";g" /etc/cfn/hooks.d/cfn-auto-reloader.conf - - echo "Starting CloudFormation helper scripts as a service" - mv /opt/cfn-hup/cfn-hup.service /etc/systemd/system/cfn-hup.service - - systemctl daemon-reload - systemctl enable --now cfn-hup - systemctl start cfn-hup.service - -fi - -echo "Starting FANTOM as a service" -sudo bash -c 'cat > /etc/systemd/system/fantom.service </tmp/syncchecker.log 2>&1") | crontab - -crontab -l - -if [ "$NODE_ROLE" == "single-node" ]; then - echo "Single node. Signaling completion to CloudFormation" - cfn-signal --stack $STACK_NAME --resource $RESOURCE_ID --region $AWS_REGION -fi - -if [ "$NODE_ROLE" == "single-node" ]; then - echo "Single node. Wait for one minute for the volume to be available" - sleep 60 -fi - -mkdir -p /data - -if [[ "$DATA_VOLUME_TYPE" == "instance-store" ]]; then - echo "Data volume type is instance store" - - cd /opt - chmod +x /opt/setup-instance-store-volumes.sh - - (crontab -l; echo "@reboot /opt/setup-instance-store-volumes.sh >/tmp/setup-instance-store-volumes.log 2>&1") | crontab - - crontab -l - - /opt/setup-instance-store-volumes.sh - -else - echo "Data volume type is EBS" - - DATA_VOLUME_ID=/dev/$(lsblk -lnb | awk -v VOLUME_SIZE_BYTES="$DATA_VOLUME_SIZE" '{if ($4== VOLUME_SIZE_BYTES) {print $1}}') - sudo mkfs.xfs -f $DATA_VOLUME_ID - sleep 10 - DATA_VOLUME_UUID=$(lsblk -fn -o UUID $DATA_VOLUME_ID) - DATA_VOLUME_FSTAB_CONF="UUID=$DATA_VOLUME_UUID /data ext4 defaults 0 2" - echo "DATA_VOLUME_ID="$DATA_VOLUME_ID - echo "DATA_VOLUME_UUID="$DATA_VOLUME_UUID - echo "DATA_VOLUME_FSTAB_CONF="$DATA_VOLUME_FSTAB_CONF - echo $DATA_VOLUME_FSTAB_CONF | tee -a /etc/fstab - mount -a -fi - -lsblk -d - -# download snapshot if network is mainnet -if [ "$FANTOM_NETWORK" == "mainnet" ]; then - echo "Downloading FANTOM snapshot" - chmod +x /opt/download-snapshot.sh - /opt/download-snapshot.sh - if [ "$?" == 0 ]; then - echo "Snapshot download successful" - else - echo "Snapshot download failed, falling back to fresh sync" - fi -fi - -# Download Genesis file -wget https://download.fantom.network/mainnet-109331-no-history.g -O /data/genesis.g - -chown bcuser:bcuser -R /data - -sudo systemctl daemon-reload -sudo systemctl enable --now fantom - -if [[ "$LIFECYCLE_HOOK_NAME" != "none" ]]; then - echo "Signaling ASG lifecycle hook to complete" - TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") - INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id) - aws autoscaling complete-lifecycle-action --lifecycle-action-result CONTINUE --instance-id $INSTANCE_ID --lifecycle-hook-name "$LIFECYCLE_HOOK_NAME" --auto-scaling-group-name "$AUTOSCALING_GROUP_NAME" --region $AWS_REGION -fi - -echo "All Done!!" -set -e diff --git a/lib/fantom/lib/common-stack.ts b/lib/fantom/lib/common-stack.ts deleted file mode 100644 index 5e292885..00000000 --- a/lib/fantom/lib/common-stack.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as cdk from "aws-cdk-lib"; -import * as cdkConstructs from "constructs"; -import * as iam from "aws-cdk-lib/aws-iam"; -import * as nag from "cdk-nag"; - -export interface FantomCommonStackProps extends cdk.StackProps { - -} - -export class FantomCommonStack extends cdk.Stack { - AWS_STACK_NAME = cdk.Stack.of(this).stackName; - AWS_ACCOUNT_ID = cdk.Stack.of(this).account; - - constructor(scope: cdkConstructs.Construct, id: string, props: FantomCommonStackProps) { - super(scope, id, props); - - const region = cdk.Stack.of(this).region; - - const instanceRole = new iam.Role(this, "node-role", { - assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"), - iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy") - ] - }); - - instanceRole.addToPolicy(new iam.PolicyStatement({ - resources: ["*"], - actions: ["cloudformation:SignalResource"] - })); - - instanceRole.addToPolicy(new iam.PolicyStatement({ - resources: [`arn:aws:autoscaling:${region}:${this.AWS_ACCOUNT_ID}:autoScalingGroup:*:autoScalingGroupName/fantom-*`], - actions: ["autoscaling:CompleteLifecycleAction"] - })); - - // in lifecycle - instanceRole.addToPolicy(new iam.PolicyStatement({ - resources: [`arn:aws:autoscaling:${region}:${this.AWS_ACCOUNT_ID}:autoScalingGroup:*:autoScalingGroupName/fantom-*`], - actions: ["autoscaling:RecordLifecycleActionHeartbeat"] - })); - - - new cdk.CfnOutput(this, "Instance Role ARN", { - value: instanceRole.roleArn, - exportName: "FantomNodeInstanceRoleArn" - }); - - - // cdk-nag suppressions - nag.NagSuppressions.addResourceSuppressions( - this, - [ - { - id: "AwsSolutions-IAM4", - reason: "AmazonSSMManagedInstanceCore and CloudWatchAgentServerPolicy are restrictive enough" - }, - { - id: "AwsSolutions-IAM5", - reason: "Can't target specific stack: https://github.com/aws/aws-cdk/issues/22657" - } - ], - true - ); - } -} diff --git a/lib/fantom/lib/config/fantomConfig.interface.ts b/lib/fantom/lib/config/fantomConfig.interface.ts deleted file mode 100644 index 5e3efdf2..00000000 --- a/lib/fantom/lib/config/fantomConfig.interface.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as configTypes from "../../../constructs/config.interface"; - -export type FantomNetwork = "mainnet" ; -export type FantomNodeConfiguration = "read" ; - -export type FantomNodeRole = "rpc-node" | "single-node"; - -export interface FantomDataVolumeConfig extends configTypes.DataVolumeConfig { - -} - -export interface FantomBaseConfig extends configTypes.BaseConfig { - -} - -export interface FantomBaseNodeConfig extends configTypes.BaseNodeConfig { - fantomNetwork: FantomNetwork; - nodeConfiguration: FantomNodeConfiguration; - snapshotsUrl: string; - dataVolume: FantomDataVolumeConfig; -} - -export interface FantomHAConfig { - albHealthCheckGracePeriodMin: number; - heartBeatDelayMin: number; - numberOfNodes: number; -} diff --git a/lib/fantom/lib/config/fantomConfig.ts b/lib/fantom/lib/config/fantomConfig.ts deleted file mode 100644 index ff23bf3f..00000000 --- a/lib/fantom/lib/config/fantomConfig.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as ec2 from "aws-cdk-lib/aws-ec2"; -import * as configTypes from "./fantomConfig.interface"; -import * as constants from "../../../constructs/constants"; - -const parseDataVolumeType = (dataVolumeType: string) => { - switch (dataVolumeType) { - case "gp3": - return ec2.EbsDeviceVolumeType.GP3; - case "io2": - return ec2.EbsDeviceVolumeType.IO2; - case "io1": - return ec2.EbsDeviceVolumeType.IO1; - case "instance-store": - return constants.InstanceStoreageDeviceVolumeType; - default: - return ec2.EbsDeviceVolumeType.GP3; - } -}; - -export const baseConfig: configTypes.FantomBaseConfig = { - accountId: process.env.AWS_ACCOUNT_ID || "xxxxxxxxxxx", - region: process.env.AWS_REGION || "us-east-1" -}; - -export const baseNodeConfig: configTypes.FantomBaseNodeConfig = { - instanceType: new ec2.InstanceType(process.env.FANTOM_INSTANCE_TYPE ? process.env.FANTOM_INSTANCE_TYPE : "m6a.2xlarge"), - instanceCpuType: process.env.FANTOM_CPU_TYPE?.toLowerCase() == "x86_64" ? ec2.AmazonLinuxCpuType.X86_64 : ec2.AmazonLinuxCpuType.ARM_64 , - fantomNetwork: process.env.FANTOM_CLUSTER || "mainnet", - nodeConfiguration: process.env.FANTOM_NODE_CONFIGURATION || "read", - snapshotsUrl: process.env.FANTOM_SNAPSHOTS_URL || constants.NoneValue, - dataVolume: { - sizeGiB: process.env.FANTOM_DATA_VOL_SIZE ? parseInt(process.env.FANTOM_DATA_VOL_SIZE) : 2000, - type: parseDataVolumeType(process.env.FANTOM_DATA_VOL_TYPE?.toLowerCase() ? process.env.FANTOM_DATA_VOL_TYPE?.toLowerCase() : "gp3"), - iops: process.env.FANTOM_DATA_VOL_IOPS ? parseInt(process.env.FANTOM_DATA_VOL_IOPS) : 7000, - throughput: process.env.FANTOM_DATA_VOL_THROUGHPUT ? parseInt(process.env.FANTOM_DATA_VOL_THROUGHPUT) : 400 - } -}; - -export const haNodeConfig: configTypes.FantomHAConfig = { - albHealthCheckGracePeriodMin: process.env.FANTOM_HA_ALB_HEALTHCHECK_GRACE_PERIOD_MIN ? parseInt(process.env.FANTOM_HA_ALB_HEALTHCHECK_GRACE_PERIOD_MIN) : 10, - heartBeatDelayMin: process.env.FANTOM_HA_NODES_HEARTBEAT_DELAY_MIN ? parseInt(process.env.FANTOM_HA_NODES_HEARTBEAT_DELAY_MIN) : 40, - numberOfNodes: process.env.FANTOM_HA_NUMBER_OF_NODES ? parseInt(process.env.FANTOM_HA_NUMBER_OF_NODES) : 2 -}; diff --git a/lib/fantom/lib/constructs/fantom-node-security-group.ts b/lib/fantom/lib/constructs/fantom-node-security-group.ts deleted file mode 100644 index c2c879fc..00000000 --- a/lib/fantom/lib/constructs/fantom-node-security-group.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as cdk from "aws-cdk-lib"; -import * as cdkConstructs from "constructs"; -import * as ec2 from "aws-cdk-lib/aws-ec2"; -import * as nag from "cdk-nag"; - -export interface FantomNodeSecurityGroupConstructsProps { - vpc: cdk.aws_ec2.IVpc; -} - -export class FantomNodeSecurityGroupConstructs extends cdkConstructs.Construct { - public securityGroup: cdk.aws_ec2.SecurityGroup; - - constructor(scope: cdkConstructs.Construct, id: string, props: FantomNodeSecurityGroupConstructsProps) { - super(scope, id); - - const { - vpc - } = props; - - const sg = new ec2.SecurityGroup(this, `rpc-node-security-group`, { - vpc, - description: "Security Group for Blockchain nodes", - allowAllOutbound: true - }); - - // public ports - sg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(5050), "P2P"); - sg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.udp(5050), "P2P"); - - // private ports - sg.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(18545), "FANTOM RPC Port"); - sg.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(18546), "FANTOM WebSocket Port"); - - this.securityGroup = sg; - - // cdk-nag suppressions - nag.NagSuppressions.addResourceSuppressions( - this, - [ - { - id: "AwsSolutions-EC23", - reason: "Need to use wildcard for P2P ports" - } - ], - true - ); - } -} diff --git a/lib/fantom/lib/ha-nodes-stack.ts b/lib/fantom/lib/ha-nodes-stack.ts deleted file mode 100644 index 5970fbc0..00000000 --- a/lib/fantom/lib/ha-nodes-stack.ts +++ /dev/null @@ -1,137 +0,0 @@ -import * as cdk from "aws-cdk-lib"; -import * as cdkConstructs from "constructs"; -import * as ec2 from "aws-cdk-lib/aws-ec2"; -import * as iam from "aws-cdk-lib/aws-iam"; -import { AmazonLinuxGeneration, AmazonLinuxImage } from "aws-cdk-lib/aws-ec2"; -import * as s3Assets from "aws-cdk-lib/aws-s3-assets"; -import * as configTypes from "./config/fantomConfig.interface"; -import { FantomNodeSecurityGroupConstructs } from "./constructs/fantom-node-security-group"; -import * as fs from "fs"; -import * as path from "path"; -import * as constants from "../../constructs/constants"; -import { HANodesConstruct } from "../../constructs/ha-rpc-nodes-with-alb"; -import * as nag from "cdk-nag"; - -export interface FantomHANodesStackProps extends cdk.StackProps { - nodeRole: configTypes.FantomNodeRole; - instanceType: ec2.InstanceType; - instanceCpuType: ec2.AmazonLinuxCpuType; - fantomNetwork: configTypes.FantomNetwork; - nodeConfiguration: configTypes.FantomNodeConfiguration; - snapshotsUrl: string; - dataVolume: configTypes.FantomDataVolumeConfig; - albHealthCheckGracePeriodMin: number; - heartBeatDelayMin: number; - numberOfNodes: number; -} - -export class FantomHANodesStack extends cdk.Stack { - constructor(scope: cdkConstructs.Construct, id: string, props: FantomHANodesStackProps) { - super(scope, id, props); - - const REGION = cdk.Stack.of(this).region; - const STACK_NAME = cdk.Stack.of(this).stackName; - const lifecycleHookName = STACK_NAME; - const autoScalingGroupName = STACK_NAME; - - const { - nodeRole, - instanceType, - instanceCpuType, - fantomNetwork, - nodeConfiguration, - snapshotsUrl, - dataVolume, - albHealthCheckGracePeriodMin, - heartBeatDelayMin, - numberOfNodes - } = props; - - // using default vpc - const vpc = ec2.Vpc.fromLookup(this, "vpc", { isDefault: true }); - - // setting up the security group for the node from fantom-specific construct - const instanceSG = new FantomNodeSecurityGroupConstructs(this, "security-group", { vpc: vpc }); - - // getting the IAM Role ARM from the common stack - const importedInstanceRoleArn = cdk.Fn.importValue("FantomNodeInstanceRoleArn"); - - const instanceRole = iam.Role.fromRoleArn(this, "iam-role", importedInstanceRoleArn); - - // making our scripts and configs from the local "assets" directory available for instance to download - const asset = new s3Assets.Asset(this, "assets", { - path: path.join(__dirname, "assets") - }); - - asset.bucket.grantRead(instanceRole); - - // parsing user data script and injecting necessary variables - const nodeScript = fs.readFileSync(path.join(__dirname, "assets", "user-data", "node.sh")).toString(); - const dataVolumeSizeBytes = dataVolume.sizeGiB * constants.GibibytesToBytesConversionCoefficient; - - const modifiedInitNodeScript = cdk.Fn.sub(nodeScript, { - _AWS_REGION_: REGION, - _ASSETS_S3_PATH_: `s3://${asset.s3BucketName}/${asset.s3ObjectKey}`, - _STACK_NAME_: STACK_NAME, - _FANTOM_SNAPSHOTS_URI_: snapshotsUrl, - _STACK_ID_: constants.NoneValue, - _NODE_CF_LOGICAL_ID_: constants.NoneValue, - _FANTOM_NODE_TYPE_: nodeConfiguration, - _DATA_VOLUME_TYPE_: dataVolume.type, - _DATA_VOLUME_SIZE_: dataVolumeSizeBytes.toString(), - _NODE_ROLE_: nodeRole, - - _FANTOM_NETWORK_: fantomNetwork, - _LIFECYCLE_HOOK_NAME_: lifecycleHookName, - _AUTOSCALING_GROUP_NAME_: autoScalingGroupName - }); - - // Use Ubuntu 22.04 LTS image for amd64. Find more: https://discourse.ubuntu.com/t/finding-ubuntu-images-with-the-aws-ssm-parameter-store/15507 - const ubuntu2204stableImageSsmName = "/aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id" - - const rpcNodes = new HANodesConstruct(this, "rpc-nodes", { - instanceType, - dataVolumes: [dataVolume], - rootDataVolumeDeviceName: "/dev/sda1", - machineImage: ec2.MachineImage.fromSsmParameter(ubuntu2204stableImageSsmName), - role: instanceRole, - vpc, - securityGroup: instanceSG.securityGroup, - userData: modifiedInitNodeScript, - numberOfNodes, - rpcPortForALB: 18545, - albHealthCheckGracePeriodMin, - heartBeatDelayMin, - lifecycleHookName: lifecycleHookName, - autoScalingGroupName: autoScalingGroupName - }); - - - - new cdk.CfnOutput(this, "alb-url", { value: rpcNodes.loadBalancerDnsName }); - - // Adding suppressions to the stack - nag.NagSuppressions.addResourceSuppressions( - this, - [ - { - id: "AwsSolutions-AS3", - reason: "No notifications needed" - }, - { - id: "AwsSolutions-S1", - reason: "No access log needed for ALB logs bucket" - }, - { - id: "AwsSolutions-EC28", - reason: "Using basic monitoring to save costs" - }, - { - id: "AwsSolutions-IAM5", - reason: "Need read access to the S3 bucket with assets" - } - ], - true - ); - } -} diff --git a/lib/fantom/lib/single-node-stack.ts b/lib/fantom/lib/single-node-stack.ts deleted file mode 100644 index 67a20ab6..00000000 --- a/lib/fantom/lib/single-node-stack.ts +++ /dev/null @@ -1,141 +0,0 @@ -import * as cdk from "aws-cdk-lib"; -import * as cdkConstructs from "constructs"; -import * as ec2 from "aws-cdk-lib/aws-ec2"; -import * as iam from "aws-cdk-lib/aws-iam"; -import * as s3Assets from "aws-cdk-lib/aws-s3-assets"; -import * as path from "path"; -import * as fs from "fs"; -import * as nodeCwDashboard from "./assets/node-cw-dashboard" -import * as cw from 'aws-cdk-lib/aws-cloudwatch'; -import * as constants from "../../constructs/constants"; -import { SingleNodeConstruct } from "../../constructs/single-node" -import * as configTypes from "./config/fantomConfig.interface"; -import { FantomNodeSecurityGroupConstructs } from "./constructs/fantom-node-security-group" -import * as nag from "cdk-nag"; - -export interface FantomSingleNodeStackProps extends cdk.StackProps { - nodeRole: configTypes.FantomNodeRole; - instanceType: ec2.InstanceType; - instanceCpuType: ec2.AmazonLinuxCpuType; - fantomNetwork: configTypes.FantomNetwork; - nodeConfiguration: configTypes.FantomNodeConfiguration; - snapshotsUrl: string; - dataVolume: configTypes.FantomDataVolumeConfig; -} - -export class FantomSingleNodeStack extends cdk.Stack { - constructor(scope: cdkConstructs.Construct, id: string, props: FantomSingleNodeStackProps) { - super(scope, id, props); - - // Setting up necessary environment variables - const REGION = cdk.Stack.of(this).region; - const STACK_NAME = cdk.Stack.of(this).stackName; - const STACK_ID = cdk.Stack.of(this).stackId; - const availabilityZones = cdk.Stack.of(this).availabilityZones; - const chosenAvailabilityZone = availabilityZones.slice(0, 1)[0]; - - // Getting our config from initialization properties - const { - instanceType, - nodeRole, - instanceCpuType, - fantomNetwork, - nodeConfiguration, - snapshotsUrl, - dataVolume, - } = props; - - // Using default VPC - const vpc = ec2.Vpc.fromLookup(this, "vpc", { isDefault: true }); - - // Setting up the security group for the node from Ethereum-specific construct - const instanceSG = new FantomNodeSecurityGroupConstructs (this, "security-group", { - vpc: vpc, - }) - - // Making our scripts and configis from the local "assets" directory available for instance to download - const asset = new s3Assets.Asset(this, "assets", { - path: path.join(__dirname, "assets"), - }); - - // Getting the snapshot bucket name and IAM role ARN from the common stack - const importedInstanceRoleArn = cdk.Fn.importValue("FantomNodeInstanceRoleArn"); - - const instanceRole = iam.Role.fromRoleArn(this, "iam-role", importedInstanceRoleArn); - - // Making sure our instance will be able to read the assets - asset.bucket.grantRead(instanceRole); - - // Use Ubuntu 22.04 LTS image for amd64. Find more: https://discourse.ubuntu.com/t/finding-ubuntu-images-with-the-aws-ssm-parameter-store/15507 - const ubuntu2204stableImageSsmName = "/aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id" - - // Setting up the node using generic Single Node constract - const node = new SingleNodeConstruct(this, "single-node", { - instanceName: STACK_NAME, - instanceType, - dataVolumes: [dataVolume], - rootDataVolumeDeviceName: "/dev/sda1", - machineImage: ec2.MachineImage.fromSsmParameter(ubuntu2204stableImageSsmName), - vpc, - availabilityZone: chosenAvailabilityZone, - role: instanceRole, - securityGroup: instanceSG.securityGroup, - vpcSubnets: { - subnetType: ec2.SubnetType.PUBLIC, - }, - }); - - // Parsing user data script and injecting necessary variables - const userData = fs.readFileSync(path.join(__dirname, "assets", "user-data", "node.sh")).toString(); - - const dataVolumeSizeBytes = dataVolume.sizeGiB * constants.GibibytesToBytesConversionCoefficient; - - const modifiedUserData = cdk.Fn.sub(userData, { - _AWS_REGION_: REGION, - _ASSETS_S3_PATH_: `s3://${asset.s3BucketName}/${asset.s3ObjectKey}`, - _STACK_NAME_: STACK_NAME, - _FANTOM_SNAPSHOTS_URI_: snapshotsUrl, - _STACK_ID_: STACK_ID, - _NODE_CF_LOGICAL_ID_: node.nodeCFLogicalId, - _FANTOM_NODE_TYPE_: nodeConfiguration, - _DATA_VOLUME_TYPE_: dataVolume.type, - _DATA_VOLUME_SIZE_: dataVolumeSizeBytes.toString(), - _NODE_ROLE_: nodeRole, - - _FANTOM_NETWORK_: fantomNetwork, - _LIFECYCLE_HOOK_NAME_: constants.NoneValue, - _AUTOSCALING_GROUP_NAME_: constants.NoneValue, - }); - - // Adding modified userdata script to the instance prepared fro us by Single Node constract - node.instance.addUserData(modifiedUserData); - - // Adding CloudWatch dashboard to the node - const dashboardString = cdk.Fn.sub(JSON.stringify(nodeCwDashboard.SyncNodeCWDashboardJSON), { - INSTANCE_ID:node.instanceId, - INSTANCE_NAME: STACK_NAME, - REGION: REGION, - }) - - new cw.CfnDashboard(this, 'single-cw-dashboard', { - dashboardName: `${STACK_NAME}-${node.instanceId}`, - dashboardBody: dashboardString, - }); - - new cdk.CfnOutput(this, "single-instance-id", { - value: node.instanceId, - }); - - // Adding suppressions to the stack - nag.NagSuppressions.addResourceSuppressions( - this, - [ - { - id: "AwsSolutions-IAM5", - reason: "Need read and write access to the S3 bucket", - }, - ], - true - ); - } -} diff --git a/lib/fantom/package.json b/lib/fantom/package.json deleted file mode 100644 index 92957cc6..00000000 --- a/lib/fantom/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "aws-blockchain-node-runners-fantom", - "version": "0.1.0", - "bin": { - "scroll": "bin/fantom.js" - }, - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "test": "jest", - "cdk": "cdk", - "cdk_deploy_common": "cdk deploy fantom-common", - "cdk_deploy_ha_nodes": "cdk deploy fantom-ha-nodes", - "cdk_destroy_common": "cdk destroy fantom-common", - "cdk_destroy_ha_nodes": "cdk destroy fantom-ha-nodes" - } -} diff --git a/lib/fantom/sample-configs/.env-sample-read b/lib/fantom/sample-configs/.env-sample-read deleted file mode 100644 index 51eb7195..00000000 --- a/lib/fantom/sample-configs/.env-sample-read +++ /dev/null @@ -1,29 +0,0 @@ -############################################################# -# Example configuration for FANTOM nodes runner app on AWS # -############################################################# - -## Set the AWS account is and region for your environment ## -AWS_ACCOUNT_ID="xxxxxxxxxxx" -AWS_REGION="us-east-1" - -## Optional FANTOM snapshots download link # IMPORTANT !!! Make sure the url is valid before you use it! -FANTOM_SNAPSHOTS_URL=https://snapshot.fantom.network/files/snapsync/latest/listtgzfiles.txt # Optional param. Do a full sync if not provided. - -## Common configuration parameters ## -FANTOM_NETWORK="mainnet" # All options: "mainnet", "testnet" -FANTOM_NODE_CONFIGURATION="read" # All options: "read". Options "api" and "validator" are not yet supported - -## Instance Nodes -FANTOM_INSTANCE_TYPE="i3en.xlarge" -FANTOM_CPU_TYPE="x86_64" # All options: "x86_64", "ARM_64". IMPORTANT: Make sure the CPU type matches the instance type used - -# Data volume configuration -FANTOM_DATA_VOL_TYPE="instance-store" # Other options: "io1" | "io2" | "gp3" | "instance-store" . IMPORTANT: Use "instance-store" option only with instance types that support that feature, like popular for node im4gn, d3, i3en, and i4i instance families -FANTOM_DATA_VOL_SIZE="2000" # Current required data size to keep both snapshot archive and unarchived version of it -FANTOM_DATA_VOL_IOPS="7000" # Max IOPS for EBS volumes (not applicable for "instance-store") -FANTOM_DATA_VOL_THROUGHPUT="400" # Max throughput for EBS gp3 volumes (not applicable for "io1" | "io2" | "instance-store") - -## HA nodes configuration ## -FANTOM_HA_NUMBER_OF_NODES="2" # Total number of RPC nodes to be provisioned. Default: 2 -FANTOM_HA_ALB_HEALTHCHECK_GRACE_PERIOD_MIN="10080" # Time enough to initialize the instance -FANTOM_HA_NODES_HEARTBEAT_DELAY_MIN="120" # Time sufficient enough for a node do sync diff --git a/lib/fantom/test/.env-test b/lib/fantom/test/.env-test deleted file mode 100644 index 573b5054..00000000 --- a/lib/fantom/test/.env-test +++ /dev/null @@ -1,29 +0,0 @@ -############################################################# -# Example configuration for FANTOM nodes runner app on AWS # -############################################################# - -## Set the AWS account is and region for your environment ## -AWS_ACCOUNT_ID="xxxxxxxxxxx" -AWS_REGION="ap-southeast-2" - -## Optional FANTOM snapshots download link # IMPORTANT !!! Make sure the url is valid before you use it! -#FANTOM_SNAPSHOTS_URI=https://snapshot.fantom.network/files/snapsync/ # Optional param. We extract the actual URL from https://raw.githubusercontent.com/48Club/fantom-snapshots/main/data.json if nothing is specified. Otherwise, check the latest vesion for Full node here: https://github.com/48Club/fantom-snapshots - -## Common configuration parameters ## -FANTOM_NETWORK="mainnet" # All options: "mainnet", "testnet" -FANTOM_NODE_CONFIGURATION="read" # All options: "read", "api", "validator" - -## Instance Nodes -FANTOM_INSTANCE_TYPE="m6a.2xlarge" -FANTOM_CPU_TYPE="x86_64" # All options: "x86_64", "ARM_64". IMPORTANT: Make sure the CPU type matches the instance type used - -# Data volume configuration -FANTOM_DATA_VOL_TYPE="gp3" # Other options: "io1" | "io2" | "gp3" | "instance-store" . IMPORTANT: Use "instance-store" option only with instance types that support that feature, like popular for node im4gn, d3, i3en, and i4i instance families -FANTOM_DATA_VOL_SIZE="2000" # Current required data size to keep both snapshot archive and unarchived version of it -FANTOM_DATA_VOL_IOPS="7000" # Max IOPS for EBS volumes (not applicable for "instance-store") -FANTOM_DATA_VOL_THROUGHPUT="400" # Max throughput for EBS gp3 volumes (not applicable for "io1" | "io2" | "instance-store") - -## HA nodes configuration ## -FANTOM_HA_NUMBER_OF_NODES="2" # Total number of RPC nodes to be provisioned. Default: 2 -FANTOM_HA_ALB_HEALTHCHECK_GRACE_PERIOD_MIN="300" # Time enough to initialize the instance -FANTOM_HA_NODES_HEARTBEAT_DELAY_MIN="120" # Time sufficient enough for a node do sync diff --git a/lib/fantom/test/common-stack.test.ts b/lib/fantom/test/common-stack.test.ts deleted file mode 100644 index 3c39162c..00000000 --- a/lib/fantom/test/common-stack.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Match, Template } from "aws-cdk-lib/assertions"; -import * as cdk from "aws-cdk-lib"; -import * as dotenv from 'dotenv'; -dotenv.config({ path: './test/.env-test' }); -import * as config from "../lib/config/fantomConfig"; -import { FantomCommonStack } from "../lib/common-stack"; - -describe("FANTOMCommonStack", () => { - test("synthesizes the way we expect", () => { - const app = new cdk.App(); - - // Create the FantomCommonStack. - const fantomCommonStack = new FantomCommonStack(app, "fantom-common", { - env: { account: config.baseConfig.accountId, region: config.baseConfig.region }, - stackName: `fantom-nodes-common`, - }); - - // Prepare the stack for assertions. - const template = Template.fromStack(fantomCommonStack); - - // Has EC2 instance role. - template.hasResourceProperties("AWS::IAM::Role", { - AssumeRolePolicyDocument: { - Statement: [ - { - Action: "sts:AssumeRole", - Effect: "Allow", - Principal: { - Service: "ec2.amazonaws.com" - } - } - ] - }, - ManagedPolicyArns: [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - Ref: "AWS::Partition" - }, - ":iam::aws:policy/AmazonSSMManagedInstanceCore" - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/CloudWatchAgentServerPolicy" - ] - ] - } - ] - }) - - }); -}); diff --git a/lib/fantom/test/ha-nodes-stack.test.ts b/lib/fantom/test/ha-nodes-stack.test.ts deleted file mode 100644 index 3c2b5a6b..00000000 --- a/lib/fantom/test/ha-nodes-stack.test.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { Match, Template } from "aws-cdk-lib/assertions"; -import * as cdk from "aws-cdk-lib"; -import * as dotenv from 'dotenv'; -dotenv.config({ path: './test/.env-test' }); -import * as config from "../lib/config/fantomConfig"; -import * as configTypes from "../lib/config/fantomConfig.interface"; -import { FantomHANodesStack } from "../lib/ha-nodes-stack"; - -describe("FantomHANodesStack", () => { - test("synthesizes the way we expect", () => { - const app = new cdk.App(); - - // Create the FantomHANodesStack. - const fantomHANodesStack = new FantomHANodesStack(app, "fantom-sync-node", { - stackName: `fantom-ha-nodes-${config.baseNodeConfig.nodeConfiguration}`, - env: { account: config.baseConfig.accountId, region: config.baseConfig.region }, - nodeRole: "rpc-node", - - instanceType: config.baseNodeConfig.instanceType, - instanceCpuType: config.baseNodeConfig.instanceCpuType, - fantomNetwork: config.baseNodeConfig.fantomNetwork, - nodeConfiguration: config.baseNodeConfig.nodeConfiguration, - snapshotsUrl:config.baseNodeConfig.snapshotsUrl, - dataVolume: config.baseNodeConfig.dataVolume, - - albHealthCheckGracePeriodMin: config.haNodeConfig.albHealthCheckGracePeriodMin, - heartBeatDelayMin: config.haNodeConfig.heartBeatDelayMin, - numberOfNodes: config.haNodeConfig.numberOfNodes, - }); - - // Prepare the stack for assertions. - const template = Template.fromStack(fantomHANodesStack); - - // Has EC2 instance security group. - template.hasResourceProperties("AWS::EC2::SecurityGroup", { - GroupDescription: Match.anyValue(), - VpcId: Match.anyValue(), - SecurityGroupEgress: [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" - } - ], - SecurityGroupIngress: [ - { - "CidrIp": "0.0.0.0/0", - "Description": "P2P", - "FromPort": 5050, - "IpProtocol": "tcp", - "ToPort": 5050 - }, - { - "CidrIp": "0.0.0.0/0", - "Description": "P2P", - "FromPort": 5050, - "IpProtocol": "udp", - "ToPort": 5050 - }, - { - "CidrIp": "1.2.3.4/5", - "Description": "FANTOM RPC Port", - "FromPort": 18545, - "IpProtocol": "tcp", - "ToPort": 18545 - }, - { - "CidrIp": "1.2.3.4/5", - "Description": "FANTOM WebSocket Port", - "FromPort": 18546, - "IpProtocol": "tcp", - "ToPort": 18546 - }, - { - "Description": "Allow access from ALB to Blockchain Node", - "FromPort": 0, - "IpProtocol": "tcp", - "SourceSecurityGroupId": { - "Fn::GetAtt": [ - Match.anyValue(), - "GroupId" - ] - }, - "ToPort": 65535 - }, - ] - }) - - // Has security group from ALB to EC2. - template.hasResourceProperties("AWS::EC2::SecurityGroupIngress", { - Description: "Load balancer to target", - FromPort: 18545, - GroupId: Match.anyValue(), - IpProtocol: "tcp", - SourceSecurityGroupId: Match.anyValue(), - ToPort: 18545, - }) - - // Has launch template profile for EC2 instances. - template.hasResourceProperties("AWS::IAM::InstanceProfile", { - Roles: [Match.anyValue()] - }); - - // Has EC2 launch template. - template.hasResourceProperties("AWS::EC2::LaunchTemplate", { - LaunchTemplateData: { - BlockDeviceMappings: [ - { - "DeviceName": "/dev/sda1", - "Ebs": { - "DeleteOnTermination": true, - "Encrypted": true, - "Iops": 3000, - "Throughput": 125, - "VolumeSize": 46, - "VolumeType": "gp3" - } - }, - { - "DeviceName": "/dev/sdf", - "Ebs": { - "DeleteOnTermination": true, - "Encrypted": true, - "Iops": 7000, - "Throughput": 400, - "VolumeSize": 2000, - "VolumeType": "gp3" - } - } - ], - EbsOptimized: true, - IamInstanceProfile: Match.anyValue(), - ImageId: Match.anyValue(), - InstanceType:"m6a.2xlarge", - SecurityGroupIds: [Match.anyValue()], - UserData: Match.anyValue(), - TagSpecifications: Match.anyValue(), - } - }) - - // Has Auto Scaling Group. - template.hasResourceProperties("AWS::AutoScaling::AutoScalingGroup", { - AutoScalingGroupName: `fantom-ha-nodes-${config.baseNodeConfig.nodeConfiguration}`, - HealthCheckGracePeriod: config.haNodeConfig.albHealthCheckGracePeriodMin * 60, - HealthCheckType: "ELB", - DefaultInstanceWarmup: 60, - MinSize: "0", - MaxSize: "4", - DesiredCapacity: config.haNodeConfig.numberOfNodes.toString(), - VPCZoneIdentifier: Match.anyValue(), - TargetGroupARNs: Match.anyValue(), - }); - - // Has Auto Scaling Lifecycle Hook. - template.hasResourceProperties("AWS::AutoScaling::LifecycleHook", { - DefaultResult: "ABANDON", - HeartbeatTimeout: config.haNodeConfig.heartBeatDelayMin * 60, - LifecycleHookName: `fantom-ha-nodes-${config.baseNodeConfig.nodeConfiguration}`, - LifecycleTransition: "autoscaling:EC2_INSTANCE_LAUNCHING", - }); - - // Has Auto Scaling Security Group. - template.hasResourceProperties("AWS::EC2::SecurityGroup", { - GroupDescription: Match.anyValue(), - SecurityGroupEgress: [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" - } - ], - SecurityGroupIngress: [ - { - "CidrIp": "1.2.3.4/5", - "Description": "Blockchain Node RPC", - "FromPort": 18545, - "IpProtocol": "tcp", - "ToPort": 18545 - } - ], - VpcId: Match.anyValue(), - }); - - // Has ALB. - template.hasResourceProperties("AWS::ElasticLoadBalancingV2::LoadBalancer", { - LoadBalancerAttributes: [ - { - Key: "deletion_protection.enabled", - Value: "false" - }, - { - Key: "access_logs.s3.enabled", - Value: "true" - }, - { - Key: "access_logs.s3.bucket", - Value: Match.anyValue(), - }, - { - Key: "access_logs.s3.prefix", - Value: `fantom-ha-nodes-${config.baseNodeConfig.nodeConfiguration}` - } - ], - Scheme: "internal", - SecurityGroups: [ - Match.anyValue() - ], - "Subnets": [ - Match.anyValue(), - Match.anyValue() - ], - Type: "application", - }); - - // Has ALB listener. - template.hasResourceProperties("AWS::ElasticLoadBalancingV2::Listener", { - "DefaultActions": [ - { - "TargetGroupArn": Match.anyValue(), - Type: "forward" - } - ], - LoadBalancerArn: Match.anyValue(), - Port: 18545, - Protocol: "HTTP" - }) - - // Has ALB target group. - template.hasResourceProperties("AWS::ElasticLoadBalancingV2::TargetGroup", { - HealthCheckEnabled: true, - HealthCheckIntervalSeconds: 30, - HealthCheckPath: "/", - HealthCheckPort: "18545", - HealthyThresholdCount: 3, - Matcher: { - HttpCode: "200-299" - }, - Port: 18545, - Protocol: "HTTP", - TargetGroupAttributes: [ - { - Key: "deregistration_delay.timeout_seconds", - Value: "30" - }, - { - Key: "stickiness.enabled", - Value: "false" - } - ], - TargetType: "instance", - UnhealthyThresholdCount: 2, - VpcId: Match.anyValue(), - }) - }); -}); diff --git a/lib/fantom/test/single-node-stack.test.ts b/lib/fantom/test/single-node-stack.test.ts deleted file mode 100644 index 159e811a..00000000 --- a/lib/fantom/test/single-node-stack.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Match, Template } from "aws-cdk-lib/assertions"; -import * as cdk from "aws-cdk-lib"; -import * as dotenv from 'dotenv'; -dotenv.config({ path: './test/.env-test' }); -import * as config from "../lib/config/fantomConfig"; -import * as configTypes from "../lib/config/fantomConfig.interface"; -import { FantomSingleNodeStack } from "../lib/single-node-stack"; - -describe("FANTOMSingleNodeStack", () => { - test("synthesizes the way we expect", () => { - const app = new cdk.App(); - - // Create the EthSingleNodeStack. - const fantomSingleNodeStack = new FantomSingleNodeStack(app, "fantom-single-node", { - stackName: `fantom-single-node`, - - env: { account: config.baseConfig.accountId, region: config.baseConfig.region }, - nodeRole: "single-node", - instanceType: config.baseNodeConfig.instanceType, - instanceCpuType: config.baseNodeConfig.instanceCpuType, - fantomNetwork: config.baseNodeConfig.fantomNetwork, - nodeConfiguration: config.baseNodeConfig.nodeConfiguration, - snapshotsUrl:config.baseNodeConfig.snapshotsUrl, - dataVolume: config.baseNodeConfig.dataVolume, - }); - - // Prepare the stack for assertions. - const template = Template.fromStack(fantomSingleNodeStack); - - // Has EC2 instance security group. - template.hasResourceProperties("AWS::EC2::SecurityGroup", { - GroupDescription: Match.anyValue(), - VpcId: Match.anyValue(), - SecurityGroupEgress: [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" - } - ], - SecurityGroupIngress: [ - { - "CidrIp": "0.0.0.0/0", - "Description": "P2P", - "FromPort": 5050, - "IpProtocol": "tcp", - "ToPort": 5050 - }, - { - "CidrIp": "0.0.0.0/0", - "Description": "P2P", - "FromPort": 5050, - "IpProtocol": "udp", - "ToPort": 5050 - }, - { - "CidrIp": "1.2.3.4/5", - "Description": "FANTOM RPC Port", - "FromPort": 18545, - "IpProtocol": "tcp", - "ToPort": 18545 - }, - { - "CidrIp": "1.2.3.4/5", - "Description": "FANTOM WebSocket Port", - "FromPort": 18546, - "IpProtocol": "tcp", - "ToPort": 18546 - } - ] - }) - - // Has EC2 instance with node configuration - template.hasResourceProperties("AWS::EC2::Instance", { - AvailabilityZone: Match.anyValue(), - UserData: Match.anyValue(), - BlockDeviceMappings: [ - { - DeviceName: "/dev/sda1", - Ebs: { - DeleteOnTermination: true, - Encrypted: true, - Iops: 3000, - VolumeSize: 46, - VolumeType: "gp3" - } - } - ], - IamInstanceProfile: Match.anyValue(), - ImageId: Match.anyValue(), - InstanceType: "m6a.2xlarge", - Monitoring: true, - PropagateTagsToVolumeOnCreation: true, - SecurityGroupIds: Match.anyValue(), - SubnetId: Match.anyValue(), - }) - - // Has EBS data volume. - template.hasResourceProperties("AWS::EC2::Volume", { - AvailabilityZone: Match.anyValue(), - Encrypted: true, - Iops: 7000, - MultiAttachEnabled: false, - Size: 2000, - Throughput: 400, - VolumeType: "gp3" - }) - - // Has EBS data volume attachment. - template.hasResourceProperties("AWS::EC2::VolumeAttachment", { - Device: "/dev/sdf", - InstanceId: Match.anyValue(), - VolumeId: Match.anyValue(), - }) - - // Has CloudWatch dashboard. - template.hasResourceProperties("AWS::CloudWatch::Dashboard", { - DashboardBody: Match.anyValue(), - DashboardName: { - "Fn::Join": ["", ["fantom-single-node-",{ "Ref": Match.anyValue() }]] - } - }) - - }); -}); diff --git a/lib/fantom/tsconfig.json b/lib/fantom/tsconfig.json deleted file mode 100644 index 8e1979f3..00000000 --- a/lib/fantom/tsconfig.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "lib": [ - "es2020", - "dom" - ], - "declaration": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": false, - "inlineSourceMap": true, - "inlineSources": true, - "experimentalDecorators": true, - "strictPropertyInitialization": false, - "typeRoots": [ - "../../node_modules/@types" - ] - }, - "exclude": [ - "node_modules", - "cdk.out" - ] -} diff --git a/lib/solana/README.md b/lib/solana/README.md index 444eb5d5..9c70dca8 100644 --- a/lib/solana/README.md +++ b/lib/solana/README.md @@ -76,7 +76,7 @@ This is the Well-Architected checklist for Solana nodes implementation of the AW | | Authorization and access control | Use instance profile with Amazon Elastic Compute Cloud (Amazon EC2) instances | This solution uses AWS Identity and Access Management (AWS IAM) role instead of IAM user. | | | | Following principle of least privilege access | In all node types, root user is not used (using special user "solana" instead). | | | Application security | Security focused development practices | cdk-nag is being used with appropriate suppressions. | -| Cost optimization | Service selection | Use cost effective resources | 1/ AMD-based instances are used for Consensus and RPC node to save the costs. Consider compiling Graviton-based binaries to improve costs for compute. 2/ Cost-effective EBS gp3 are preferred instead of io2. 3/ Solana nodes generate a substantial amount of outgoing data traffic, which deeds to be addressed with non-technical means like getting private agreements with AWS. | +| Cost optimization | Service selection | Use cost effective resources | 1/ AMD-based instances are used for Consensus and RPC node to save the costs. Consider compiling Graviton-based binaries to improve costs for compute. 2/ Cost-effective EBS gp3 are preferred instead of io2. 3/ Solana nodes generate a substantial amount of outgoing data traffic, which needs to be addressed with non-technical means like getting private agreements with AWS. | | | Cost awareness | Estimate costs | Single RPC node with `r7a.12xlarge` EBS gp3 volumes about 2549 GB with On-Demand pricing will cost around US$2,665.67 per month in the US East (N. Virginia) region. More cost-optimal option with 3 year Compute Savings plan the cost goes down to $1,643.52 USD. Additionally, the data transfer costs can be about $1,356.80 USD per month for 15TB of outgoing traffic. Check pricing with [AWS Calculator](https://calculator.aws/) | | Reliability | Resiliency implementation | Withstand component failures | This solution uses AWS Application Load Balancer with RPC nodes for high availability. Newly provisioned Solana nodes triggered by Auto Scaling get up and running in about 30-50 minutes. | | | Data backup | How is data backed up? | Considering blockchain data is replicated by nodes automatically and Solana nodes sync from start within an hour, we don't use any additional mechanisms to backup the data. | diff --git a/lib/stacks/lib/config/stacksConfigDefaults.ts b/lib/stacks/lib/config/stacksConfigDefaults.ts index 7730ee5b..2f46b769 100644 --- a/lib/stacks/lib/config/stacksConfigDefaults.ts +++ b/lib/stacks/lib/config/stacksConfigDefaults.ts @@ -12,7 +12,7 @@ export function stacksNodeConfigDefaults( const isMainnet: boolean = stacksNetwork === "mainnet"; const defaultDataVolume: configTypes.StacksVolumeConfig = { - sizeGiB: isMainnet ? 512 : 256, + sizeGiB: isMainnet ? 1024 : 512, type: ec2.EbsDeviceVolumeType.GP3, iops: 12000, throughput: 700 diff --git a/lib/stacks/sample-configs/.env-sample-follower b/lib/stacks/sample-configs/.env-sample-follower index 2a9e057f..88776003 100644 --- a/lib/stacks/sample-configs/.env-sample-follower +++ b/lib/stacks/sample-configs/.env-sample-follower @@ -33,7 +33,7 @@ STACKS_NETWORK="mainnet" # Network onto whi # STACKS_MINER_SECRET_ARN="none" # Optional. ARN for AWS Secrets Manager secret with miner private key # # Storage configuration -# STACKS_DATA_VOL_SIZE=512 # Volume size in GB for node data storage +# STACKS_DATA_VOL_SIZE=1024 # Volume size in GB for node data storage # STACKS_DATA_VOL_TYPE="gp3" # EBS volume type. Example: "gp2", "io2" for high-performance needs # STACKS_DATA_VOL_IOPS=12000 # IOPS for "io2" volume type. IMPORTANT: Adjust based on performance needs # STACKS_DATA_VOL_THROUGHPUT=700 # Throughput in MB/s for "gp3" volume type. Check compatibility with chosen volume type diff --git a/package-lock.json b/package-lock.json index ae850b1e..265a4189 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,9 @@ "name": "aws-blockchain-node-runners", "version": "0.1.0", "dependencies": { + "@aws-sdk/client-secrets-manager": "^3.775.0", "aws-cdk-lib": "^2.184.0", + "base64url": "^3.0.1", "constructs": "^10.3.0", "dotenv": "^16.4.5", "source-map-support": "^0.5.21" @@ -82,6 +84,580 @@ "node": ">=10" } }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager": { + "version": "3.777.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.777.0.tgz", + "integrity": "sha512-HqBGQPFUZMTOkQJB6JLv7Jopfz+cBP4spzjpBlZ/JeJShMAXw9To2mxi22jU5qGGWPGH5y4tVXnw4aVf2TOPhQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.775.0", + "@aws-sdk/credential-provider-node": "3.777.0", + "@aws-sdk/middleware-host-header": "3.775.0", + "@aws-sdk/middleware-logger": "3.775.0", + "@aws-sdk/middleware-recursion-detection": "3.775.0", + "@aws-sdk/middleware-user-agent": "3.775.0", + "@aws-sdk/region-config-resolver": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@aws-sdk/util-endpoints": "3.775.0", + "@aws-sdk/util-user-agent-browser": "3.775.0", + "@aws-sdk/util-user-agent-node": "3.775.0", + "@smithy/config-resolver": "^4.1.0", + "@smithy/core": "^3.2.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.0", + "@smithy/middleware-retry": "^4.1.0", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.8", + "@smithy/util-defaults-mode-node": "^4.0.8", + "@smithy/util-endpoints": "^3.0.2", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.2", + "@smithy/util-utf8": "^4.0.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.777.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.777.0.tgz", + "integrity": "sha512-0+z6CiAYIQa7s6FJ+dpBYPi9zr9yY5jBg/4/FGcwYbmqWPXwL9Thdtr0FearYRZgKl7bhL3m3dILCCfWqr3teQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.775.0", + "@aws-sdk/middleware-host-header": "3.775.0", + "@aws-sdk/middleware-logger": "3.775.0", + "@aws-sdk/middleware-recursion-detection": "3.775.0", + "@aws-sdk/middleware-user-agent": "3.775.0", + "@aws-sdk/region-config-resolver": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@aws-sdk/util-endpoints": "3.775.0", + "@aws-sdk/util-user-agent-browser": "3.775.0", + "@aws-sdk/util-user-agent-node": "3.775.0", + "@smithy/config-resolver": "^4.1.0", + "@smithy/core": "^3.2.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.0", + "@smithy/middleware-retry": "^4.1.0", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.8", + "@smithy/util-defaults-mode-node": "^4.0.8", + "@smithy/util-endpoints": "^3.0.2", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.775.0.tgz", + "integrity": "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/core": "^3.2.0", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/signature-v4": "^5.0.2", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/util-middleware": "^4.0.2", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.775.0.tgz", + "integrity": "sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.775.0.tgz", + "integrity": "sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/util-stream": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.777.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.777.0.tgz", + "integrity": "sha512-1X9mCuM9JSQPmQ+D2TODt4THy6aJWCNiURkmKmTIPRdno7EIKgAqrr/LLN++K5mBf54DZVKpqcJutXU2jwo01A==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/credential-provider-env": "3.775.0", + "@aws-sdk/credential-provider-http": "3.775.0", + "@aws-sdk/credential-provider-process": "3.775.0", + "@aws-sdk/credential-provider-sso": "3.777.0", + "@aws-sdk/credential-provider-web-identity": "3.777.0", + "@aws-sdk/nested-clients": "3.777.0", + "@aws-sdk/types": "3.775.0", + "@smithy/credential-provider-imds": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.777.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.777.0.tgz", + "integrity": "sha512-ZD66ywx1Q0KyUSuBXZIQzBe3Q7MzX8lNwsrCU43H3Fww+Y+HB3Ncws9grhSdNhKQNeGmZ+MgKybuZYaaeLwJEQ==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.775.0", + "@aws-sdk/credential-provider-http": "3.775.0", + "@aws-sdk/credential-provider-ini": "3.777.0", + "@aws-sdk/credential-provider-process": "3.775.0", + "@aws-sdk/credential-provider-sso": "3.777.0", + "@aws-sdk/credential-provider-web-identity": "3.777.0", + "@aws-sdk/types": "3.775.0", + "@smithy/credential-provider-imds": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.775.0.tgz", + "integrity": "sha512-A6k68H9rQp+2+7P7SGO90Csw6nrUEm0Qfjpn9Etc4EboZhhCLs9b66umUsTsSBHus4FDIe5JQxfCUyt1wgNogg==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.777.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.777.0.tgz", + "integrity": "sha512-9mPz7vk9uE4PBVprfINv4tlTkyq1OonNevx2DiXC1LY4mCUCNN3RdBwAY0BTLzj0uyc3k5KxFFNbn3/8ZDQP7w==", + "dependencies": { + "@aws-sdk/client-sso": "3.777.0", + "@aws-sdk/core": "3.775.0", + "@aws-sdk/token-providers": "3.777.0", + "@aws-sdk/types": "3.775.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.777.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.777.0.tgz", + "integrity": "sha512-uGCqr47fnthkqwq5luNl2dksgcpHHjSXz2jUra7TXtFOpqvnhOW8qXjoa1ivlkq8qhqlaZwCzPdbcN0lXpmLzQ==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/nested-clients": "3.777.0", + "@aws-sdk/types": "3.775.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.775.0.tgz", + "integrity": "sha512-tkSegM0Z6WMXpLB8oPys/d+umYIocvO298mGvcMCncpRl77L9XkvSLJIFzaHes+o7djAgIduYw8wKIMStFss2w==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.775.0.tgz", + "integrity": "sha512-FaxO1xom4MAoUJsldmR92nT1G6uZxTdNYOFYtdHfd6N2wcNaTuxgjIvqzg5y7QIH9kn58XX/dzf1iTjgqUStZw==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.775.0.tgz", + "integrity": "sha512-GLCzC8D0A0YDG5u3F5U03Vb9j5tcOEFhr8oc6PDk0k0vm5VwtZOE6LvK7hcCSoAB4HXyOUM0sQuXrbaAh9OwXA==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.775.0.tgz", + "integrity": "sha512-7Lffpr1ptOEDE1ZYH1T78pheEY1YmeXWBfFt/amZ6AGsKSLG+JPXvof3ltporTGR2bhH/eJPo7UHCglIuXfzYg==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@aws-sdk/util-endpoints": "3.775.0", + "@smithy/core": "^3.2.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.777.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.777.0.tgz", + "integrity": "sha512-bmmVRsCjuYlStYPt06hr+f8iEyWg7+AklKCA8ZLDEJujXhXIowgUIqXmqpTkXwkVvDQ9tzU7hxaONjyaQCGybA==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.775.0", + "@aws-sdk/middleware-host-header": "3.775.0", + "@aws-sdk/middleware-logger": "3.775.0", + "@aws-sdk/middleware-recursion-detection": "3.775.0", + "@aws-sdk/middleware-user-agent": "3.775.0", + "@aws-sdk/region-config-resolver": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@aws-sdk/util-endpoints": "3.775.0", + "@aws-sdk/util-user-agent-browser": "3.775.0", + "@aws-sdk/util-user-agent-node": "3.775.0", + "@smithy/config-resolver": "^4.1.0", + "@smithy/core": "^3.2.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.0", + "@smithy/middleware-retry": "^4.1.0", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.8", + "@smithy/util-defaults-mode-node": "^4.0.8", + "@smithy/util-endpoints": "^3.0.2", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.775.0.tgz", + "integrity": "sha512-40iH3LJjrQS3LKUJAl7Wj0bln7RFPEvUYKFxtP8a+oKFDO0F65F52xZxIJbPn6sHkxWDAnZlGgdjZXM3p2g5wQ==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.777.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.777.0.tgz", + "integrity": "sha512-Yc2cDONsHOa4dTSGOev6Ng2QgTKQUEjaUnsyKd13pc/nLLz/WLqHiQ/o7PcnKERJxXGs1g1C6l3sNXiX+kbnFQ==", + "dependencies": { + "@aws-sdk/nested-clients": "3.777.0", + "@aws-sdk/types": "3.775.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.775.0.tgz", + "integrity": "sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.775.0.tgz", + "integrity": "sha512-yjWmUgZC9tUxAo8Uaplqmq0eUh0zrbZJdwxGRKdYxfm4RG6fMw1tj52+KkatH7o+mNZvg1GDcVp/INktxonJLw==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/types": "^4.2.0", + "@smithy/util-endpoints": "^3.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.723.0.tgz", + "integrity": "sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.775.0.tgz", + "integrity": "sha512-txw2wkiJmZKVdDbscK7VBK+u+TJnRtlUjRTLei+elZg2ADhpQxfVAQl436FUeIv6AhB/oRHW6/K/EAGXUSWi0A==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/types": "^4.2.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.775.0.tgz", + "integrity": "sha512-N9yhTevbizTOMo3drH7Eoy6OkJ3iVPxhV7dwb6CMAObbLneS36CSfA6xQXupmHWcRvZPTz8rd1JGG3HzFOau+g==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -931,6 +1507,532 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", + "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.0.tgz", + "integrity": "sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==", + "dependencies": { + "@smithy/node-config-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.2.0.tgz", + "integrity": "sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==", + "dependencies": { + "@smithy/middleware-serde": "^4.0.3", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-stream": "^4.2.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.2.tgz", + "integrity": "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==", + "dependencies": { + "@smithy/node-config-provider": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", + "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", + "dependencies": { + "@smithy/protocol-http": "^5.1.0", + "@smithy/querystring-builder": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.2.tgz", + "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==", + "dependencies": { + "@smithy/types": "^4.2.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz", + "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz", + "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==", + "dependencies": { + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.0.tgz", + "integrity": "sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==", + "dependencies": { + "@smithy/core": "^3.2.0", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.0.tgz", + "integrity": "sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==", + "dependencies": { + "@smithy/node-config-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/service-error-classification": "^4.0.2", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.2", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", + "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", + "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.2.tgz", + "integrity": "sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==", + "dependencies": { + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", + "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", + "dependencies": { + "@smithy/abort-controller": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/querystring-builder": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.2.tgz", + "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", + "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", + "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", + "dependencies": { + "@smithy/types": "^4.2.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", + "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz", + "integrity": "sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==", + "dependencies": { + "@smithy/types": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz", + "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.2.tgz", + "integrity": "sha512-Mz+mc7okA73Lyz8zQKJNyr7lIcHLiPYp0+oiqiMNc/t7/Kf2BENs5d63pEj7oPqdjaum6g0Fc8wC78dY1TgtXw==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.0.tgz", + "integrity": "sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==", + "dependencies": { + "@smithy/core": "^3.2.0", + "@smithy/middleware-endpoint": "^4.1.0", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-stream": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", + "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", + "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", + "dependencies": { + "@smithy/querystring-parser": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.8.tgz", + "integrity": "sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.2", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.8.tgz", + "integrity": "sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==", + "dependencies": { + "@smithy/config-resolver": "^4.1.0", + "@smithy/credential-provider-imds": "^4.0.2", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.2.tgz", + "integrity": "sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", + "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.2.tgz", + "integrity": "sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==", + "dependencies": { + "@smithy/service-error-classification": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", + "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/types": "^4.2.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -1054,6 +2156,11 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==" + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -1643,12 +2750,27 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1869,7 +2991,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/constructs": { "version": "10.4.2", @@ -2134,6 +3257,27 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -3296,6 +4440,7 @@ "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" }, @@ -3637,6 +4782,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -3787,6 +4933,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3959,6 +5116,11 @@ } } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -4029,6 +5191,18 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index 3ea7052d..64ed4835 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "aws-cdk-lib": "^2.184.0", "constructs": "^10.3.0", "dotenv": "^16.4.5", - "source-map-support": "^0.5.21" + "source-map-support": "^0.5.21", + "@aws-sdk/client-secrets-manager": "^3.775.0", + "base64url": "^3.0.1" } } diff --git a/website/docs/Blueprints/Bitcoin-Core.md b/website/docs/Blueprints/Bitcoin-Core.md new file mode 100644 index 00000000..2f204f67 --- /dev/null +++ b/website/docs/Blueprints/Bitcoin-Core.md @@ -0,0 +1,8 @@ +--- +sidebar_label: Bitcoin Core +--- +# + +import Readme from '../../../lib/bitcoin-core/README.md'; + + diff --git a/website/docs/Blueprints/Fantom.md b/website/docs/Blueprints/Fantom.md deleted file mode 100644 index 3421e383..00000000 --- a/website/docs/Blueprints/Fantom.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -sidebar_label: Fantom ---- -# - -import Readme from '../../../lib/fantom/README.md'; - - diff --git a/website/package-lock.json b/website/package-lock.json index 63a2c135..ab8cc451 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -71,92 +71,92 @@ } }, "node_modules/@algolia/client-abtesting": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.22.0.tgz", - "integrity": "sha512-N0OyDDFrjIUArcQCblNtpbgha2vCfC8YSW3eKL008VeJjfhrfRflrEPABLLkCJeaWVt5m6OG5Kc/RzHRIYzabQ==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.24.0.tgz", + "integrity": "sha512-pNTIB5YqVVwu6UogvdX8TqsRZENaflqMMjdY7/XIPMNGrBoNH9tewINLI7+qc9tIaOLcAp3ZldqoEwAihZZ3ig==", "dependencies": { - "@algolia/client-common": "5.22.0", - "@algolia/requester-browser-xhr": "5.22.0", - "@algolia/requester-fetch": "5.22.0", - "@algolia/requester-node-http": "5.22.0" + "@algolia/client-common": "5.24.0", + "@algolia/requester-browser-xhr": "5.24.0", + "@algolia/requester-fetch": "5.24.0", + "@algolia/requester-node-http": "5.24.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.22.0.tgz", - "integrity": "sha512-nl12TZv8p7SkgnVW3Vi3WKCa2xMrlqykVN2gnxkbk64VkW3/TNAHDYJp4GHDkzudr/tqKMP3PV2VvIcnp6RxQA==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.24.0.tgz", + "integrity": "sha512-IF+r9RRQsIf0ylIBNFxo7c6hDxxuhIfIbffhBXEF1HD13rjhP5AVfiaea9RzbsAZoySkm318plDpH/nlGIjbRA==", "dependencies": { - "@algolia/client-common": "5.22.0", - "@algolia/requester-browser-xhr": "5.22.0", - "@algolia/requester-fetch": "5.22.0", - "@algolia/requester-node-http": "5.22.0" + "@algolia/client-common": "5.24.0", + "@algolia/requester-browser-xhr": "5.24.0", + "@algolia/requester-fetch": "5.24.0", + "@algolia/requester-node-http": "5.24.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.22.0.tgz", - "integrity": "sha512-N9OhHB4Z00Vxy4Nz02L1WtQcq/r6DZFCPLLzlXDsGxm0v5k82MdrRxWs+LBGoQ3yb1RdGN33xdDxAG+BcPmw8A==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.24.0.tgz", + "integrity": "sha512-p8K6tiXQTebRBxbrzWIfGCvfkT+Umml+2lzI92acZjHsvl6KYH6igOfVstKqXJRei9pvRzEEvVDNDLXDVleGTA==", "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-insights": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.22.0.tgz", - "integrity": "sha512-Qf8y7SfBlzrIqS1GGLzsjhuJQ0Tt3m8rGummJxOH9/ZMMwWTHBfI9L12PQkklbgVKSSYgxXye/IPabaxd1R5Mw==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.24.0.tgz", + "integrity": "sha512-jOHF0+tixR3IZJMhZPquFNdCVPzwzzXoiqVsbTvfKojeaY6ZXybgUiTSB8JNX+YpsUT8Ebhu3UvRy4mw2PbEzw==", "dependencies": { - "@algolia/client-common": "5.22.0", - "@algolia/requester-browser-xhr": "5.22.0", - "@algolia/requester-fetch": "5.22.0", - "@algolia/requester-node-http": "5.22.0" + "@algolia/client-common": "5.24.0", + "@algolia/requester-browser-xhr": "5.24.0", + "@algolia/requester-fetch": "5.24.0", + "@algolia/requester-node-http": "5.24.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.22.0.tgz", - "integrity": "sha512-4NqaOn6ls6sYURdO7dHg7uqVyCEtIPOaN5Fr1YHJvWrTvQACCJxajLfyHVyx6E/IvOcPvw62rZTRJjXXEZWGYA==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.24.0.tgz", + "integrity": "sha512-Fx/Fp6d8UmDBHecTt0XYF8C9TAaA3qeCQortfGSZzWp4gVmtrUCFNZ1SUwb8ULREnO9DanVrM5hGE8R8C4zZTQ==", "dependencies": { - "@algolia/client-common": "5.22.0", - "@algolia/requester-browser-xhr": "5.22.0", - "@algolia/requester-fetch": "5.22.0", - "@algolia/requester-node-http": "5.22.0" + "@algolia/client-common": "5.24.0", + "@algolia/requester-browser-xhr": "5.24.0", + "@algolia/requester-fetch": "5.24.0", + "@algolia/requester-node-http": "5.24.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.22.0.tgz", - "integrity": "sha512-zZvWhOB772Ih2KQAGVy/hIxgBOnBuKpI9Xtmi4RYcXgLGSvmrK1gxVRj8HJpbNkuPEgBBZC79ngn/HuEiaHU8A==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.24.0.tgz", + "integrity": "sha512-F8ypOedSMhz6W7zuT5O1SXXsdXSOVhY2U6GkRbYk/mzrhs3jWFR3uQIfeQVWmsJjUwIGZmPoAr9E+T/Zm2M4wA==", "dependencies": { - "@algolia/client-common": "5.22.0", - "@algolia/requester-browser-xhr": "5.22.0", - "@algolia/requester-fetch": "5.22.0", - "@algolia/requester-node-http": "5.22.0" + "@algolia/client-common": "5.24.0", + "@algolia/requester-browser-xhr": "5.24.0", + "@algolia/requester-fetch": "5.24.0", + "@algolia/requester-node-http": "5.24.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.22.0.tgz", - "integrity": "sha512-hAJ3NlK/k7XzToy+GRjtd+nrkCyO0yg9oxgEynhQ1mBtAjm09qsRKavyEgTzgF8YaeSU1gSeHe8MBkwHtvco0w==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.24.0.tgz", + "integrity": "sha512-k+nuciQuq7WERNNE+hsx3DX636zIy+9R4xdtvW3PANT2a2BDGOv3fv2mta8+QUMcVTVcGe/Mo3QCb4pc1HNoxA==", "dependencies": { - "@algolia/client-common": "5.22.0", - "@algolia/requester-browser-xhr": "5.22.0", - "@algolia/requester-fetch": "5.22.0", - "@algolia/requester-node-http": "5.22.0" + "@algolia/client-common": "5.24.0", + "@algolia/requester-browser-xhr": "5.24.0", + "@algolia/requester-fetch": "5.24.0", + "@algolia/requester-node-http": "5.24.0" }, "engines": { "node": ">= 14.0.0" @@ -168,75 +168,75 @@ "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" }, "node_modules/@algolia/ingestion": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.22.0.tgz", - "integrity": "sha512-pEnF5jOdR4dfD/kbrilSy7G8q+Z/ZxW2vRmqxb1Zo/Zsiz1vNRNl1XIm9KiRUDb3Q2Q/awXw7VTsZHsumygv7g==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.24.0.tgz", + "integrity": "sha512-/lqVxmrvwoA+OyVK4XLMdz/PJaCTW4qYchX1AZ+98fdnH3K6XM/kMydQLfP0bUNGBQbmVrF88MqhqZRnZEn/MA==", "dependencies": { - "@algolia/client-common": "5.22.0", - "@algolia/requester-browser-xhr": "5.22.0", - "@algolia/requester-fetch": "5.22.0", - "@algolia/requester-node-http": "5.22.0" + "@algolia/client-common": "5.24.0", + "@algolia/requester-browser-xhr": "5.24.0", + "@algolia/requester-fetch": "5.24.0", + "@algolia/requester-node-http": "5.24.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.22.0.tgz", - "integrity": "sha512-K11+UYGLqS4HY7Zn3cz3CGwKjrPLtFUsTm9clcUNkryATOsOJcnMiQhfSYcputUCem20C8M8wzPdKI7asEXOrg==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.24.0.tgz", + "integrity": "sha512-cRisDXQJhvfZCXL4hD22qca2CmW52TniOx6L7pvkaBDx0oQk1k9o+3w11fgfcCG+47OndMeNx5CMpu+K+COMzg==", "dependencies": { - "@algolia/client-common": "5.22.0", - "@algolia/requester-browser-xhr": "5.22.0", - "@algolia/requester-fetch": "5.22.0", - "@algolia/requester-node-http": "5.22.0" + "@algolia/client-common": "5.24.0", + "@algolia/requester-browser-xhr": "5.24.0", + "@algolia/requester-fetch": "5.24.0", + "@algolia/requester-node-http": "5.24.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.22.0.tgz", - "integrity": "sha512-awxmTF/BbQPCNKq9W8OAFSSIGbd8WR2IwYBI7v9yA/gIWP/SGqWVpzbNs1ISWbP9dFZ9srrYqvAyKdKI2Yak2w==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.24.0.tgz", + "integrity": "sha512-JTMz0JqN2gidvKa2QCF/rMe8LNtdHaght03px2cluZaZfBRYy8TgHgkCeBspKKvV/abWJwl7J0FzWThCshqT3w==", "dependencies": { - "@algolia/client-common": "5.22.0", - "@algolia/requester-browser-xhr": "5.22.0", - "@algolia/requester-fetch": "5.22.0", - "@algolia/requester-node-http": "5.22.0" + "@algolia/client-common": "5.24.0", + "@algolia/requester-browser-xhr": "5.24.0", + "@algolia/requester-fetch": "5.24.0", + "@algolia/requester-node-http": "5.24.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.22.0.tgz", - "integrity": "sha512-TTdgsycBWAM1yFZu+L6CSk0Yw+rHDVx8HnsVZ2uonRE/83aHfoFKJft26PTYUTPoU6wkMcSgZH+xCQPOmJd3VQ==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.24.0.tgz", + "integrity": "sha512-B2Gc+iSxct1WSza5CF6AgfNgmLvVb61d5bqmIWUZixtJIhyAC6lSQZuF+nvt+lmKhQwuY2gYjGGClil8onQvKQ==", "dependencies": { - "@algolia/client-common": "5.22.0" + "@algolia/client-common": "5.24.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.22.0.tgz", - "integrity": "sha512-4gnonkuUoBvJN4GMiltD7AOCe26iA+rccXuO21AGSNoir+2sK68E9QRZy2Zj4VM8mRioLAUa0rtLJAtD7sRGIQ==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.24.0.tgz", + "integrity": "sha512-6E5+hliqGc5w8ZbyTAQ+C3IGLZ/GiX623Jl2bgHA974RPyFWzVSj4rKqkboUAxQmrFY7Z02ybJWVZS5OhPQocA==", "dependencies": { - "@algolia/client-common": "5.22.0" + "@algolia/client-common": "5.24.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.22.0.tgz", - "integrity": "sha512-38QI2DkUaPj5gAq+lY4llWgWTq+5X6KZZIcn8tnMueFnPiPLe5K8YKRp2LxdFNNJIP3PPScTkD5GCLh7d37SwQ==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.24.0.tgz", + "integrity": "sha512-zM+nnqZpiQj20PyAh6uvgdSz+hD7Rj7UfAZwizqNP+bLvcbGXZwABERobuilkCQqyDBBH4uv0yqIcPRl8dSBEg==", "dependencies": { - "@algolia/client-common": "5.22.0" + "@algolia/client-common": "5.24.0" }, "engines": { "node": ">= 14.0.0" @@ -255,41 +255,41 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.1.tgz", + "integrity": "sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", + "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helpers": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -313,12 +313,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", "dependencies": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -328,23 +328,23 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", + "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", - "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.1.tgz", + "integrity": "sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==", "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -362,16 +362,16 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", - "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.27.0", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", "semver": "^6.3.1" }, "engines": { @@ -390,11 +390,11 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.0.tgz", - "integrity": "sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-annotate-as-pure": "^7.27.1", "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, @@ -429,37 +429,37 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -469,32 +469,32 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -504,13 +504,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -520,72 +520,72 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", "dependencies": { - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz", + "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==", "dependencies": { - "@babel/types": "^7.27.0" + "@babel/types": "^7.27.1" }, "bin": { "parser": "bin/babel-parser.js" @@ -595,12 +595,12 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -610,11 +610,11 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -624,11 +624,11 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -638,13 +638,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -654,12 +654,12 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", + "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -691,11 +691,11 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -705,11 +705,11 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -719,11 +719,11 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -733,11 +733,11 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -762,11 +762,11 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -776,13 +776,13 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", - "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", + "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.26.8" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -792,13 +792,13 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -808,11 +808,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", - "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -822,11 +822,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz", - "integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.1.tgz", + "integrity": "sha512-QEcFlMl9nGTgh1rn2nIeU5bkfb9BAjaQcWbiP4LvKxUot52ABcTkpcyJ7f2Q2U2RuQ84BNLgts3jRme2dTx6Fw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -836,12 +836,12 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -851,12 +851,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", + "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -866,15 +866,15 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", + "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.27.1", "globals": "^11.1.0" }, "engines": { @@ -885,12 +885,12 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -900,11 +900,11 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.1.tgz", + "integrity": "sha512-ttDCqhfvpE9emVkXbPD8vyxxh4TWYACVybGkDj+oReOGwnp066ITEivDlLwe0b1R0+evJ13IXQuLNB5w1fhC5Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -914,12 +914,12 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -929,11 +929,11 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -943,12 +943,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -958,11 +958,11 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -972,11 +972,11 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", - "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -986,11 +986,11 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1000,12 +1000,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", - "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1015,13 +1015,13 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1031,11 +1031,11 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1045,11 +1045,11 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1059,11 +1059,11 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1073,11 +1073,11 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1087,12 +1087,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1102,12 +1102,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", "dependencies": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1117,14 +1117,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1134,12 +1134,12 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1149,12 +1149,12 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1164,11 +1164,11 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1178,11 +1178,11 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.26.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", - "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1192,11 +1192,11 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1206,13 +1206,13 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.1.tgz", + "integrity": "sha512-/sSliVc9gHE20/7D5qsdGlq7RG5NCDTWsAhyqzGuq174EtWJoGzIu1BQ7G56eDsTcy1jseBZwv50olSdXOlGuA==", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1222,12 +1222,12 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1237,11 +1237,11 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1251,12 +1251,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1266,11 +1266,11 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", + "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1280,12 +1280,12 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1295,13 +1295,13 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1311,11 +1311,11 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1325,11 +1325,11 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz", - "integrity": "sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", + "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1339,11 +1339,11 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", - "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.27.1.tgz", + "integrity": "sha512-p9+Vl3yuHPmkirRrg021XiP+EETmPMQTLr6Ayjj85RLNEbb3Eya/4VI0vAdzQG9SEAl2Lnt7fy5lZyMzjYoZQQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1353,15 +1353,15 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", - "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1371,11 +1371,11 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", - "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.25.9" + "@babel/plugin-transform-react-jsx": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1385,12 +1385,12 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", - "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1400,12 +1400,11 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.0.tgz", - "integrity": "sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.1.tgz", + "integrity": "sha512-B19lbbL7PMrKr52BNPjCqg1IyNUIjTcxKj8uX9zHO+PmWN93s19NDr/f69mIkEp2x9nmDJ08a7lgHaTTzvW7mw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "regenerator-transform": "^0.15.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1415,12 +1414,12 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", - "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1430,11 +1429,11 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1444,12 +1443,12 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", - "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.1.tgz", + "integrity": "sha512-TqGF3desVsTcp3WrJGj4HfKokfCXCLcHpt4PJF0D8/iT6LPd9RS82Upw3KPeyr6B22Lfd3DO8MVrmp0oRkUDdw==", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", @@ -1471,11 +1470,11 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1485,12 +1484,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1500,11 +1499,11 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1514,11 +1513,11 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", - "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1528,11 +1527,11 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.0.tgz", - "integrity": "sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1542,15 +1541,15 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", - "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", + "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.27.0", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-syntax-typescript": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1560,11 +1559,11 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1574,12 +1573,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1589,12 +1588,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1604,12 +1603,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1619,73 +1618,73 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", - "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", - "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.1.tgz", + "integrity": "sha512-TZ5USxFpLgKDpdEt8YWBR7p6g+bZo6sHaXLqP2BY/U0acaoI8FTVflcYCr/v94twM1C5IWFdZ/hscq9WjUeLXA==", + "dependencies": { + "@babel/compat-data": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.25.9", - "@babel/plugin-transform-async-generator-functions": "^7.26.8", - "@babel/plugin-transform-async-to-generator": "^7.25.9", - "@babel/plugin-transform-block-scoped-functions": "^7.26.5", - "@babel/plugin-transform-block-scoping": "^7.25.9", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.26.0", - "@babel/plugin-transform-classes": "^7.25.9", - "@babel/plugin-transform-computed-properties": "^7.25.9", - "@babel/plugin-transform-destructuring": "^7.25.9", - "@babel/plugin-transform-dotall-regex": "^7.25.9", - "@babel/plugin-transform-duplicate-keys": "^7.25.9", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-dynamic-import": "^7.25.9", - "@babel/plugin-transform-exponentiation-operator": "^7.26.3", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-for-of": "^7.26.9", - "@babel/plugin-transform-function-name": "^7.25.9", - "@babel/plugin-transform-json-strings": "^7.25.9", - "@babel/plugin-transform-literals": "^7.25.9", - "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", - "@babel/plugin-transform-member-expression-literals": "^7.25.9", - "@babel/plugin-transform-modules-amd": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-modules-systemjs": "^7.25.9", - "@babel/plugin-transform-modules-umd": "^7.25.9", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-new-target": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", - "@babel/plugin-transform-numeric-separator": "^7.25.9", - "@babel/plugin-transform-object-rest-spread": "^7.25.9", - "@babel/plugin-transform-object-super": "^7.25.9", - "@babel/plugin-transform-optional-catch-binding": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9", - "@babel/plugin-transform-private-methods": "^7.25.9", - "@babel/plugin-transform-private-property-in-object": "^7.25.9", - "@babel/plugin-transform-property-literals": "^7.25.9", - "@babel/plugin-transform-regenerator": "^7.25.9", - "@babel/plugin-transform-regexp-modifiers": "^7.26.0", - "@babel/plugin-transform-reserved-words": "^7.25.9", - "@babel/plugin-transform-shorthand-properties": "^7.25.9", - "@babel/plugin-transform-spread": "^7.25.9", - "@babel/plugin-transform-sticky-regex": "^7.25.9", - "@babel/plugin-transform-template-literals": "^7.26.8", - "@babel/plugin-transform-typeof-symbol": "^7.26.7", - "@babel/plugin-transform-unicode-escapes": "^7.25.9", - "@babel/plugin-transform-unicode-property-regex": "^7.25.9", - "@babel/plugin-transform-unicode-regex": "^7.25.9", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.27.1", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.27.1", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.27.1", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.27.1", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.11.0", @@ -1722,16 +1721,16 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", - "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-transform-react-display-name": "^7.25.9", - "@babel/plugin-transform-react-jsx": "^7.25.9", - "@babel/plugin-transform-react-jsx-development": "^7.25.9", - "@babel/plugin-transform-react-pure-annotations": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1741,15 +1740,15 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", - "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-typescript": "^7.27.0" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1759,51 +1758,47 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.0.tgz", - "integrity": "sha512-UWjX6t+v+0ckwZ50Y5ShZLnlk95pP5MyW/pon9tiYzl3+18pkTHTFNTKr7rQbfRXPkowt2QAn30o1b6oswszew==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.1.tgz", + "integrity": "sha512-909rVuj3phpjW6y0MCXAZ5iNeORePa6ldJvp2baWGcTjwqbBDDz6xoS5JHJ7lS88NlwLYj07ImL/8IUMtDZzTA==", "dependencies": { - "core-js-pure": "^3.30.2", - "regenerator-runtime": "^0.14.0" + "core-js-pure": "^3.30.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz", + "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1812,12 +1807,12 @@ } }, "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1873,9 +1868,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.2.tgz", - "integrity": "sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz", + "integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==", "funding": [ { "type": "github", @@ -1895,9 +1890,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.8.tgz", - "integrity": "sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz", + "integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==", "funding": [ { "type": "github", @@ -1910,7 +1905,7 @@ ], "dependencies": { "@csstools/color-helpers": "^5.0.2", - "@csstools/css-calc": "^2.1.2" + "@csstools/css-calc": "^2.1.3" }, "engines": { "node": ">=18" @@ -2040,9 +2035,9 @@ } }, "node_modules/@csstools/postcss-color-function": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.8.tgz", - "integrity": "sha512-9dUvP2qpZI6PlGQ/sob+95B3u5u7nkYt9yhZFCC7G9HBRHBxj+QxS/wUlwaMGYW0waf+NIierI8aoDTssEdRYw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.9.tgz", + "integrity": "sha512-2UeQCGMO5+EeQsPQK2DqXp0dad+P6nIz6G2dI06APpBuYBKxZEq7CTH+UiztFQ8cB1f89dnO9+D/Kfr+JfI2hw==", "funding": [ { "type": "github", @@ -2054,10 +2049,10 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2068,9 +2063,9 @@ } }, "node_modules/@csstools/postcss-color-mix-function": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.8.tgz", - "integrity": "sha512-yuZpgWUzqZWQhEqfvtJufhl28DgO9sBwSbXbf/59gejNuvZcoUTRGQZhzhwF4ccqb53YAGB+u92z9+eSKoB4YA==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.9.tgz", + "integrity": "sha512-Enj7ZIIkLD7zkGCN31SZFx4H1gKiCs2Y4taBo/v/cqaHN7p1qGrf5UTMNSjQFZ7MgClGufHx4pddwFTGL+ipug==", "funding": [ { "type": "github", @@ -2082,10 +2077,10 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2096,9 +2091,9 @@ } }, "node_modules/@csstools/postcss-content-alt-text": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.4.tgz", - "integrity": "sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.5.tgz", + "integrity": "sha512-9BOS535v6YmyOYk32jAHXeddRV+iyd4vRcbrEekpwxmueAXX5J8WgbceFnE4E4Pmw/ysnB9v+n/vSWoFmcLMcA==", "funding": [ { "type": "github", @@ -2112,7 +2107,7 @@ "dependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2123,9 +2118,9 @@ } }, "node_modules/@csstools/postcss-exponential-functions": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.7.tgz", - "integrity": "sha512-XTb6Mw0v2qXtQYRW9d9duAjDnoTbBpsngD7sRNLmYDjvwU2ebpIHplyxgOeo6jp/Kr52gkLi5VaK5RDCqzMzZQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.8.tgz", + "integrity": "sha512-vHgDXtGIBPpFQnFNDftMQg4MOuXcWnK91L/7REjBNYzQ/p2Fa/6RcnehTqCRrNtQ46PNIolbRsiDdDuxiHolwQ==", "funding": [ { "type": "github", @@ -2137,7 +2132,7 @@ } ], "dependencies": { - "@csstools/css-calc": "^2.1.2", + "@csstools/css-calc": "^2.1.3", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" }, @@ -2174,9 +2169,9 @@ } }, "node_modules/@csstools/postcss-gamut-mapping": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.8.tgz", - "integrity": "sha512-/K8u9ZyGMGPjmwCSIjgaOLKfic2RIGdFHHes84XW5LnmrvdhOTVxo255NppHi3ROEvoHPW7MplMJgjZK5Q+TxA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.9.tgz", + "integrity": "sha512-quksIsFm3DGsf8Qbr9KiSGBF2w3RwxSfOfma5wbORDB1AFF15r4EVW7sUuWw3s5IAEGMqzel/dE2rQsI7Yb8mA==", "funding": [ { "type": "github", @@ -2188,7 +2183,7 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" }, @@ -2200,9 +2195,9 @@ } }, "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.8.tgz", - "integrity": "sha512-CoHQ/0UXrvxLovu0ZeW6c3/20hjJ/QRg6lyXm3dZLY/JgvRU6bdbQZF/Du30A4TvowfcgvIHQmP1bNXUxgDrAw==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.9.tgz", + "integrity": "sha512-duqTeUHF4ambUybAmhX9KonkicLM/WNp2JjMUbegRD4O8A/tb6fdZ7jUNdp/UUiO1FIdDkMwmNw6856bT0XF8Q==", "funding": [ { "type": "github", @@ -2214,10 +2209,10 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2228,9 +2223,9 @@ } }, "node_modules/@csstools/postcss-hwb-function": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.8.tgz", - "integrity": "sha512-LpFKjX6hblpeqyych1cKmk+3FJZ19QmaJtqincySoMkbkG/w2tfbnO5oE6mlnCTXcGUJ0rCEuRHvTqKK0nHYUQ==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.9.tgz", + "integrity": "sha512-sDpdPsoGAhYl/PMSYfu5Ez82wXb2bVkg1Cb8vsRLhpXhAk4OSlsJN+GodAql6tqc1B2G/WToxsFU6G74vkhPvA==", "funding": [ { "type": "github", @@ -2242,10 +2237,10 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2256,9 +2251,9 @@ } }, "node_modules/@csstools/postcss-ic-unit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.0.tgz", - "integrity": "sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.1.tgz", + "integrity": "sha512-lECc38i1w3qU9nhrUhP6F8y4BfcQJkR1cb8N6tZNf2llM6zPkxnqt04jRCwsUgNcB3UGKDy+zLenhOYGHqCV+Q==", "funding": [ { "type": "github", @@ -2270,7 +2265,7 @@ } ], "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, @@ -2361,9 +2356,9 @@ } }, "node_modules/@csstools/postcss-light-dark-function": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.7.tgz", - "integrity": "sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.8.tgz", + "integrity": "sha512-v8VU5WtrZIyEtk88WB4fkG22TGd8HyAfSFfZZQ1uNN0+arMJdZc++H3KYTfbYDpJRGy8GwADYH8ySXiILn+OyA==", "funding": [ { "type": "github", @@ -2377,7 +2372,7 @@ "dependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2500,9 +2495,9 @@ } }, "node_modules/@csstools/postcss-media-minmax": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.7.tgz", - "integrity": "sha512-LB6tIP7iBZb5CYv8iRenfBZmbaG3DWNEziOnPjGoQX5P94FBPvvTBy68b/d9NnS5PELKwFmmOYsAEIgEhDPCHA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.8.tgz", + "integrity": "sha512-Skum5wIXw2+NyCQWUyfstN3c1mfSh39DRAo+Uh2zzXOglBG8xB9hnArhYFScuMZkzeM+THVa//mrByKAfumc7w==", "funding": [ { "type": "github", @@ -2514,7 +2509,7 @@ } ], "dependencies": { - "@csstools/css-calc": "^2.1.2", + "@csstools/css-calc": "^2.1.3", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "@csstools/media-query-list-parser": "^4.0.2" @@ -2602,9 +2597,9 @@ } }, "node_modules/@csstools/postcss-oklab-function": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.8.tgz", - "integrity": "sha512-+5aPsNWgxohXoYNS1f+Ys0x3Qnfehgygv3qrPyv+Y25G0yX54/WlVB+IXprqBLOXHM1gsVF+QQSjlArhygna0Q==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.9.tgz", + "integrity": "sha512-UHrnujimwtdDw8BYDcWJtBXuJ13uc/BjAddPdfMc/RsWxhg8gG8UbvTF0tnMtHrZ4i7lwy85fPEzK1AiykMyRA==", "funding": [ { "type": "github", @@ -2616,10 +2611,10 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2630,9 +2625,9 @@ } }, "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.0.tgz", - "integrity": "sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.1.tgz", + "integrity": "sha512-Ofz81HaY8mmbP8/Qr3PZlUzjsyV5WuxWmvtYn+jhYGvvjFazTmN9R2io5W5znY1tyk2CA9uM0IPWyY4ygDytCw==", "funding": [ { "type": "github", @@ -2654,9 +2649,9 @@ } }, "node_modules/@csstools/postcss-random-function": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-1.0.3.tgz", - "integrity": "sha512-dbNeEEPHxAwfQJ3duRL5IPpuD77QAHtRl4bAHRs0vOVhVbHrsL7mHnwe0irYjbs9kYwhAHZBQTLBgmvufPuRkA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.0.tgz", + "integrity": "sha512-MYZKxSr4AKfjECL8vg49BbfNNzK+t3p2OWX+Xf7rXgMaTP44oy/e8VGWu4MLnJ3NUd9tFVkisLO/sg+5wMTNsg==", "funding": [ { "type": "github", @@ -2668,7 +2663,7 @@ } ], "dependencies": { - "@csstools/css-calc": "^2.1.2", + "@csstools/css-calc": "^2.1.3", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" }, @@ -2680,9 +2675,9 @@ } }, "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.8.tgz", - "integrity": "sha512-eGE31oLnJDoUysDdjS9MLxNZdtqqSxjDXMdISpLh80QMaYrKs7VINpid34tWQ+iU23Wg5x76qAzf1Q/SLLbZVg==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.9.tgz", + "integrity": "sha512-+AGOcLF5PmMnTRPnOdCvY7AwvD5veIOhTWbJV6vC3hB1tt0ii/k6QOwhWfsGGg1ZPQ0JY15u+wqLR4ZTtB0luA==", "funding": [ { "type": "github", @@ -2694,10 +2689,10 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2744,9 +2739,9 @@ } }, "node_modules/@csstools/postcss-sign-functions": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.2.tgz", - "integrity": "sha512-4EcAvXTUPh7n6UoZZkCzgtCf/wPzMlTNuddcKg7HG8ozfQkUcHsJ2faQKeLmjyKdYPyOUn4YA7yDPf8K/jfIxw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.3.tgz", + "integrity": "sha512-4F4GRhj8xNkBtLZ+3ycIhReaDfKJByXI+cQGIps3AzCO8/CJOeoDPxpMnL5vqZrWKOceSATHEQJUO/Q/r2y7OQ==", "funding": [ { "type": "github", @@ -2758,7 +2753,7 @@ } ], "dependencies": { - "@csstools/css-calc": "^2.1.2", + "@csstools/css-calc": "^2.1.3", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" }, @@ -2770,9 +2765,9 @@ } }, "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.7.tgz", - "integrity": "sha512-rdrRCKRnWtj5FyRin0u/gLla7CIvZRw/zMGI1fVJP0Sg/m1WGicjPVHRANL++3HQtsiXKAbPrcPr+VkyGck0IA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.8.tgz", + "integrity": "sha512-6Y4yhL4fNhgzbZ/wUMQ4EjFUfoNNMpEXZnDw1JrlcEBHUT15gplchtFsZGk7FNi8PhLHJfCUwVKrEHzhfhKK+g==", "funding": [ { "type": "github", @@ -2784,7 +2779,7 @@ } ], "dependencies": { - "@csstools/css-calc": "^2.1.2", + "@csstools/css-calc": "^2.1.3", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" }, @@ -2821,9 +2816,9 @@ } }, "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.7.tgz", - "integrity": "sha512-qTrZgLju3AV7Djhzuh2Bq/wjFqbcypnk0FhHjxW8DWJQcZLS1HecIus4X2/RLch1ukX7b+YYCdqbEnpIQO5ccg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.8.tgz", + "integrity": "sha512-YcDvYTRu7f78/91B6bX+mE1WoAO91Su7/8KSRpuWbIGUB8hmaNSRu9wziaWSLJ1lOB1aQe+bvo9BIaLKqPOo/g==", "funding": [ { "type": "github", @@ -2835,7 +2830,7 @@ } ], "dependencies": { - "@csstools/css-calc": "^2.1.2", + "@csstools/css-calc": "^2.1.3", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" }, @@ -3813,9 +3808,9 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.28", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", - "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==" + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==" }, "node_modules/@sideway/address": { "version": "4.1.5", @@ -4320,11 +4315,11 @@ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" }, "node_modules/@types/node": { - "version": "22.13.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz", - "integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==", + "version": "22.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/node-forge": { @@ -4356,9 +4351,9 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { - "version": "19.0.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.12.tgz", - "integrity": "sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==", + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", "dependencies": { "csstype": "^3.0.2" } @@ -4446,9 +4441,9 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, "node_modules/@types/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dependencies": { "@types/node": "*" } @@ -4744,32 +4739,32 @@ } }, "node_modules/algoliasearch": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.22.0.tgz", - "integrity": "sha512-Epyi6P2TePHzNdUxtLlaI19x4/r4QXocoL2zyidDtTTSN7CY8cmYKQ0pTn0YfFJePtwX7oNjnQiiEPMBPI4QuQ==", - "dependencies": { - "@algolia/client-abtesting": "5.22.0", - "@algolia/client-analytics": "5.22.0", - "@algolia/client-common": "5.22.0", - "@algolia/client-insights": "5.22.0", - "@algolia/client-personalization": "5.22.0", - "@algolia/client-query-suggestions": "5.22.0", - "@algolia/client-search": "5.22.0", - "@algolia/ingestion": "1.22.0", - "@algolia/monitoring": "1.22.0", - "@algolia/recommend": "5.22.0", - "@algolia/requester-browser-xhr": "5.22.0", - "@algolia/requester-fetch": "5.22.0", - "@algolia/requester-node-http": "5.22.0" + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.24.0.tgz", + "integrity": "sha512-CkaUygzZ91Xbw11s0CsHMawrK3tl+Ue57725HGRgRzKgt2Z4wvXVXRCtQfvzh8K7Tp4Zp7f1pyHAtMROtTJHxg==", + "dependencies": { + "@algolia/client-abtesting": "5.24.0", + "@algolia/client-analytics": "5.24.0", + "@algolia/client-common": "5.24.0", + "@algolia/client-insights": "5.24.0", + "@algolia/client-personalization": "5.24.0", + "@algolia/client-query-suggestions": "5.24.0", + "@algolia/client-search": "5.24.0", + "@algolia/ingestion": "1.24.0", + "@algolia/monitoring": "1.24.0", + "@algolia/recommend": "5.24.0", + "@algolia/requester-browser-xhr": "5.24.0", + "@algolia/requester-fetch": "5.24.0", + "@algolia/requester-node-http": "5.24.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.24.3", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.24.3.tgz", - "integrity": "sha512-3QKg5lzSfUiPN8Hn1ViHEGv6PjK7i4SFEDLzwlSzPO/4mVOsyos7B7/AsEtFQW5KHHPiCq6DyJl+mzg7CYlEgw==", + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.25.0.tgz", + "integrity": "sha512-vQoK43U6HXA9/euCqLjvyNdM4G2Fiu/VFp4ae0Gau9sZeIKBPvUPnXfLYAe65Bg7PFuw03coeu5K6lTPSXRObw==", "dependencies": { "@algolia/events": "^4.0.1" }, @@ -5154,9 +5149,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "funding": [ { "type": "opencollective", @@ -5172,10 +5167,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -5306,9 +5301,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001707", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz", - "integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==", + "version": "1.0.30001717", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", + "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==", "funding": [ { "type": "opencollective", @@ -5843,9 +5838,9 @@ } }, "node_modules/core-js": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.41.0.tgz", - "integrity": "sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==", + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", + "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -5853,9 +5848,9 @@ } }, "node_modules/core-js-compat": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", - "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz", + "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==", "dependencies": { "browserslist": "^4.24.4" }, @@ -5865,9 +5860,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.41.0.tgz", - "integrity": "sha512-71Gzp96T9YPk63aUvE5Q5qP+DryB4ZloUZPSOebGM88VNw8VNfvdA7z6kGA8iGOTEzAomsRidp4jXSmUIJsL+Q==", + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.42.0.tgz", + "integrity": "sha512-007bM04u91fF4kMgwom2I5cQxAFIy8jVulgr9eozILl/SZE53QOqnW/+vviC+wQWLv+AunBG+8Q0TLoeSsSxRQ==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -6185,9 +6180,9 @@ } }, "node_modules/cssdb": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.2.4.tgz", - "integrity": "sha512-3KSCVkjZJe/QxicVXnbyYSY26WsFc1YoMY7jep1ZKWMEVc7jEm6V2Xq2r+MX8WKQIuB7ofGbnr5iVI+aZpoSzg==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.2.5.tgz", + "integrity": "sha512-leAt8/hdTCtzql9ZZi86uYAmCLzVKpJMMdjbvOGVnXFXz/BWFpBmM1MHEHU/RqtPyRYmabVmEW1DtX3YGLuuLA==", "funding": [ { "type": "opencollective", @@ -6720,9 +6715,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.5.123", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.123.tgz", - "integrity": "sha512-refir3NlutEZqlKaBLK0tzlVLe5P2wDKS7UQt/3SpibizgsRAPOsqQC3ffw1nlv3ze5gjRQZYHoPymgVZkplFA==" + "version": "1.5.149", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.149.tgz", + "integrity": "sha512-UyiO82eb9dVOx8YO3ajDf9jz2kKyt98DEITRdeLPstOEuTlLzDA4Gyq5K9he71TQziU5jUVu2OAu5N48HmQiyQ==" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -6807,9 +6802,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -7002,9 +6997,9 @@ } }, "node_modules/estree-util-value-to-estree": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.3.2.tgz", - "integrity": "sha512-hYH1aSvQI63Cvq3T3loaem6LW4u72F187zW4FHpTrReJSm6W66vYTFNO1vH/chmcOulp1HlAj1pxn8Ag0oXI5Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.3.3.tgz", + "integrity": "sha512-Db+m1WSD4+mUO7UgMeKkAwdbfNWwIxLt48XF2oFU9emPfXkIu+k5/nlOj313v7wqtAPo0f9REhUvznFrPkG8CQ==", "dependencies": { "@types/estree": "^1.0.0" }, @@ -8263,9 +8258,9 @@ } }, "node_modules/html-entities": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.3.tgz", - "integrity": "sha512-D3AfvN7SjhTgBSA8L1BN4FpPzuEd06uy4lHwSoRWr0lndi9BKaNzPLKGOWZ2ocSGguozr08TTb2jhCLHaemruw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", "funding": [ { "type": "github", @@ -8433,9 +8428,9 @@ } }, "node_modules/http-parser-js": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", - "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==" + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==" }, "node_modules/http-proxy": { "version": "1.18.1", @@ -8451,9 +8446,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", - "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -8535,9 +8530,9 @@ } }, "node_modules/image-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.0.tgz", - "integrity": "sha512-4S8fwbO6w3GeCVN6OPtA9I5IGKkcDMPcKndtUlpJuCwu7JLjtj7JZpwqLuyY2nrmQT3AWsCJLSKPsc2mPBSl3w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", "dependencies": { "queue": "6.0.2" }, @@ -11970,11 +11965,11 @@ "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" }, "node_modules/parse5": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "dependencies": { - "entities": "^4.5.0" + "entities": "^6.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -11992,6 +11987,17 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -12249,9 +12255,9 @@ } }, "node_modules/postcss-color-functional-notation": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.8.tgz", - "integrity": "sha512-S/TpMKVKofNvsxfau/+bw+IA6cSfB6/kmzFj5szUofHOVnFFMB2WwK+Zu07BeMD8T0n+ZnTO5uXiMvAKe2dPkA==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.9.tgz", + "integrity": "sha512-WScwD3pSsIz+QP97sPkGCeJm7xUH0J18k6zV5o8O2a4cQJyv15vLUx/WFQajuJVgZhmJL5awDu8zHnqzAzm4lw==", "funding": [ { "type": "github", @@ -12263,10 +12269,10 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -12547,9 +12553,9 @@ } }, "node_modules/postcss-double-position-gradients": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.0.tgz", - "integrity": "sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.1.tgz", + "integrity": "sha512-ZitCwmvOR4JzXmKw6sZblTgwV1dcfLvClcyjADuqZ5hU0Uk4SVNpvSN9w8NcJ7XuxhRYxVA8m8AB3gy+HNBQOA==", "funding": [ { "type": "github", @@ -12561,7 +12567,7 @@ } ], "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, @@ -12699,9 +12705,9 @@ } }, "node_modules/postcss-lab-function": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.8.tgz", - "integrity": "sha512-plV21I86Hg9q8omNz13G9fhPtLopIWH06bt/Cb5cs1XnaGU2kUtEitvVd4vtQb/VqCdNUHK5swKn3QFmMRbpDg==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.9.tgz", + "integrity": "sha512-IGbsIXbqMDusymJAKYX+f9oakPo89wL9Pzd/qRBQOVf3EIQWT9hgvqC4Me6Dkzxp3KPuIBf6LPkjrLHe/6ZMIQ==", "funding": [ { "type": "github", @@ -12713,10 +12719,10 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -13254,9 +13260,9 @@ } }, "node_modules/postcss-preset-env": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.1.5.tgz", - "integrity": "sha512-LQybafF/K7H+6fAs4SIkgzkSCixJy0/h0gubDIAP3Ihz+IQBRwsjyvBnAZ3JUHD+A/ITaxVRPDxn//a3Qy4pDw==", + "version": "10.1.6", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.1.6.tgz", + "integrity": "sha512-1jRD7vttKLJ7o0mcmmYWKRLm7W14rI8K1I7Y41OeXUPEVc/CAzfTssNUeJ0zKbR+zMk4boqct/gwS/poIFF5Lg==", "funding": [ { "type": "github", @@ -13269,59 +13275,59 @@ ], "dependencies": { "@csstools/postcss-cascade-layers": "^5.0.1", - "@csstools/postcss-color-function": "^4.0.8", - "@csstools/postcss-color-mix-function": "^3.0.8", - "@csstools/postcss-content-alt-text": "^2.0.4", - "@csstools/postcss-exponential-functions": "^2.0.7", + "@csstools/postcss-color-function": "^4.0.9", + "@csstools/postcss-color-mix-function": "^3.0.9", + "@csstools/postcss-content-alt-text": "^2.0.5", + "@csstools/postcss-exponential-functions": "^2.0.8", "@csstools/postcss-font-format-keywords": "^4.0.0", - "@csstools/postcss-gamut-mapping": "^2.0.8", - "@csstools/postcss-gradients-interpolation-method": "^5.0.8", - "@csstools/postcss-hwb-function": "^4.0.8", - "@csstools/postcss-ic-unit": "^4.0.0", + "@csstools/postcss-gamut-mapping": "^2.0.9", + "@csstools/postcss-gradients-interpolation-method": "^5.0.9", + "@csstools/postcss-hwb-function": "^4.0.9", + "@csstools/postcss-ic-unit": "^4.0.1", "@csstools/postcss-initial": "^2.0.1", "@csstools/postcss-is-pseudo-class": "^5.0.1", - "@csstools/postcss-light-dark-function": "^2.0.7", + "@csstools/postcss-light-dark-function": "^2.0.8", "@csstools/postcss-logical-float-and-clear": "^3.0.0", "@csstools/postcss-logical-overflow": "^2.0.0", "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", "@csstools/postcss-logical-resize": "^3.0.0", "@csstools/postcss-logical-viewport-units": "^3.0.3", - "@csstools/postcss-media-minmax": "^2.0.7", + "@csstools/postcss-media-minmax": "^2.0.8", "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.4", "@csstools/postcss-nested-calc": "^4.0.0", "@csstools/postcss-normalize-display-values": "^4.0.0", - "@csstools/postcss-oklab-function": "^4.0.8", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/postcss-random-function": "^1.0.3", - "@csstools/postcss-relative-color-syntax": "^3.0.8", + "@csstools/postcss-oklab-function": "^4.0.9", + "@csstools/postcss-progressive-custom-properties": "^4.0.1", + "@csstools/postcss-random-function": "^2.0.0", + "@csstools/postcss-relative-color-syntax": "^3.0.9", "@csstools/postcss-scope-pseudo-class": "^4.0.1", - "@csstools/postcss-sign-functions": "^1.1.2", - "@csstools/postcss-stepped-value-functions": "^4.0.7", + "@csstools/postcss-sign-functions": "^1.1.3", + "@csstools/postcss-stepped-value-functions": "^4.0.8", "@csstools/postcss-text-decoration-shorthand": "^4.0.2", - "@csstools/postcss-trigonometric-functions": "^4.0.7", + "@csstools/postcss-trigonometric-functions": "^4.0.8", "@csstools/postcss-unset-value": "^4.0.0", - "autoprefixer": "^10.4.19", + "autoprefixer": "^10.4.21", "browserslist": "^4.24.4", "css-blank-pseudo": "^7.0.1", "css-has-pseudo": "^7.0.2", "css-prefers-color-scheme": "^10.0.0", - "cssdb": "^8.2.3", + "cssdb": "^8.2.5", "postcss-attribute-case-insensitive": "^7.0.1", "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^7.0.8", + "postcss-color-functional-notation": "^7.0.9", "postcss-color-hex-alpha": "^10.0.0", "postcss-color-rebeccapurple": "^10.0.0", "postcss-custom-media": "^11.0.5", "postcss-custom-properties": "^14.0.4", "postcss-custom-selectors": "^8.0.4", "postcss-dir-pseudo-class": "^9.0.1", - "postcss-double-position-gradients": "^6.0.0", + "postcss-double-position-gradients": "^6.0.1", "postcss-focus-visible": "^10.0.1", "postcss-focus-within": "^9.0.1", "postcss-font-variant": "^5.0.0", "postcss-gap-properties": "^6.0.0", "postcss-image-set-function": "^7.0.0", - "postcss-lab-function": "^7.0.8", + "postcss-lab-function": "^7.0.9", "postcss-logical": "^8.1.0", "postcss-nesting": "^13.0.1", "postcss-opacity-percentage": "^3.0.0", @@ -13766,9 +13772,9 @@ } }, "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "engines": { "node": ">=0.10.0" } @@ -13906,14 +13912,14 @@ } }, "node_modules/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "dependencies": { - "scheduler": "^0.25.0" + "scheduler": "^0.26.0" }, "peerDependencies": { - "react": "^19.0.0" + "react": "^19.1.0" } }, "node_modules/react-error-overlay": { @@ -14150,19 +14156,6 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, "node_modules/regexpu-core": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", @@ -14358,9 +14351,9 @@ } }, "node_modules/remark-rehype": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", - "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", @@ -14661,14 +14654,14 @@ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" }, "node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==" + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==" }, "node_modules/schema-utils": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", - "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -15244,9 +15237,9 @@ } }, "node_modules/std-env": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", - "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==" + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==" }, "node_modules/string_decoder": { "version": "1.3.0", @@ -15674,9 +15667,9 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", @@ -16158,12 +16151,13 @@ } }, "node_modules/webpack": { - "version": "5.98.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", - "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", + "version": "5.99.7", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz", + "integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==", "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", @@ -16180,7 +16174,7 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.0", + "schema-utils": "^4.3.2", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", @@ -16343,9 +16337,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "engines": { "node": ">=10.0.0" },