~cytrogen/masto-fe

c22fc2fa805a1919650bcfd7f66bc00493929e70 — Claire 2 years ago ee771f5 + 00c2223
Merge commit '00c222377db0e305ac3f4a15bf1c18eb89c1f45f' into glitch-soc/merge-upstream

Conflicts:
- `.rubocop_todo.yml`:
  Took upstream's changes.
M .eslintrc.js => .eslintrc.js +1 -1
@@ 323,7 323,7 @@ module.exports = {
        'plugin:import/recommended',
        'plugin:import/typescript',
        'plugin:promise/recommended',
        'plugin:jsdoc/recommended',
        'plugin:jsdoc/recommended-typescript',
        'plugin:prettier/recommended',
      ],


M .rubocop.yml => .rubocop.yml +25 -86
@@ 53,6 53,28 @@ Lint/UselessAccessModifier:
  ContextCreatingMethods:
    - class_methods

## Disable most Metrics/*Length cops
# Reason: those are often triggered and force significant refactors when this happend
#         but the team feel they are not really improving the code quality.

# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocklength
Metrics/BlockLength:
  Enabled: false

# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsclasslength
Metrics/ClassLength:
  Enabled: false

# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmethodlength
Metrics/MethodLength:
  Enabled: false

# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmodulelength
Metrics/ModuleLength:
  Enabled: false

## End Disable Metrics/*Length cops

# Reason: Currently disabled in .rubocop_todo.yml
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsabcsize
Metrics/AbcSize:


@@ 60,88 82,12 @@ Metrics/AbcSize:
    - 'lib/mastodon/cli/*.rb'
    - db/*migrate/**/*

# Reason: Some functions cannot be broken up, but others may be refactor candidates
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocklength
Metrics/BlockLength:
  CountAsOne: ['array', 'hash', 'heredoc', 'method_call']
  Exclude:
    - 'config/routes.rb'
    - 'lib/mastodon/cli/*.rb'
    - 'lib/tasks/*.rake'
    - 'app/models/concerns/account_associations.rb'
    - 'app/models/concerns/account_interactions.rb'
    - 'app/models/concerns/ldap_authenticable.rb'
    - 'app/models/concerns/omniauthable.rb'
    - 'app/models/concerns/pam_authenticable.rb'
    - 'app/models/concerns/remotable.rb'
    - 'app/services/suspend_account_service.rb'
    - 'app/services/unsuspend_account_service.rb'
    - 'app/views/accounts/show.rss.ruby'
    - 'app/views/tags/show.rss.ruby'
    - 'config/environments/development.rb'
    - 'config/environments/production.rb'
    - 'config/initializers/devise.rb'
    - 'config/initializers/doorkeeper.rb'
    - 'config/initializers/omniauth.rb'
    - 'config/initializers/simple_form.rb'
    - 'config/navigation.rb'
    - 'config/routes.rb'
    - 'config/routes/*.rb'
    - 'db/post_migrate/20221101190723_backfill_admin_action_logs.rb'
    - 'db/post_migrate/20221206114142_backfill_admin_action_logs_again.rb'
    - 'lib/paperclip/gif_transcoder.rb'

# Reason:
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocknesting
Metrics/BlockNesting:
  Exclude:
    - 'lib/mastodon/cli/*.rb'

# Reason: Some Excluded files would be candidates for refactoring but not currently addressed
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsclasslength
Metrics/ClassLength:
  CountAsOne: ['array', 'hash', 'heredoc', 'method_call']
  Exclude:
    - 'lib/mastodon/cli/*.rb'
    - 'app/controllers/admin/accounts_controller.rb'
    - 'app/controllers/api/base_controller.rb'
    - 'app/controllers/api/v1/admin/accounts_controller.rb'
    - 'app/controllers/application_controller.rb'
    - 'app/controllers/auth/registrations_controller.rb'
    - 'app/controllers/auth/sessions_controller.rb'
    - 'app/lib/activitypub/activity.rb'
    - 'app/lib/activitypub/activity/create.rb'
    - 'app/lib/activitypub/tag_manager.rb'
    - 'app/lib/feed_manager.rb'
    - 'app/lib/link_details_extractor.rb'
    - 'app/lib/request.rb'
    - 'app/lib/text_formatter.rb'
    - 'app/lib/user_settings_decorator.rb'
    - 'app/mailers/user_mailer.rb'
    - 'app/models/account.rb'
    - 'app/models/admin/account_action.rb'
    - 'app/models/form/account_batch.rb'
    - 'app/models/media_attachment.rb'
    - 'app/models/status.rb'
    - 'app/models/tag.rb'
    - 'app/models/user.rb'
    - 'app/serializers/activitypub/actor_serializer.rb'
    - 'app/serializers/activitypub/note_serializer.rb'
    - 'app/serializers/rest/status_serializer.rb'
    - 'app/services/account_search_service.rb'
    - 'app/services/activitypub/process_account_service.rb'
    - 'app/services/activitypub/process_status_update_service.rb'
    - 'app/services/backup_service.rb'
    - 'app/services/bulk_import_service.rb'
    - 'app/services/delete_account_service.rb'
    - 'app/services/fan_out_on_write_service.rb'
    - 'app/services/fetch_link_card_service.rb'
    - 'app/services/import_service.rb'
    - 'app/services/notify_service.rb'
    - 'app/services/post_status_service.rb'
    - 'app/services/update_status_service.rb'
    - 'lib/paperclip/color_extractor.rb'

# Reason: Currently disabled in .rubocop_todo.yml
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricscyclomaticcomplexity
Metrics/CyclomaticComplexity:


@@ 149,17 95,10 @@ Metrics/CyclomaticComplexity:
    - lib/mastodon/cli/*.rb
    - db/*migrate/**/*

# Reason: Currently disabled in .rubocop_todo.yml
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmethodlength
Metrics/MethodLength:
  CountAsOne: [array, heredoc]
  Exclude:
    - 'lib/mastodon/cli/*.rb'

# Reason:
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmodulelength
Metrics/ModuleLength:
  CountAsOne: [array, heredoc]
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsparameterlists
Metrics/ParameterLists:
  CountKeywordArgs: false

# Reason: Prevailing style is argument file paths
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsfilepath

M .rubocop_todo.yml => .rubocop_todo.yml +0 -30
@@ 156,12 156,6 @@ Metrics/AbcSize:
  Exclude:
    - 'app/serializers/initial_state_serializer.rb'

# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
# AllowedMethods: refine
Metrics/BlockLength:
  Exclude:
    - 'app/models/concerns/status_safe_reblog_insert.rb'

# Configuration parameters: CountBlocks, Max.
Metrics/BlockNesting:
  Exclude:


@@ 171,28 165,6 @@ Metrics/BlockNesting:
Metrics/CyclomaticComplexity:
  Max: 25

# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
  Max: 58

# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ModuleLength:
  Exclude:
    - 'app/controllers/concerns/signature_verification.rb'
    - 'app/helpers/application_helper.rb'
    - 'app/helpers/jsonld_helper.rb'
    - 'app/models/concerns/account_interactions.rb'
    - 'app/models/concerns/has_user_settings.rb'
    - 'lib/sanitize_ext/sanitize_config.rb'

# Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters.
Metrics/ParameterLists:
  Exclude:
    - 'app/models/concerns/account_interactions.rb'
    - 'app/services/activitypub/fetch_remote_account_service.rb'
    - 'app/services/activitypub/fetch_remote_actor_service.rb'
    - 'app/services/activitypub/fetch_remote_status_service.rb'

# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/PerceivedComplexity:
  Max: 28


@@ 894,7 866,6 @@ Rails/WhereExists:
    - 'app/validators/vote_validator.rb'
    - 'app/workers/move_worker.rb'
    - 'db/migrate/20190529143559_preserve_old_layout_for_existing_users.rb'
    - 'lib/mastodon/cli/email_domain_blocks.rb'
    - 'lib/tasks/tests.rake'
    - 'spec/controllers/api/v1/accounts/notes_controller_spec.rb'
    - 'spec/controllers/api/v1/tags_controller_spec.rb'


@@ 956,7 927,6 @@ Style/FormatStringToken:
  Exclude:
    - 'app/models/privacy_policy.rb'
    - 'config/initializers/devise.rb'
    - 'lib/mastodon/cli/maintenance.rb'
    - 'lib/paperclip/color_extractor.rb'

# This cop supports unsafe autocorrection (--autocorrect-all).

M Gemfile => Gemfile +1 -1
@@ 59,7 59,7 @@ gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.4.1', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.14'
gem 'nokogiri', '~> 1.15'
gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.14'
gem 'ox', '~> 2.14'

M Gemfile.lock => Gemfile.lock +4 -4
@@ 439,8 439,8 @@ GEM
      net-protocol
    net-ssh (7.1.0)
    nio4r (2.5.9)
    nokogiri (1.14.3)
      mini_portile2 (~> 2.8.0)
    nokogiri (1.15.2)
      mini_portile2 (~> 2.8.2)
      racc (~> 1.4)
    nsa (0.2.8)
      activesupport (>= 4.2, < 7)


@@ 642,7 642,7 @@ GEM
      activerecord (>= 4.0.0)
      railties (>= 4.0.0)
    semantic_range (3.0.0)
    sidekiq (6.5.8)
    sidekiq (6.5.9)
      connection_pool (>= 2.2.5, < 3)
      rack (~> 2.0)
      redis (>= 4.5.0, < 5)


@@ 829,7 829,7 @@ DEPENDENCIES
  mime-types (~> 3.4.1)
  net-http (~> 0.3.2)
  net-ldap (~> 0.18)
  nokogiri (~> 1.14)
  nokogiri (~> 1.15)
  nsa (~> 0.2)
  oj (~> 3.14)
  omniauth (~> 1.9)

M app/helpers/languages_helper.rb => app/helpers/languages_helper.rb +0 -2
@@ 1,7 1,5 @@
# frozen_string_literal: true

# rubocop:disable Metrics/ModuleLength

module LanguagesHelper
  ISO_639_1 = {
    aa: ['Afar', 'Afaraf'].freeze,

M app/javascript/mastodon/features/interaction_modal/index.jsx => app/javascript/mastodon/features/interaction_modal/index.jsx +1 -1
@@ 13,7 13,7 @@ import { registrationsOpen } from 'mastodon/initial_state';

const mapStateToProps = (state, { accountId }) => ({
  displayNameHtml: state.getIn(['accounts', accountId, 'display_name_html']),
  signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], '/auth/sign_up'),
  signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up',
});

const mapDispatchToProps = (dispatch) => ({

M app/javascript/mastodon/features/status/index.jsx => app/javascript/mastodon/features/status/index.jsx +3 -2
@@ 166,8 166,9 @@ const makeMapStateToProps = () => {
};

const truncate = (str, num) => {
  if (str.length > num) {
    return str.slice(0, num) + '…';
  const arr = Array.from(str);
  if (arr.length > num) {
    return arr.slice(0, num).join('') + '…';
  } else {
    return str;
  }

M app/javascript/mastodon/features/ui/components/sign_in_banner.jsx => app/javascript/mastodon/features/ui/components/sign_in_banner.jsx +1 -1
@@ 17,7 17,7 @@ const SignInBanner = () => {

  let signupButton;

  const signupUrl = useAppSelector((state) => state.getIn(['server', 'server', 'registrations', 'url'], '/auth/sign_up'));
  const signupUrl = useAppSelector((state) => state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up');

  if (registrationsOpen) {
    signupButton = (

M app/javascript/mastodon/utils/numbers.ts => app/javascript/mastodon/utils/numbers.ts +7 -6
@@ 14,14 14,15 @@ export type DecimalUnits = ValueOf<typeof DECIMAL_UNITS>;
const TEN_THOUSAND = DECIMAL_UNITS.THOUSAND * 10;
const TEN_MILLIONS = DECIMAL_UNITS.MILLION * 10;

export type ShortNumber = [number, DecimalUnits, 0 | 1]; // Array of: shorten number, unit of shorten number and maximum fraction digits

/**
 * @param {number} sourceNumber Number to convert to short number
 * @returns {ShortNumber} Calculated short number
 * @param sourceNumber Number to convert to short number
 * @returns Calculated short number
 * @example
 * shortNumber(5936);
 * // => [5.936, 1000, 1]
 */
export type ShortNumber = [number, DecimalUnits, 0 | 1]; // Array of: shorten number, unit of shorten number and maximum fraction digits
export function toShortNumber(sourceNumber: number): ShortNumber {
  if (sourceNumber < DECIMAL_UNITS.THOUSAND) {
    return [sourceNumber, DECIMAL_UNITS.ONE, 0];


@@ 45,9 46,9 @@ export function toShortNumber(sourceNumber: number): ShortNumber {
}

/**
 * @param {number} sourceNumber Original number that is shortened
 * @param {number} division The scale in which short number is displayed
 * @returns {number} Number that can be used for plurals when short form used
 * @param sourceNumber Original number that is shortened
 * @param division The scale in which short number is displayed
 * @returns Number that can be used for plurals when short form used
 * @example
 * pluralReady(1793, DECIMAL_UNITS.THOUSAND)
 * // => 1790

D app/lib/settings/extend.rb => app/lib/settings/extend.rb +0 -9
@@ 1,9 0,0 @@
# frozen_string_literal: true

module Settings
  module Extend
    def settings
      @settings ||= ScopedSettings.new(self)
    end
  end
end

M app/models/account.rb => app/models/account.rb +1 -1
@@ 123,7 123,7 @@ class Account < ApplicationRecord
  scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc, accounts.id desc')) }
  scope :by_recent_sign_in, -> { order(Arel.sql('(case when users.current_sign_in_at is null then 1 else 0 end) asc, users.current_sign_in_at desc, accounts.id desc')) }
  scope :popular, -> { order('account_stats.followers_count desc') }
  scope :by_domain_and_subdomains, ->(domain) { where(domain: Instance.by_domain_and_subdomain(domain).select(:domain)) }
  scope :by_domain_and_subdomains, ->(domain) { where(domain: Instance.by_domain_and_subdomains(domain).select(:domain)) }
  scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
  scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) }


M app/models/instance.rb => app/models/instance.rb +1 -1
@@ 22,7 22,7 @@ class Instance < ApplicationRecord
  end

  scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
  scope :by_domain_and_subdomain, ->(domain) { where("reverse('.' || domain) LIKE reverse(?)", "%.#{domain}") }
  scope :by_domain_and_subdomains, ->(domain) { where("reverse('.' || domain) LIKE reverse(?)", "%.#{domain}") }

  def self.refresh
    Scenic.database.refresh_materialized_view(table_name, concurrently: true, cascade: false)

M app/services/fetch_resource_service.rb => app/services/fetch_resource_service.rb +1 -1
@@ 19,7 19,7 @@ class FetchResourceService < BaseService

  private

  def process(url, terminal = false)
  def process(url, terminal: false)
    @url = url

    perform_request { |response| process_response(response, terminal) }

M babel.config.js => babel.config.js +3 -3
@@ 11,7 11,7 @@ module.exports = (api) => {
    modules: false,
    debug: false,
    include: [
      'proposal-numeric-separator',
      'transform-numeric-separator',
    ],
  };



@@ 24,8 24,8 @@ module.exports = (api) => {
    plugins: [
      ['react-intl', { messagesDir: './build/messages' }],
      'preval',
      '@babel/plugin-proposal-optional-chaining',
      '@babel/plugin-proposal-nullish-coalescing-operator',
      '@babel/plugin-transform-optional-chaining',
      '@babel/plugin-transform-nullish-coalescing-operator',
    ],
    overrides: [
      {

M lib/mastodon/cli/accounts.rb => lib/mastodon/cli/accounts.rb +22 -30
@@ 113,12 113,7 @@ module Mastodon::CLI
        say('OK', :green)
        say("New password: #{password}")
      else
        user.errors.each do |error|
          say('Failure/Error: ', :red)
          say(error.attribute)
          say("    #{error.type}", :red)
        end

        report_errors(user.errors)
        exit(1)
      end
    end


@@ 189,12 184,7 @@ module Mastodon::CLI
        say('OK', :green)
        say("New password: #{password}") if options[:reset_password]
      else
        user.errors.each do |error|
          say('Failure/Error: ', :red)
          say(error.attribute)
          say("    #{error.type}", :red)
        end

        report_errors(user.errors)
        exit(1)
      end
    end


@@ 217,7 207,6 @@ module Mastodon::CLI
        exit(1)
      end

      dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
      account = nil

      if username.present?


@@ 234,9 223,9 @@ module Mastodon::CLI
        end
      end

      say("Deleting user with #{account.statuses_count} statuses, this might take a while...#{dry_run}")
      DeleteAccountService.new.call(account, reserve_email: false) unless options[:dry_run]
      say("OK#{dry_run}", :green)
      say("Deleting user with #{account.statuses_count} statuses, this might take a while...#{dry_run_mode_suffix}")
      DeleteAccountService.new.call(account, reserve_email: false) unless dry_run?
      say("OK#{dry_run_mode_suffix}", :green)
    end

    option :force, type: :boolean, aliases: [:f], description: 'Override public key check'


@@ 291,7 280,7 @@ module Mastodon::CLI
      Account.remote.select(:uri, 'count(*)').group(:uri).having('count(*) > 1').pluck(:uri).each do |uri|
        say("Duplicates found for #{uri}")
        begin
          ActivityPub::FetchRemoteAccountService.new.call(uri) unless options[:dry_run]
          ActivityPub::FetchRemoteAccountService.new.call(uri) unless dry_run?
        rescue => e
          say("Error processing #{uri}: #{e}", :red)
        end


@@ 332,7 321,6 @@ module Mastodon::CLI
    LONG_DESC
    def cull(*domains)
      skip_threshold = 7.days.ago
      dry_run        = options[:dry_run] ? ' (DRY RUN)' : ''
      skip_domains   = Concurrent::Set.new

      query = Account.remote.where(protocol: :activitypub)


@@ 350,7 338,7 @@ module Mastodon::CLI
        end

        if [404, 410].include?(code)
          DeleteAccountService.new.call(account, reserve_username: false) unless options[:dry_run]
          DeleteAccountService.new.call(account, reserve_username: false) unless dry_run?
          1
        else
          # Touch account even during dry run to avoid getting the account into the window again


@@ 358,7 346,7 @@ module Mastodon::CLI
        end
      end

      say("Visited #{processed} accounts, removed #{culled}#{dry_run}", :green)
      say("Visited #{processed} accounts, removed #{culled}#{dry_run_mode_suffix}", :green)

      unless skip_domains.empty?
        say('The following domains were not available during the check:', :yellow)


@@ 381,21 369,19 @@ module Mastodon::CLI
      specified with space-separated USERNAMES.
    LONG_DESC
    def refresh(*usernames)
      dry_run = options[:dry_run] ? ' (DRY RUN)' : ''

      if options[:domain] || options[:all]
        scope  = Account.remote
        scope  = scope.where(domain: options[:domain]) if options[:domain]

        processed, = parallelize_with_progress(scope) do |account|
          next if options[:dry_run]
          next if dry_run?

          account.reset_avatar!
          account.reset_header!
          account.save
        end

        say("Refreshed #{processed} accounts#{dry_run}", :green, true)
        say("Refreshed #{processed} accounts#{dry_run_mode_suffix}", :green, true)
      elsif !usernames.empty?
        usernames.each do |user|
          user, domain = user.split('@')


@@ 406,7 392,7 @@ module Mastodon::CLI
            exit(1)
          end

          next if options[:dry_run]
          next if dry_run?

          begin
            account.reset_avatar!


@@ 417,7 403,7 @@ module Mastodon::CLI
          end
        end

        say("OK#{dry_run}", :green)
        say("OK#{dry_run_mode_suffix}", :green)
      else
        say('No account(s) given', :red)
        exit(1)


@@ 568,8 554,6 @@ module Mastodon::CLI
      - not muted/blocked by us
    LONG_DESC
    def prune
      dry_run = options[:dry_run] ? ' (dry run)' : ''

      query = Account.remote.where.not(actor_type: %i(Application Service))
      query = query.where('NOT EXISTS (SELECT 1 FROM mentions WHERE account_id = accounts.id)')
      query = query.where('NOT EXISTS (SELECT 1 FROM favourites WHERE account_id = accounts.id)')


@@ 585,11 569,11 @@ module Mastodon::CLI
        next if account.suspended?
        next if account.silenced?

        account.destroy unless options[:dry_run]
        account.destroy unless dry_run?
        1
      end

      say("OK, pruned #{deleted} accounts#{dry_run}", :green)
      say("OK, pruned #{deleted} accounts#{dry_run_mode_suffix}", :green)
    end

    option :force, type: :boolean


@@ 667,6 651,14 @@ module Mastodon::CLI

    private

    def report_errors(errors)
      errors.each do |error|
        say('Failure/Error: ', :red)
        say(error.attribute)
        say("    #{error.type}", :red)
      end
    end

    def rotate_keys_for_account(account, delay = 0)
      if account.nil?
        say('No such account', :red)

M lib/mastodon/cli/domains.rb => lib/mastodon/cli/domains.rb +7 -8
@@ 34,7 34,6 @@ module Mastodon::CLI
      When the --purge-domain-blocks option is given, also purge matching domain blocks.
    LONG_DESC
    def purge(*domains)
      dry_run            = options[:dry_run] ? ' (DRY RUN)' : ''
      domains            = domains.map { |domain| TagManager.instance.normalize_domain(domain) }
      account_scope      = Account.none
      domain_block_scope = DomainBlock.none


@@ 79,23 78,23 @@ module Mastodon::CLI

      # Actually perform the deletions
      processed, = parallelize_with_progress(account_scope) do |account|
        DeleteAccountService.new.call(account, reserve_username: false, skip_side_effects: true) unless options[:dry_run]
        DeleteAccountService.new.call(account, reserve_username: false, skip_side_effects: true) unless dry_run?
      end

      say("Removed #{processed} accounts#{dry_run}", :green)
      say("Removed #{processed} accounts#{dry_run_mode_suffix}", :green)

      if options[:purge_domain_blocks]
        domain_block_count = domain_block_scope.count
        domain_block_scope.in_batches.destroy_all unless options[:dry_run]
        say("Removed #{domain_block_count} domain blocks#{dry_run}", :green)
        domain_block_scope.in_batches.destroy_all unless dry_run?
        say("Removed #{domain_block_count} domain blocks#{dry_run_mode_suffix}", :green)
      end

      custom_emojis_count = emoji_scope.count
      emoji_scope.in_batches.destroy_all unless options[:dry_run]
      emoji_scope.in_batches.destroy_all unless dry_run?

      Instance.refresh unless options[:dry_run]
      Instance.refresh unless dry_run?

      say("Removed #{custom_emojis_count} custom emojis#{dry_run}", :green)
      say("Removed #{custom_emojis_count} custom emojis#{dry_run_mode_suffix}", :green)
    end

    option :concurrency, type: :numeric, default: 50, aliases: [:c]

M lib/mastodon/cli/email_domain_blocks.rb => lib/mastodon/cli/email_domain_blocks.rb +2 -2
@@ 39,7 39,7 @@ module Mastodon::CLI
      processed = 0

      domains.each do |domain|
        if EmailDomainBlock.where(domain: domain).exists?
        if EmailDomainBlock.exists?(domain: domain)
          say("#{domain} is already blocked.", :yellow)
          skipped += 1
          next


@@ 60,7 60,7 @@ module Mastodon::CLI
        (email_domain_block.other_domains || []).uniq.each do |hostname|
          another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: email_domain_block)

          if EmailDomainBlock.where(domain: hostname).exists?
          if EmailDomainBlock.exists?(domain: hostname)
            say("#{hostname} is already blocked.", :yellow)
            skipped += 1
            next

M lib/mastodon/cli/feeds.rb => lib/mastodon/cli/feeds.rb +4 -6
@@ 18,14 18,12 @@ module Mastodon::CLI
      Otherwise, a single user specified by USERNAME.
    LONG_DESC
    def build(username = nil)
      dry_run = options[:dry_run] ? '(DRY RUN)' : ''

      if options[:all] || username.nil?
        processed, = parallelize_with_progress(Account.joins(:user).merge(User.active)) do |account|
          PrecomputeFeedService.new.call(account) unless options[:dry_run]
          PrecomputeFeedService.new.call(account) unless dry_run?
        end

        say("Regenerated feeds for #{processed} accounts #{dry_run}", :green, true)
        say("Regenerated feeds for #{processed} accounts #{dry_run_mode_suffix}", :green, true)
      elsif username.present?
        account = Account.find_local(username)



@@ 34,9 32,9 @@ module Mastodon::CLI
          exit(1)
        end

        PrecomputeFeedService.new.call(account) unless options[:dry_run]
        PrecomputeFeedService.new.call(account) unless dry_run?

        say("OK #{dry_run}", :green, true)
        say("OK #{dry_run_mode_suffix}", :green, true)
      else
        say('No account(s) given', :red)
        exit(1)

M lib/mastodon/cli/helper.rb => lib/mastodon/cli/helper.rb +4 -0
@@ 15,6 15,10 @@ module Mastodon::CLI
      options[:dry_run]
    end

    def dry_run_mode_suffix
      dry_run? ? ' (DRY RUN)' : ''
    end

    def create_progress_bar(total = nil)
      ProgressBar.create(total: total, format: '%c/%u |%b%i| %e')
    end

M lib/mastodon/cli/main.rb => lib/mastodon/cli/main.rb +5 -6
@@ 94,7 94,7 @@ module Mastodon::CLI

      exit(1) unless prompt.ask('Type in the domain of the server to confirm:', required: true) == Rails.configuration.x.local_domain

      unless options[:dry_run]
      unless dry_run?
        prompt.warn('This operation WILL NOT be reversible. It can also take a long time.')
        prompt.warn('While the data won\'t be erased locally, the server will be in a BROKEN STATE afterwards.')
        prompt.warn('A running Sidekiq process is required. Do not shut it down until queues clear.')


@@ 104,12 104,11 @@ module Mastodon::CLI

      inboxes   = Account.inboxes
      processed = 0
      dry_run   = options[:dry_run] ? ' (DRY RUN)' : ''

      Setting.registrations_mode = 'none' unless options[:dry_run]
      Setting.registrations_mode = 'none' unless dry_run?

      if inboxes.empty?
        Account.local.without_suspended.in_batches.update_all(suspended_at: Time.now.utc, suspension_origin: :local) unless options[:dry_run]
        Account.local.without_suspended.in_batches.update_all(suspended_at: Time.now.utc, suspension_origin: :local) unless dry_run?
        prompt.ok('It seems like your server has not federated with anything')
        prompt.ok('You can shut it down and delete it any time')
        return


@@ 126,7 125,7 @@ module Mastodon::CLI

        json = Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(account))

        unless options[:dry_run]
        unless dry_run?
          ActivityPub::DeliveryWorker.push_bulk(inboxes, limit: 1_000) do |inbox_url|
            [json, account.id, inbox_url]
          end


@@ 140,7 139,7 @@ module Mastodon::CLI
      Account.local.without_suspended.find_each { |account| delete_account.call(account) }
      Account.local.suspended.joins(:deletion_request).find_each { |account| delete_account.call(account) }

      prompt.ok("Queued #{inboxes.size * processed} items into Sidekiq for #{processed} accounts#{dry_run}")
      prompt.ok("Queued #{inboxes.size * processed} items into Sidekiq for #{processed} accounts#{dry_run_mode_suffix}")
      prompt.ok('Wait until Sidekiq processes all items, then you can shut everything down and delete the data')
    rescue TTY::Reader::InputInterrupt
      exit(1)

M lib/mastodon/cli/maintenance.rb => lib/mastodon/cli/maintenance.rb +68 -63
@@ 1,6 1,5 @@
# frozen_string_literal: true

require 'tty-prompt'
require_relative 'base'

module Mastodon::CLI


@@ 134,25 133,23 @@ module Mastodon::CLI
      Mastodon has to be stopped to run this task, which will take a long time and may be destructive.
    LONG_DESC
    def fix_duplicates
      @prompt = TTY::Prompt.new

      if ActiveRecord::Migrator.current_version < MIN_SUPPORTED_VERSION
        @prompt.error 'Your version of the database schema is too old and is not supported by this script.'
        @prompt.error 'Please update to at least Mastodon 3.0.0 before running this script.'
        say 'Your version of the database schema is too old and is not supported by this script.', :red
        say 'Please update to at least Mastodon 3.0.0 before running this script.', :red
        exit(1)
      elsif ActiveRecord::Migrator.current_version > MAX_SUPPORTED_VERSION
        @prompt.warn 'Your version of the database schema is more recent than this script, this may cause unexpected errors.'
        exit(1) unless @prompt.yes?('Continue anyway? (Yes/No)')
        say 'Your version of the database schema is more recent than this script, this may cause unexpected errors.', :yellow
        exit(1) unless yes?('Continue anyway? (Yes/No)')
      end

      if Sidekiq::ProcessSet.new.any?
        @prompt.error 'It seems Sidekiq is running. All Mastodon processes need to be stopped when using this script.'
        say 'It seems Sidekiq is running. All Mastodon processes need to be stopped when using this script.', :red
        exit(1)
      end

      @prompt.warn 'This task will take a long time to run and is potentially destructive.'
      @prompt.warn 'Please make sure to stop Mastodon and have a backup.'
      exit(1) unless @prompt.yes?('Continue? (Yes/No)')
      say 'This task will take a long time to run and is potentially destructive.', :yellow
      say 'Please make sure to stop Mastodon and have a backup.', :yellow
      exit(1) unless yes?('Continue? (Yes/No)')

      deduplicate_users!
      deduplicate_account_domain_blocks!


@@ 176,7 173,7 @@ module Mastodon::CLI
      Scenic.database.refresh_materialized_view('instances', concurrently: true, cascade: false) if ActiveRecord::Migrator.current_version >= 2020_12_06_004238
      Rails.cache.clear

      @prompt.say 'Finished!'
      say 'Finished!'
    end

    private


@@ 184,7 181,7 @@ module Mastodon::CLI
    def deduplicate_accounts!
      remove_index_if_exists!(:accounts, 'index_accounts_on_username_and_domain_lower')

      @prompt.say 'Deduplicating accounts… for local accounts, you will be asked to chose which account to keep unchanged.'
      say 'Deduplicating accounts… for local accounts, you will be asked to chose which account to keep unchanged.'

      find_duplicate_accounts.each do |row|
        accounts = Account.where(id: row['ids'].split(',')).to_a


@@ 196,14 193,14 @@ module Mastodon::CLI
        end
      end

      @prompt.say 'Restoring index_accounts_on_username_and_domain_lower…'
      say 'Restoring index_accounts_on_username_and_domain_lower…'
      if ActiveRecord::Migrator.current_version < 2020_06_20_164023
        ActiveRecord::Base.connection.add_index :accounts, 'lower (username), lower(domain)', name: 'index_accounts_on_username_and_domain_lower', unique: true
      else
        ActiveRecord::Base.connection.add_index :accounts, "lower (username), COALESCE(lower(domain), '')", name: 'index_accounts_on_username_and_domain_lower', unique: true
      end

      @prompt.say 'Reindexing textual indexes on accounts…'
      say 'Reindexing textual indexes on accounts…'
      ActiveRecord::Base.connection.execute('REINDEX INDEX search_index;')
      ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_uri;')
      ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_url;')


@@ 215,19 212,18 @@ module Mastodon::CLI
      remove_index_if_exists!(:users, 'index_users_on_remember_token')
      remove_index_if_exists!(:users, 'index_users_on_reset_password_token')

      @prompt.say 'Deduplicating user records…'
      say 'Deduplicating user records…'

      # Deduplicating email
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users GROUP BY email HAVING count(*) > 1").each do |row|
        users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse
        ref_user = users.shift
        @prompt.warn "Multiple users registered with e-mail address #{ref_user.email}."
        @prompt.warn "e-mail will be disabled for the following accounts: #{user.map(&:account).map(&:acct).join(', ')}"
        @prompt.warn 'Please reach out to them and set another address with `tootctl account modify` or delete them.'
        say "Multiple users registered with e-mail address #{ref_user.email}.", :yellow
        say "e-mail will be disabled for the following accounts: #{user.map(&:account).map(&:acct).join(', ')}", :yellow
        say 'Please reach out to them and set another address with `tootctl account modify` or delete them.', :yellow

        i = 0
        users.each do |user|
          user.update!(email: "#{i} " + user.email)
        users.each_with_index do |user, index|
          user.update!(email: "#{index} " + user.email)
        end
      end



@@ 235,7 231,7 @@ module Mastodon::CLI
      deduplicate_users_process_remember_token
      deduplicate_users_process_password_token

      @prompt.say 'Restoring users indexes…'
      say 'Restoring users indexes…'
      ActiveRecord::Base.connection.add_index :users, ['confirmation_token'], name: 'index_users_on_confirmation_token', unique: true
      ActiveRecord::Base.connection.add_index :users, ['email'], name: 'index_users_on_email', unique: true
      ActiveRecord::Base.connection.add_index :users, ['remember_token'], name: 'index_users_on_remember_token', unique: true if ActiveRecord::Migrator.current_version < 2022_01_18_183010


@@ 250,7 246,7 @@ module Mastodon::CLI
    def deduplicate_users_process_confirmation_token
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE confirmation_token IS NOT NULL GROUP BY confirmation_token HAVING count(*) > 1").each do |row|
        users = User.where(id: row['ids'].split(',')).sort_by(&:created_at).reverse.drop(1)
        @prompt.warn "Unsetting confirmation token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}"
        say "Unsetting confirmation token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}", :yellow

        users.each do |user|
          user.update!(confirmation_token: nil)


@@ 262,7 258,7 @@ module Mastodon::CLI
      if ActiveRecord::Migrator.current_version < 2022_01_18_183010
        ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE remember_token IS NOT NULL GROUP BY remember_token HAVING count(*) > 1").each do |row|
          users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse.drop(1)
          @prompt.warn "Unsetting remember token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}"
          say "Unsetting remember token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}", :yellow

          users.each do |user|
            user.update!(remember_token: nil)


@@ 274,7 270,7 @@ module Mastodon::CLI
    def deduplicate_users_process_password_token
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE reset_password_token IS NOT NULL GROUP BY reset_password_token HAVING count(*) > 1").each do |row|
        users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse.drop(1)
        @prompt.warn "Unsetting password reset token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}"
        say "Unsetting password reset token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}", :yellow

        users.each do |user|
          user.update!(reset_password_token: nil)


@@ 285,12 281,12 @@ module Mastodon::CLI
    def deduplicate_account_domain_blocks!
      remove_index_if_exists!(:account_domain_blocks, 'index_account_domain_blocks_on_account_id_and_domain')

      @prompt.say 'Removing duplicate account domain blocks…'
      say 'Removing duplicate account domain blocks…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM account_domain_blocks GROUP BY account_id, domain HAVING count(*) > 1").each do |row|
        AccountDomainBlock.where(id: row['ids'].split(',').drop(1)).delete_all
      end

      @prompt.say 'Restoring account domain blocks indexes…'
      say 'Restoring account domain blocks indexes…'
      ActiveRecord::Base.connection.add_index :account_domain_blocks, %w(account_id domain), name: 'index_account_domain_blocks_on_account_id_and_domain', unique: true
    end



@@ 299,12 295,12 @@ module Mastodon::CLI

      remove_index_if_exists!(:account_identity_proofs, 'index_account_proofs_on_account_and_provider_and_username')

      @prompt.say 'Removing duplicate account identity proofs…'
      say 'Removing duplicate account identity proofs…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM account_identity_proofs GROUP BY account_id, provider, provider_username HAVING count(*) > 1").each do |row|
        AccountIdentityProof.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
      end

      @prompt.say 'Restoring account identity proofs indexes…'
      say 'Restoring account identity proofs indexes…'
      ActiveRecord::Base.connection.add_index :account_identity_proofs, %w(account_id provider provider_username), name: 'index_account_proofs_on_account_and_provider_and_username', unique: true
    end



@@ 313,19 309,19 @@ module Mastodon::CLI

      remove_index_if_exists!(:announcement_reactions, 'index_announcement_reactions_on_account_id_and_announcement_id')

      @prompt.say 'Removing duplicate account identity proofs…'
      say 'Removing duplicate account identity proofs…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM announcement_reactions GROUP BY account_id, announcement_id, name HAVING count(*) > 1").each do |row|
        AnnouncementReaction.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
      end

      @prompt.say 'Restoring announcement_reactions indexes…'
      say 'Restoring announcement_reactions indexes…'
      ActiveRecord::Base.connection.add_index :announcement_reactions, %w(account_id announcement_id name), name: 'index_announcement_reactions_on_account_id_and_announcement_id', unique: true
    end

    def deduplicate_conversations!
      remove_index_if_exists!(:conversations, 'index_conversations_on_uri')

      @prompt.say 'Deduplicating conversations…'
      say 'Deduplicating conversations…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM conversations WHERE uri IS NOT NULL GROUP BY uri HAVING count(*) > 1").each do |row|
        conversations = Conversation.where(id: row['ids'].split(',')).sort_by(&:id).reverse



@@ 337,7 333,7 @@ module Mastodon::CLI
        end
      end

      @prompt.say 'Restoring conversations indexes…'
      say 'Restoring conversations indexes…'
      if ActiveRecord::Migrator.current_version < 2022_03_07_083603
        ActiveRecord::Base.connection.add_index :conversations, ['uri'], name: 'index_conversations_on_uri', unique: true
      else


@@ 348,7 344,7 @@ module Mastodon::CLI
    def deduplicate_custom_emojis!
      remove_index_if_exists!(:custom_emojis, 'index_custom_emojis_on_shortcode_and_domain')

      @prompt.say 'Deduplicating custom_emojis…'
      say 'Deduplicating custom_emojis…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM custom_emojis GROUP BY shortcode, domain HAVING count(*) > 1").each do |row|
        emojis = CustomEmoji.where(id: row['ids'].split(',')).sort_by(&:id).reverse



@@ 360,14 356,14 @@ module Mastodon::CLI
        end
      end

      @prompt.say 'Restoring custom_emojis indexes…'
      say 'Restoring custom_emojis indexes…'
      ActiveRecord::Base.connection.add_index :custom_emojis, %w(shortcode domain), name: 'index_custom_emojis_on_shortcode_and_domain', unique: true
    end

    def deduplicate_custom_emoji_categories!
      remove_index_if_exists!(:custom_emoji_categories, 'index_custom_emoji_categories_on_name')

      @prompt.say 'Deduplicating custom_emoji_categories…'
      say 'Deduplicating custom_emoji_categories…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM custom_emoji_categories GROUP BY name HAVING count(*) > 1").each do |row|
        categories = CustomEmojiCategory.where(id: row['ids'].split(',')).sort_by(&:id).reverse



@@ 379,26 375,26 @@ module Mastodon::CLI
        end
      end

      @prompt.say 'Restoring custom_emoji_categories indexes…'
      say 'Restoring custom_emoji_categories indexes…'
      ActiveRecord::Base.connection.add_index :custom_emoji_categories, ['name'], name: 'index_custom_emoji_categories_on_name', unique: true
    end

    def deduplicate_domain_allows!
      remove_index_if_exists!(:domain_allows, 'index_domain_allows_on_domain')

      @prompt.say 'Deduplicating domain_allows…'
      say 'Deduplicating domain_allows…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM domain_allows GROUP BY domain HAVING count(*) > 1").each do |row|
        DomainAllow.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
      end

      @prompt.say 'Restoring domain_allows indexes…'
      say 'Restoring domain_allows indexes…'
      ActiveRecord::Base.connection.add_index :domain_allows, ['domain'], name: 'index_domain_allows_on_domain', unique: true
    end

    def deduplicate_domain_blocks!
      remove_index_if_exists!(:domain_blocks, 'index_domain_blocks_on_domain')

      @prompt.say 'Deduplicating domain_allows…'
      say 'Deduplicating domain_allows…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM domain_blocks GROUP BY domain HAVING count(*) > 1").each do |row|
        domain_blocks = DomainBlock.where(id: row['ids'].split(',')).by_severity.reverse.to_a



@@ 415,7 411,7 @@ module Mastodon::CLI
        domain_blocks.each(&:destroy)
      end

      @prompt.say 'Restoring domain_blocks indexes…'
      say 'Restoring domain_blocks indexes…'
      ActiveRecord::Base.connection.add_index :domain_blocks, ['domain'], name: 'index_domain_blocks_on_domain', unique: true
    end



@@ 424,37 420,37 @@ module Mastodon::CLI

      remove_index_if_exists!(:unavailable_domains, 'index_unavailable_domains_on_domain')

      @prompt.say 'Deduplicating unavailable_domains…'
      say 'Deduplicating unavailable_domains…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM unavailable_domains GROUP BY domain HAVING count(*) > 1").each do |row|
        UnavailableDomain.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
      end

      @prompt.say 'Restoring domain_allows indexes…'
      say 'Restoring domain_allows indexes…'
      ActiveRecord::Base.connection.add_index :unavailable_domains, ['domain'], name: 'index_unavailable_domains_on_domain', unique: true
    end

    def deduplicate_email_domain_blocks!
      remove_index_if_exists!(:email_domain_blocks, 'index_email_domain_blocks_on_domain')

      @prompt.say 'Deduplicating email_domain_blocks…'
      say 'Deduplicating email_domain_blocks…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM email_domain_blocks GROUP BY domain HAVING count(*) > 1").each do |row|
        domain_blocks = EmailDomainBlock.where(id: row['ids'].split(',')).sort_by { |b| b.parent.nil? ? 1 : 0 }.to_a
        domain_blocks.drop(1).each(&:destroy)
      end

      @prompt.say 'Restoring email_domain_blocks indexes…'
      say 'Restoring email_domain_blocks indexes…'
      ActiveRecord::Base.connection.add_index :email_domain_blocks, ['domain'], name: 'index_email_domain_blocks_on_domain', unique: true
    end

    def deduplicate_media_attachments!
      remove_index_if_exists!(:media_attachments, 'index_media_attachments_on_shortcode')

      @prompt.say 'Deduplicating media_attachments…'
      say 'Deduplicating media_attachments…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM media_attachments WHERE shortcode IS NOT NULL GROUP BY shortcode HAVING count(*) > 1").each do |row|
        MediaAttachment.where(id: row['ids'].split(',').drop(1)).update_all(shortcode: nil)
      end

      @prompt.say 'Restoring media_attachments indexes…'
      say 'Restoring media_attachments indexes…'
      if ActiveRecord::Migrator.current_version < 2022_03_10_060626
        ActiveRecord::Base.connection.add_index :media_attachments, ['shortcode'], name: 'index_media_attachments_on_shortcode', unique: true
      else


@@ 465,19 461,19 @@ module Mastodon::CLI
    def deduplicate_preview_cards!
      remove_index_if_exists!(:preview_cards, 'index_preview_cards_on_url')

      @prompt.say 'Deduplicating preview_cards…'
      say 'Deduplicating preview_cards…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM preview_cards GROUP BY url HAVING count(*) > 1").each do |row|
        PreviewCard.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
      end

      @prompt.say 'Restoring preview_cards indexes…'
      say 'Restoring preview_cards indexes…'
      ActiveRecord::Base.connection.add_index :preview_cards, ['url'], name: 'index_preview_cards_on_url', unique: true
    end

    def deduplicate_statuses!
      remove_index_if_exists!(:statuses, 'index_statuses_on_uri')

      @prompt.say 'Deduplicating statuses…'
      say 'Deduplicating statuses…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM statuses WHERE uri IS NOT NULL GROUP BY uri HAVING count(*) > 1").each do |row|
        statuses = Status.where(id: row['ids'].split(',')).sort_by(&:id)
        ref_status = statuses.shift


@@ 487,7 483,7 @@ module Mastodon::CLI
        end
      end

      @prompt.say 'Restoring statuses indexes…'
      say 'Restoring statuses indexes…'
      if ActiveRecord::Migrator.current_version < 2022_03_10_060706
        ActiveRecord::Base.connection.add_index :statuses, ['uri'], name: 'index_statuses_on_uri', unique: true
      else


@@ 499,7 495,7 @@ module Mastodon::CLI
      remove_index_if_exists!(:tags, 'index_tags_on_name_lower')
      remove_index_if_exists!(:tags, 'index_tags_on_name_lower_btree')

      @prompt.say 'Deduplicating tags…'
      say 'Deduplicating tags…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM tags GROUP BY lower((name)::text) HAVING count(*) > 1").each do |row|
        tags = Tag.where(id: row['ids'].split(',')).sort_by { |t| [t.usable?, t.trendable?, t.listable?].count(false) }
        ref_tag = tags.shift


@@ 509,7 505,7 @@ module Mastodon::CLI
        end
      end

      @prompt.say 'Restoring tags indexes…'
      say 'Restoring tags indexes…'
      if ActiveRecord::Migrator.current_version < 2021_04_21_121431
        ActiveRecord::Base.connection.add_index :tags, 'lower((name)::text)', name: 'index_tags_on_name_lower', unique: true
      else


@@ 522,12 518,12 @@ module Mastodon::CLI

      remove_index_if_exists!(:webauthn_credentials, 'index_webauthn_credentials_on_external_id')

      @prompt.say 'Deduplicating webauthn_credentials…'
      say 'Deduplicating webauthn_credentials…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM webauthn_credentials GROUP BY external_id HAVING count(*) > 1").each do |row|
        WebauthnCredential.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
      end

      @prompt.say 'Restoring webauthn_credentials indexes…'
      say 'Restoring webauthn_credentials indexes…'
      ActiveRecord::Base.connection.add_index :webauthn_credentials, ['external_id'], name: 'index_webauthn_credentials_on_external_id', unique: true
    end



@@ 536,28 532,37 @@ module Mastodon::CLI

      remove_index_if_exists!(:webhooks, 'index_webhooks_on_url')

      @prompt.say 'Deduplicating webhooks…'
      say 'Deduplicating webhooks…'
      ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM webhooks GROUP BY url HAVING count(*) > 1").each do |row|
        Webhooks.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
      end

      @prompt.say 'Restoring webhooks indexes…'
      say 'Restoring webhooks indexes…'
      ActiveRecord::Base.connection.add_index :webhooks, ['url'], name: 'index_webhooks_on_url', unique: true
    end

    def deduplicate_local_accounts!(accounts)
      accounts = accounts.sort_by(&:id).reverse

      @prompt.warn "Multiple local accounts were found for username '#{accounts.first.username}'."
      @prompt.warn 'All those accounts are distinct accounts but only the most recently-created one is fully-functional.'
      say "Multiple local accounts were found for username '#{accounts.first.username}'.", :yellow
      say 'All those accounts are distinct accounts but only the most recently-created one is fully-functional.', :yellow

      accounts.each_with_index do |account, idx|
        @prompt.say format('%2d. %s: created at: %s; updated at: %s; last logged in at: %s; statuses: %5d; last status at: %s', idx, account.username, account.created_at, account.updated_at, account.user&.last_sign_in_at&.to_s || 'N/A', account.account_stat&.statuses_count || 0, account.account_stat&.last_status_at || 'N/A')
        say format(
          '%<index>2d. %<username>s: created at: %<created_at>s; updated at: %<updated_at>s; last logged in at: %<last_log_in_at>s; statuses: %<status_count>5d; last status at: %<last_status_at>s',
          index: idx,
          username: account.username,
          created_at: account.created_at,
          updated_at: account.updated_at,
          last_log_in_at: account.user&.last_sign_in_at&.to_s || 'N/A',
          status_count: account.account_stat&.statuses_count || 0,
          last_status_at: account.account_stat&.last_status_at || 'N/A'
        )
      end

      @prompt.say 'Please chose the one to keep unchanged, other ones will be automatically renamed.'
      say 'Please chose the one to keep unchanged, other ones will be automatically renamed.'

      ref_id = @prompt.ask('Account to keep unchanged:') do |q|
      ref_id = ask('Account to keep unchanged:') do |q|
        q.required true
        q.default 0
        q.convert :int

M lib/mastodon/cli/media.rb => lib/mastodon/cli/media.rb +12 -15
@@ 35,12 35,12 @@ module Mastodon::CLI
        say('--prune-profiles and --remove-headers should not be specified simultaneously', :red, true)
        exit(1)
      end

      if options[:include_follows] && !(options[:prune_profiles] || options[:remove_headers])
        say('--include-follows can only be used with --prune-profiles or --remove-headers', :red, true)
        exit(1)
      end
      time_ago        = options[:days].days.ago
      dry_run         = options[:dry_run] ? ' (DRY RUN)' : ''
      time_ago = options[:days].days.ago

      if options[:prune_profiles] || options[:remove_headers]
        processed, aggregate = parallelize_with_progress(Account.remote.where({ last_webfingered_at: ..time_ago, updated_at: ..time_ago })) do |account|


@@ 51,7 51,7 @@ module Mastodon::CLI
          size = (account.header_file_size || 0)
          size += (account.avatar_file_size || 0) if options[:prune_profiles]

          unless options[:dry_run]
          unless dry_run?
            account.header.destroy
            account.avatar.destroy if options[:prune_profiles]
            account.save!


@@ 60,7 60,7 @@ module Mastodon::CLI
          size
        end

        say("Visited #{processed} accounts and removed profile media totaling #{number_to_human_size(aggregate)}#{dry_run}", :green, true)
        say("Visited #{processed} accounts and removed profile media totaling #{number_to_human_size(aggregate)}#{dry_run_mode_suffix}", :green, true)
      end

      unless options[:prune_profiles] || options[:remove_headers]


@@ 69,7 69,7 @@ module Mastodon::CLI

          size = (media_attachment.file_file_size || 0) + (media_attachment.thumbnail_file_size || 0)

          unless options[:dry_run]
          unless dry_run?
            media_attachment.file.destroy
            media_attachment.thumbnail.destroy
            media_attachment.save


@@ 78,7 78,7 @@ module Mastodon::CLI
          size
        end

        say("Removed #{processed} media attachments (approx. #{number_to_human_size(aggregate)})#{dry_run}", :green, true)
        say("Removed #{processed} media attachments (approx. #{number_to_human_size(aggregate)})#{dry_run_mode_suffix}", :green, true)
      end
    end



@@ 97,7 97,6 @@ module Mastodon::CLI
      progress        = create_progress_bar(nil)
      reclaimed_bytes = 0
      removed         = 0
      dry_run         = options[:dry_run] ? ' (DRY RUN)' : ''
      prefix          = options[:prefix]

      case Paperclip::Attachment.default_options[:storage]


@@ 123,7 122,7 @@ module Mastodon::CLI
          record_map = preload_records_from_mixed_objects(objects)

          objects.each do |object|
            object.acl.put(acl: s3_permissions) if options[:fix_permissions] && !options[:dry_run]
            object.acl.put(acl: s3_permissions) if options[:fix_permissions] && !dry_run?

            path_segments = object.key.split('/')
            path_segments.delete('cache')


@@ 145,7 144,7 @@ module Mastodon::CLI
            next unless attachment.blank? || !attachment.variant?(file_name)

            begin
              object.delete unless options[:dry_run]
              object.delete unless dry_run?

              reclaimed_bytes += object.size
              removed += 1


@@ 194,7 193,7 @@ module Mastodon::CLI
          begin
            size = File.size(path)

            unless options[:dry_run]
            unless dry_run?
              File.delete(path)
              begin
                FileUtils.rmdir(File.dirname(path), parents: true)


@@ 216,7 215,7 @@ module Mastodon::CLI
      progress.total = progress.progress
      progress.finish

      say("Removed #{removed} orphans (approx. #{number_to_human_size(reclaimed_bytes)})#{dry_run}", :green, true)
      say("Removed #{removed} orphans (approx. #{number_to_human_size(reclaimed_bytes)})#{dry_run_mode_suffix}", :green, true)
    end

    option :account, type: :string


@@ 246,8 245,6 @@ module Mastodon::CLI
      not be re-downloaded. To force re-download of every URL, use --force.
    DESC
    def refresh
      dry_run = options[:dry_run] ? ' (DRY RUN)' : ''

      if options[:status]
        scope = MediaAttachment.where(status_id: options[:status])
      elsif options[:account]


@@ 274,7 271,7 @@ module Mastodon::CLI
        next if media_attachment.remote_url.blank? || (!options[:force] && media_attachment.file_file_name.present?)
        next if DomainBlock.reject_media?(media_attachment.account.domain)

        unless options[:dry_run]
        unless dry_run?
          media_attachment.reset_file!
          media_attachment.reset_thumbnail!
          media_attachment.save


@@ 283,7 280,7 @@ module Mastodon::CLI
        media_attachment.file_file_size + (media_attachment.thumbnail_file_size || 0)
      end

      say("Downloaded #{processed} media attachments (approx. #{number_to_human_size(aggregate)})#{dry_run}", :green, true)
      say("Downloaded #{processed} media attachments (approx. #{number_to_human_size(aggregate)})#{dry_run_mode_suffix}", :green, true)
    end

    desc 'usage', 'Calculate disk space consumed by Mastodon'

M lib/mastodon/cli/preview_cards.rb => lib/mastodon/cli/preview_cards.rb +2 -3
@@ 27,7 27,6 @@ module Mastodon::CLI
    DESC
    def remove
      time_ago = options[:days].days.ago
      dry_run  = options[:dry_run] ? ' (DRY RUN)' : ''
      link     = options[:link] ? 'link-type ' : ''
      scope    = PreviewCard.cached
      scope    = scope.where(type: :link) if options[:link]


@@ 38,7 37,7 @@ module Mastodon::CLI

        size = preview_card.image_file_size

        unless options[:dry_run]
        unless dry_run?
          preview_card.image.destroy
          preview_card.save
        end


@@ 46,7 45,7 @@ module Mastodon::CLI
        size
      end

      say("Removed #{processed} #{link}preview cards (approx. #{number_to_human_size(aggregate)})#{dry_run}", :green, true)
      say("Removed #{processed} #{link}preview cards (approx. #{number_to_human_size(aggregate)})#{dry_run_mode_suffix}", :green, true)
    end
  end
end

M lib/mastodon/cli/upgrade.rb => lib/mastodon/cli/upgrade.rb +1 -2
@@ 17,7 17,6 @@ module Mastodon::CLI
    LONG_DESC
    def storage_schema
      progress = create_progress_bar(nil)
      dry_run  = dry_run? ? ' (DRY RUN)' : ''
      records  = 0

      klasses = [


@@ 69,7 68,7 @@ module Mastodon::CLI
      progress.total = progress.progress
      progress.finish

      say("Upgraded storage schema of #{records} records#{dry_run}", :green, true)
      say("Upgraded storage schema of #{records} records#{dry_run_mode_suffix}", :green, true)
    end

    private

M package.json => package.json +22 -22
@@ 2,7 2,7 @@
  "name": "@mastodon/mastodon",
  "license": "AGPL-3.0-or-later",
  "engines": {
    "node": ">=14"
    "node": ">=16"
  },
  "scripts": {
    "postversion": "git push --tags",


@@ 26,14 26,14 @@
  },
  "private": true,
  "dependencies": {
    "@babel/core": "^7.21.8",
    "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
    "@babel/core": "^7.22.1",
    "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.3",
    "@babel/plugin-transform-react-inline-elements": "^7.21.0",
    "@babel/plugin-transform-runtime": "^7.21.4",
    "@babel/preset-env": "^7.21.5",
    "@babel/preset-react": "^7.18.6",
    "@babel/plugin-transform-runtime": "^7.22.4",
    "@babel/preset-env": "^7.22.4",
    "@babel/preset-react": "^7.22.3",
    "@babel/preset-typescript": "^7.21.5",
    "@babel/runtime": "^7.21.5",
    "@babel/runtime": "^7.22.3",
    "@gamestdio/websocket": "^0.3.2",
    "@github/webauthn-json": "^2.1.1",
    "@rails/ujs": "^6.1.7",


@@ 76,7 76,7 @@
    "intl-messageformat": "^2.2.0",
    "intl-relativeformat": "^6.4.3",
    "js-yaml": "^4.1.0",
    "jsdom": "^22.0.0",
    "jsdom": "^22.1.0",
    "lodash": "^4.17.21",
    "mark-loader": "^0.1.6",
    "marky": "^1.2.5",


@@ 86,7 86,7 @@
    "path-complete-extname": "^1.0.0",
    "pg": "^8.5.0",
    "pg-connection-string": "^2.6.0",
    "postcss": "^8.4.23",
    "postcss": "^8.4.24",
    "postcss-loader": "^4.3.0",
    "prop-types": "^15.8.1",
    "punycode": "^2.3.0",


@@ 133,18 133,18 @@
    "webpack-cli": "^3.3.12",
    "webpack-merge": "^5.9.0",
    "wicg-inert": "^3.1.2",
    "workbox-expiration": "^6.5.4",
    "workbox-precaching": "^6.5.4",
    "workbox-routing": "^6.5.4",
    "workbox-strategies": "^6.5.4",
    "workbox-webpack-plugin": "^6.5.4",
    "workbox-window": "^6.5.4",
    "workbox-expiration": "^6.6.0",
    "workbox-precaching": "^6.6.0",
    "workbox-routing": "^6.6.0",
    "workbox-strategies": "^6.6.0",
    "workbox-webpack-plugin": "^6.6.0",
    "workbox-window": "^6.6.0",
    "ws": "^8.12.1"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^14.0.0",
    "@types/babel__core": "^7.20.0",
    "@types/babel__core": "^7.20.1",
    "@types/emoji-mart": "^3.0.9",
    "@types/escape-html": "^1.0.2",
    "@types/express": "^4.17.17",


@@ 152,18 152,18 @@
    "@types/intl": "^1.2.0",
    "@types/jest": "^29.5.1",
    "@types/js-yaml": "^4.0.5",
    "@types/lodash": "^4.14.194",
    "@types/lodash": "^4.14.195",
    "@types/npmlog": "^4.1.4",
    "@types/object-assign": "^4.0.30",
    "@types/pg": "^8.6.6",
    "@types/prop-types": "^15.7.5",
    "@types/punycode": "^2.1.0",
    "@types/react": "^18.0.26",
    "@types/react": "^18.2.7",
    "@types/react-dom": "^18.2.4",
    "@types/react-helmet": "^6.1.6",
    "@types/react-immutable-proptypes": "^2.1.0",
    "@types/react-intl": "2.3.18",
    "@types/react-motion": "^0.0.33",
    "@types/react-motion": "^0.0.34",
    "@types/react-overlays": "^3.1.0",
    "@types/react-router-dom": "^5.3.3",
    "@types/react-select": "^5.0.1",


@@ 177,15 177,15 @@
    "@types/uuid": "^9.0.0",
    "@types/webpack": "^4.41.33",
    "@types/yargs": "^17.0.24",
    "@typescript-eslint/eslint-plugin": "^5.59.7",
    "@typescript-eslint/parser": "^5.59.7",
    "@typescript-eslint/eslint-plugin": "^5.59.8",
    "@typescript-eslint/parser": "^5.59.8",
    "babel-jest": "^29.5.0",
    "eslint": "^8.41.0",
    "eslint-config-prettier": "^8.8.0",
    "eslint-import-resolver-typescript": "^3.5.5",
    "eslint-plugin-formatjs": "^4.10.1",
    "eslint-plugin-import": "~2.27.5",
    "eslint-plugin-jsdoc": "^44.2.5",
    "eslint-plugin-jsdoc": "^45.0.0",
    "eslint-plugin-jsx-a11y": "~6.7.1",
    "eslint-plugin-prettier": "^4.2.1",
    "eslint-plugin-promise": "~6.1.1",

M spec/controllers/admin/ip_blocks_controller_spec.rb => spec/controllers/admin/ip_blocks_controller_spec.rb +33 -0
@@ 18,4 18,37 @@ describe Admin::IpBlocksController do
      expect(response).to have_http_status(:success)
    end
  end

  describe 'GET #new' do
    it 'returns http success and renders view' do
      get :new

      expect(response).to have_http_status(:success)
      expect(response).to render_template(:new)
    end
  end

  describe 'POST #create' do
    context 'with valid data' do
      it 'creates a new ip block and redirects' do
        expect do
          post :create, params: { ip_block: { ip: '1.1.1.1', severity: 'no_access', expires_in: 1.day.to_i.to_s } }
        end.to change(IpBlock, :count).by(1)

        expect(response).to redirect_to(admin_ip_blocks_path)
        expect(flash.notice).to match(I18n.t('admin.ip_blocks.created_msg'))
      end
    end

    context 'with invalid data' do
      it 'does not create new a ip block and renders new' do
        expect do
          post :create, params: { ip_block: { ip: '1.1.1.1' } }
        end.to_not change(IpBlock, :count)

        expect(response).to have_http_status(:success)
        expect(response).to render_template(:new)
      end
    end
  end
end

M spec/controllers/admin/relays_controller_spec.rb => spec/controllers/admin/relays_controller_spec.rb +38 -0
@@ 18,4 18,42 @@ describe Admin::RelaysController do
      expect(response).to have_http_status(:success)
    end
  end

  describe 'GET #new' do
    it 'returns http success and renders view' do
      get :new

      expect(response).to have_http_status(:success)
      expect(response).to render_template(:new)
    end
  end

  describe 'POST #create' do
    context 'with valid data' do
      let(:inbox_url) { 'https://example.com/inbox' }

      before do
        stub_request(:post, inbox_url).to_return(status: 200)
      end

      it 'creates a new relay and redirects' do
        expect do
          post :create, params: { relay: { inbox_url: inbox_url } }
        end.to change(Relay, :count).by(1)

        expect(response).to redirect_to(admin_relays_path)
      end
    end

    context 'with invalid data' do
      it 'does not create new a relay and renders new' do
        expect do
          post :create, params: { relay: { inbox_url: 'invalid' } }
        end.to_not change(Relay, :count)

        expect(response).to have_http_status(:success)
        expect(response).to render_template(:new)
      end
    end
  end
end

M spec/controllers/admin/rules_controller_spec.rb => spec/controllers/admin/rules_controller_spec.rb +64 -0
@@ 18,4 18,68 @@ describe Admin::RulesController do
      expect(response).to have_http_status(:success)
    end
  end

  describe 'GET #edit' do
    let(:rule) { Fabricate(:rule) }

    it 'returns http success and renders edit' do
      get :edit, params: { id: rule.id }

      expect(response).to have_http_status(:success)
      expect(response).to render_template(:edit)
    end
  end

  describe 'POST #create' do
    context 'with valid data' do
      it 'creates a new rule and redirects' do
        expect do
          post :create, params: { rule: { text: 'The rule text.' } }
        end.to change(Rule, :count).by(1)

        expect(response).to redirect_to(admin_rules_path)
      end
    end

    context 'with invalid data' do
      it 'does creates a new rule and renders index' do
        expect do
          post :create, params: { rule: { text: '' } }
        end.to_not change(Rule, :count)

        expect(response).to render_template(:index)
      end
    end
  end

  describe 'PUT #update' do
    let(:rule) { Fabricate(:rule, text: 'Original text') }

    context 'with valid data' do
      it 'updates the rule and redirects' do
        put :update, params: { id: rule.id, rule: { text: 'Updated text.' } }

        expect(response).to redirect_to(admin_rules_path)
      end
    end

    context 'with invalid data' do
      it 'does not update the rule and renders index' do
        put :update, params: { id: rule.id, rule: { text: '' } }

        expect(response).to render_template(:edit)
      end
    end
  end

  describe 'DELETE #destroy' do
    let!(:rule) { Fabricate(:rule) }

    it 'destroys the rule and redirects' do
      delete :destroy, params: { id: rule.id }

      expect(rule.reload).to be_discarded
      expect(response).to redirect_to(admin_rules_path)
    end
  end
end

M spec/controllers/admin/webhooks_controller_spec.rb => spec/controllers/admin/webhooks_controller_spec.rb +78 -0
@@ 18,4 18,82 @@ describe Admin::WebhooksController do
      expect(response).to have_http_status(:success)
    end
  end

  describe 'GET #new' do
    it 'returns http success and renders view' do
      get :new

      expect(response).to have_http_status(:success)
      expect(response).to render_template(:new)
    end
  end

  describe 'POST #create' do
    it 'creates a new webhook record with valid data' do
      expect do
        post :create, params: { webhook: { url: 'https://example.com/hook', events: ['account.approved'] } }
      end.to change(Webhook, :count).by(1)

      expect(response).to be_redirect
    end

    it 'does not create a new webhook record with invalid data' do
      expect do
        post :create, params: { webhook: { url: 'https://example.com/hook', events: [] } }
      end.to_not change(Webhook, :count)

      expect(response).to have_http_status(:success)
      expect(response).to render_template(:new)
    end
  end

  context 'with an existing record' do
    let!(:webhook) { Fabricate :webhook }

    describe 'GET #show' do
      it 'returns http success and renders view' do
        get :show, params: { id: webhook.id }

        expect(response).to have_http_status(:success)
        expect(response).to render_template(:show)
      end
    end

    describe 'GET #edit' do
      it 'returns http success and renders view' do
        get :edit, params: { id: webhook.id }

        expect(response).to have_http_status(:success)
        expect(response).to render_template(:edit)
      end
    end

    describe 'PUT #update' do
      it 'updates the record with valid data' do
        put :update, params: { id: webhook.id, webhook: { url: 'https://example.com/new/location' } }

        expect(webhook.reload.url).to match(%r{new/location})
        expect(response).to redirect_to(admin_webhook_path(webhook))
      end

      it 'does not update the record with invalid data' do
        expect do
          put :update, params: { id: webhook.id, webhook: { url: '' } }
        end.to_not change(webhook, :url)

        expect(response).to have_http_status(:success)
        expect(response).to render_template(:show)
      end
    end

    describe 'DELETE #destroy' do
      it 'destroys the record' do
        expect do
          delete :destroy, params: { id: webhook.id }
        end.to change(Webhook, :count).by(-1)

        expect(response).to redirect_to(admin_webhooks_path)
      end
    end
  end
end

M spec/lib/mastodon/cli/accounts_spec.rb => spec/lib/mastodon/cli/accounts_spec.rb +653 -0
@@ 4,9 4,662 @@ require 'rails_helper'
require 'mastodon/cli/accounts'

describe Mastodon::CLI::Accounts do
  let(:cli) { described_class.new }

  describe '.exit_on_failure?' do
    it 'returns true' do
      expect(described_class.exit_on_failure?).to be true
    end
  end

  describe '#create' do
    shared_examples 'a new user with given email address and username' do
      it 'creates a new user with the specified email address' do
        cli.invoke(:create, arguments, options)

        expect(User.find_by(email: options[:email])).to be_present
      end

      it 'creates a new local account with the specified username' do
        cli.invoke(:create, arguments, options)

        expect(Account.find_local('tootctl_username')).to be_present
      end

      it 'returns "OK" and newly generated password' do
        allow(SecureRandom).to receive(:hex).and_return('test_password')

        expect { cli.invoke(:create, arguments, options) }.to output(
          a_string_including("OK\nNew password: test_password")
        ).to_stdout
      end
    end

    context 'when required USERNAME and --email are provided' do
      let(:arguments) { ['tootctl_username'] }

      context 'with USERNAME and --email only' do
        let(:options) { { email: 'tootctl@example.com' } }

        it_behaves_like 'a new user with given email address and username'

        context 'with invalid --email value' do
          let(:options) { { email: 'invalid' } }

          it 'exits with an error message' do
            expect { cli.invoke(:create, arguments, options) }.to output(
              a_string_including('Failure/Error: email')
            ).to_stdout
              .and raise_error(SystemExit)
          end
        end
      end

      context 'with --confirmed option' do
        let(:options) { { email: 'tootctl@example.com', confirmed: true } }

        it_behaves_like 'a new user with given email address and username'

        it 'creates a new user with confirmed status' do
          cli.invoke(:create, arguments, options)

          user = User.find_by(email: options[:email])

          expect(user.confirmed?).to be(true)
        end
      end

      context 'with --approve option' do
        let(:options) { { email: 'tootctl@example.com', approve: true } }

        before do
          Form::AdminSettings.new(registrations_mode: 'approved').save
        end

        it_behaves_like 'a new user with given email address and username'

        it 'creates a new user with approved status' do
          cli.invoke(:create, arguments, options)

          user = User.find_by(email: options[:email])

          expect(user.approved?).to be(true)
        end
      end

      context 'with --role option' do
        context 'when role exists' do
          let(:default_role) { Fabricate(:user_role) }
          let(:options) { { email: 'tootctl@example.com', role: default_role.name } }

          it_behaves_like 'a new user with given email address and username'

          it 'creates a new user and assigns the specified role' do
            cli.invoke(:create, arguments, options)

            role = User.find_by(email: options[:email])&.role

            expect(role.name).to eq(default_role.name)
          end
        end

        context 'when role does not exist' do
          let(:options) { { email: 'tootctl@example.com', role: '404' } }

          it 'exits with an error message indicating the role name was not found' do
            expect { cli.invoke(:create, arguments, options) }.to output(
              a_string_including('Cannot find user role with that name')
            ).to_stdout
              .and raise_error(SystemExit)
          end
        end
      end

      context 'with --reattach option' do
        context "when account's user is present" do
          let(:options) { { email: 'tootctl_new@example.com', reattach: true } }
          let(:user) { Fabricate.build(:user, email: 'tootctl@example.com') }

          before do
            Fabricate(:account, username: 'tootctl_username', user: user)
          end

          it 'returns an error message indicating the username is already taken' do
            expect { cli.invoke(:create, arguments, options) }.to output(
              a_string_including("The chosen username is currently in use\nUse --force to reattach it anyway and delete the other user")
            ).to_stdout
          end

          context 'with --force option' do
            let(:options) { { email: 'tootctl_new@example.com', reattach: true, force: true } }

            it 'reattaches the account to the new user and deletes the previous user' do
              cli.invoke(:create, arguments, options)

              user = Account.find_local('tootctl_username')&.user

              expect(user.email).to eq(options[:email])
            end
          end
        end

        context "when account's user is not present" do
          let(:options) { { email: 'tootctl@example.com', reattach: true } }

          before do
            Fabricate(:account, username: 'tootctl_username', user: nil)
          end

          it_behaves_like 'a new user with given email address and username'
        end
      end
    end

    context 'when required --email option is not provided' do
      let(:arguments) { ['tootctl_username'] }

      it 'raises a required argument missing error (Thor::RequiredArgumentMissingError)' do
        expect { cli.invoke(:create, arguments) }
          .to raise_error(Thor::RequiredArgumentMissingError)
      end
    end
  end

  describe '#modify' do
    context 'when the given username is not found' do
      let(:arguments) { ['non_existent_username'] }

      it 'exits with an error message indicating the user was not found' do
        expect { cli.invoke(:modify, arguments) }.to output(
          a_string_including('No user with such username')
        ).to_stdout
          .and raise_error(SystemExit)
      end
    end

    context 'when the given username is found' do
      let(:user) { Fabricate(:user) }
      let(:arguments) { [user.account.username] }

      context 'when no option is provided' do
        it 'returns a successful message' do
          expect { cli.invoke(:modify, arguments) }.to output(
            a_string_including('OK')
          ).to_stdout
        end

        it 'does not modify the user' do
          cli.invoke(:modify, arguments)

          expect(user).to eq(user.reload)
        end
      end

      context 'with --role option' do
        context 'when the given role is not found' do
          let(:options) { { role: '404' } }

          it 'exits with an error message indicating the role was not found' do
            expect { cli.invoke(:modify, arguments, options) }.to output(
              a_string_including('Cannot find user role with that name')
            ).to_stdout
              .and raise_error(SystemExit)
          end
        end

        context 'when the given role is found' do
          let(:default_role) { Fabricate(:user_role) }
          let(:options) { { role: default_role.name } }

          it "updates the user's role to the specified role" do
            cli.invoke(:modify, arguments, options)

            role = user.reload.role

            expect(role.name).to eq(default_role.name)
          end
        end
      end

      context 'with --remove-role option' do
        let(:options) { { remove_role: true } }
        let(:role) { Fabricate(:user_role) }
        let(:user) { Fabricate(:user, role: role) }

        it "removes the user's role successfully" do
          cli.invoke(:modify, arguments, options)

          role = user.reload.role

          expect(role.name).to be_empty
        end
      end

      context 'with --email option' do
        let(:user) { Fabricate(:user, email: 'old_email@email.com') }
        let(:options) { { email: 'new_email@email.com' } }

        it "sets the user's unconfirmed email to the provided email address" do
          cli.invoke(:modify, arguments, options)

          expect(user.reload.unconfirmed_email).to eq(options[:email])
        end

        it "does not update the user's original email address" do
          cli.invoke(:modify, arguments, options)

          expect(user.reload.email).to eq('old_email@email.com')
        end

        context 'with --confirm option' do
          let(:user) { Fabricate(:user, email: 'old_email@email.com', confirmed_at: nil) }
          let(:options) { { email: 'new_email@email.com', confirm: true } }

          it "updates the user's email address to the provided email" do
            cli.invoke(:modify, arguments, options)

            expect(user.reload.email).to eq(options[:email])
          end

          it "sets the user's email address as confirmed" do
            cli.invoke(:modify, arguments, options)

            expect(user.reload.confirmed?).to be(true)
          end
        end
      end

      context 'with --confirm option' do
        let(:user) { Fabricate(:user, confirmed_at: nil) }
        let(:options) { { confirm: true } }

        it "confirms the user's email address" do
          cli.invoke(:modify, arguments, options)

          expect(user.reload.confirmed?).to be(true)
        end
      end

      context 'with --approve option' do
        let(:user) { Fabricate(:user, approved: false) }
        let(:options) { { approve: true } }

        before do
          Form::AdminSettings.new(registrations_mode: 'approved').save
        end

        it 'approves the user' do
          expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.approved }.from(false).to(true)
        end
      end

      context 'with --disable option' do
        let(:user) { Fabricate(:user, disabled: false) }
        let(:options) { { disable: true } }

        it 'disables the user' do
          expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.disabled }.from(false).to(true)
        end
      end

      context 'with --enable option' do
        let(:user) { Fabricate(:user, disabled: true) }
        let(:options) { { enable: true } }

        it 'enables the user' do
          expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.disabled }.from(true).to(false)
        end
      end

      context 'with --reset-password option' do
        let(:options) { { reset_password: true } }

        it 'returns a new password for the user' do
          allow(SecureRandom).to receive(:hex).and_return('new_password')

          expect { cli.invoke(:modify, arguments, options) }.to output(
            a_string_including('new_password')
          ).to_stdout
        end
      end

      context 'with --disable-2fa option' do
        let(:user) { Fabricate(:user, otp_required_for_login: true) }
        let(:options) { { disable_2fa: true } }

        it 'disables the two-factor authentication for the user' do
          expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.otp_required_for_login }.from(true).to(false)
        end
      end

      context 'when provided data is invalid' do
        let(:user) { Fabricate(:user) }
        let(:options) { { email: 'invalid' } }

        it 'exits with an error message' do
          expect { cli.invoke(:modify, arguments, options) }.to output(
            a_string_including('Failure/Error: email')
          ).to_stdout
            .and raise_error(SystemExit)
        end
      end
    end
  end

  describe '#delete' do
    let(:account) { Fabricate(:account) }
    let(:arguments) { [account.username] }
    let(:options) { { email: account.user.email } }
    let(:delete_account_service) { instance_double(DeleteAccountService) }

    before do
      allow(DeleteAccountService).to receive(:new).and_return(delete_account_service)
      allow(delete_account_service).to receive(:call)
    end

    context 'when both username and --email are provided' do
      it 'exits with an error message indicating that only one should be used' do
        expect { cli.invoke(:delete, arguments, options) }.to output(
          a_string_including('Use username or --email, not both')
        ).to_stdout
          .and raise_error(SystemExit)
      end
    end

    context 'when neither username nor --email are provided' do
      it 'exits with an error message indicating that no username was provided' do
        expect { cli.invoke(:delete) }.to output(
          a_string_including('No username provided')
        ).to_stdout
          .and raise_error(SystemExit)
      end
    end

    context 'when username is provided' do
      it 'deletes the specified user successfully' do
        cli.invoke(:delete, arguments)

        expect(delete_account_service).to have_received(:call).with(account, reserve_email: false).once
      end

      context 'with --dry-run option' do
        let(:options) { { dry_run: true } }

        it 'does not delete the specified user' do
          cli.invoke(:delete, arguments, options)

          expect(delete_account_service).to_not have_received(:call).with(account, reserve_email: false)
        end

        it 'outputs a successful message in dry run mode' do
          expect { cli.invoke(:delete, arguments, options) }.to output(
            a_string_including('OK (DRY RUN)')
          ).to_stdout
        end
      end

      context 'when the given username is not found' do
        let(:arguments) { ['non_existent_username'] }

        it 'exits with an error message indicating that no user was found' do
          expect { cli.invoke(:delete, arguments) }.to output(
            a_string_including('No user with such username')
          ).to_stdout
            .and raise_error(SystemExit)
        end
      end
    end

    context 'when --email is provided' do
      it 'deletes the specified user successfully' do
        cli.invoke(:delete, nil, options)

        expect(delete_account_service).to have_received(:call).with(account, reserve_email: false).once
      end

      context 'with --dry-run option' do
        let(:options) { { email: account.user.email, dry_run: true } }

        it 'does not delete the user' do
          cli.invoke(:delete, nil, options)

          expect(delete_account_service).to_not have_received(:call).with(account, reserve_email: false)
        end

        it 'outputs a successful message in dry run mode' do
          expect { cli.invoke(:delete, nil, options) }.to output(
            a_string_including('OK (DRY RUN)')
          ).to_stdout
        end
      end

      context 'when the given email address is not found' do
        let(:options) { { email: '404@example.com' } }

        it 'exits with an error message indicating that no user was found' do
          expect { cli.invoke(:delete, nil, options) }.to output(
            a_string_including('No user with such email')
          ).to_stdout
            .and raise_error(SystemExit)
        end
      end
    end
  end

  describe '#approve' do
    let(:total_users) { 10 }

    before do
      Form::AdminSettings.new(registrations_mode: 'approved').save
      Fabricate.times(total_users, :user)
    end

    context 'with --all option' do
      it 'approves all pending registrations' do
        cli.invoke(:approve, nil, all: true)

        expect(User.pluck(:approved).all?(true)).to be(true)
      end
    end

    context 'with --number option' do
      context 'when the number is positive' do
        let(:options) { { number: 3 } }

        it 'approves the earliest n pending registrations' do
          cli.invoke(:approve, nil, options)

          n_earliest_pending_registrations = User.order(created_at: :asc).first(options[:number])

          expect(n_earliest_pending_registrations.all?(&:approved?)).to be(true)
        end

        it 'does not approve the remaining pending registrations' do
          cli.invoke(:approve, nil, options)

          pending_registrations = User.order(created_at: :asc).last(total_users - options[:number])

          expect(pending_registrations.all?(&:approved?)).to be(false)
        end
      end

      context 'when the number is negative' do
        it 'exits with an error message indicating that the number must be positive' do
          expect { cli.invoke(:approve, nil, number: -1) }.to output(
            a_string_including('Number must be positive')
          ).to_stdout
            .and raise_error(SystemExit)
        end
      end

      context 'when the given number is greater than the number of users' do
        let(:options) { { number: total_users * 2 } }

        it 'approves all users' do
          cli.invoke(:approve, nil, options)

          expect(User.pluck(:approved).all?(true)).to be(true)
        end

        it 'does not raise any error' do
          expect { cli.invoke(:approve, nil, options) }
            .to_not raise_error
        end
      end
    end

    context 'with username argument' do
      context 'when the given username is found' do
        let(:user) { User.last }
        let(:arguments) { [user.account.username] }

        it 'approves the specified user successfully' do
          cli.invoke(:approve, arguments)

          expect(user.reload.approved?).to be(true)
        end
      end

      context 'when the given username is not found' do
        let(:arguments) { ['non_existent_username'] }

        it 'exits with an error message indicating that no such account was found' do
          expect { cli.invoke(:approve, arguments) }.to output(
            a_string_including('No such account')
          ).to_stdout
            .and raise_error(SystemExit)
        end
      end
    end
  end

  describe '#follow' do
    context 'when the given username is not found' do
      let(:arguments) { ['non_existent_username'] }

      it 'exits with an error message indicating that no account with the given username was found' do
        expect { cli.invoke(:follow, arguments) }.to output(
          a_string_including('No such account')
        ).to_stdout
          .and raise_error(SystemExit)
      end
    end

    context 'when the given username is found' do
      let!(:target_account)   { Fabricate(:account) }
      let!(:follower_bob)     { Fabricate(:account, username: 'bob') }
      let!(:follower_rony)    { Fabricate(:account, username: 'rony') }
      let!(:follower_charles) { Fabricate(:account, username: 'charles') }
      let(:follow_service)    { instance_double(FollowService, call: nil) }
      let(:scope)             { Account.local.without_suspended }

      before do
        allow(cli).to receive(:parallelize_with_progress).and_yield(follower_bob)
                                                         .and_yield(follower_rony)
                                                         .and_yield(follower_charles)
                                                         .and_return([3, nil])
        allow(FollowService).to receive(:new).and_return(follow_service)
      end

      it 'makes all local accounts follow the target account' do
        cli.follow(target_account.username)

        expect(cli).to have_received(:parallelize_with_progress).with(scope).once
        expect(follow_service).to have_received(:call).with(follower_bob, target_account, any_args).once
        expect(follow_service).to have_received(:call).with(follower_rony, target_account, any_args).once
        expect(follow_service).to have_received(:call).with(follower_charles, target_account, any_args).once
      end

      it 'displays a successful message' do
        expect { cli.follow(target_account.username) }.to output(
          a_string_including('OK, followed target from 3 accounts')
        ).to_stdout
      end
    end
  end

  describe '#unfollow' do
    context 'when the given username is not found' do
      let(:arguments) { ['non_existent_username'] }

      it 'exits with an error message indicating that no account with the given username was found' do
        expect { cli.invoke(:unfollow, arguments) }.to output(
          a_string_including('No such account')
        ).to_stdout
          .and raise_error(SystemExit)
      end
    end

    context 'when the given username is found' do
      let!(:target_account)  { Fabricate(:account) }
      let!(:follower_chris)  { Fabricate(:account, username: 'chris') }
      let!(:follower_rambo)  { Fabricate(:account, username: 'rambo') }
      let!(:follower_ana)    { Fabricate(:account, username: 'ana') }
      let(:unfollow_service) { instance_double(UnfollowService, call: nil) }
      let(:scope)            { target_account.followers.local }

      before do
        accounts = [follower_chris, follower_rambo, follower_ana]
        accounts.each { |account| target_account.follow!(account) }
        allow(cli).to receive(:parallelize_with_progress).and_yield(follower_chris)
                                                         .and_yield(follower_rambo)
                                                         .and_yield(follower_ana)
                                                         .and_return([3, nil])
        allow(UnfollowService).to receive(:new).and_return(unfollow_service)
      end

      it 'makes all local accounts unfollow the target account' do
        cli.unfollow(target_account.username)

        expect(cli).to have_received(:parallelize_with_progress).with(scope).once
        expect(unfollow_service).to have_received(:call).with(follower_chris, target_account).once
        expect(unfollow_service).to have_received(:call).with(follower_rambo, target_account).once
        expect(unfollow_service).to have_received(:call).with(follower_ana, target_account).once
      end

      it 'displays a successful message' do
        expect { cli.unfollow(target_account.username) }.to output(
          a_string_including('OK, unfollowed target from 3 accounts')
        ).to_stdout
      end
    end
  end

  describe '#backup' do
    context 'when the given username is not found' do
      let(:arguments) { ['non_existent_username'] }

      it 'exits with an error message indicating that there is no such account' do
        expect { cli.invoke(:backup, arguments) }.to output(
          a_string_including('No user with such username')
        ).to_stdout
          .and raise_error(SystemExit)
      end
    end

    context 'when the given username is found' do
      let(:account) { Fabricate(:account) }
      let(:user) { account.user }
      let(:arguments) { [account.username] }

      it 'creates a new backup for the specified user' do
        expect { cli.invoke(:backup, arguments) }.to change { user.backups.count }.by(1)
      end

      it 'creates a backup job' do
        allow(BackupWorker).to receive(:perform_async)

        cli.invoke(:backup, arguments)
        latest_backup = user.backups.last

        expect(BackupWorker).to have_received(:perform_async).with(latest_backup.id).once
      end

      it 'displays a successful message' do
        expect { cli.invoke(:backup, arguments) }.to output(
          a_string_including('OK')
        ).to_stdout
      end
    end
  end
end

M spec/rails_helper.rb => spec/rails_helper.rb +12 -0
@@ 62,6 62,10 @@ RSpec.configure do |config|
  config.infer_spec_type_from_file_location!
  config.filter_rails_from_backtrace!

  config.define_derived_metadata(file_path: Regexp.new('spec/lib/mastodon/cli')) do |metadata|
    metadata[:type] = :cli
  end

  config.include Devise::Test::ControllerHelpers, type: :controller
  config.include Devise::Test::ControllerHelpers, type: :helper
  config.include Devise::Test::ControllerHelpers, type: :view


@@ 73,6 77,10 @@ RSpec.configure do |config|
  config.include Redisable
  config.include SignedRequestHelpers, type: :request

  config.before :each, type: :cli do
    stub_stdout
  end

  config.before :each, type: :feature do
    https = ENV['LOCAL_HTTPS'] == 'true'
    Capybara.app_host = "http#{https ? 's' : ''}://#{ENV.fetch('LOCAL_DOMAIN')}"


@@ 106,6 114,10 @@ def attachment_fixture(name)
  Rails.root.join('spec', 'fixtures', 'files', name).open
end

def stub_stdout
  allow($stdout).to receive(:write)
end

def stub_jsonld_contexts!
  stub_request(:get, 'https://www.w3.org/ns/activitystreams').to_return(request_fixture('json-ld.activitystreams.txt'))
  stub_request(:get, 'https://w3id.org/identity/v1').to_return(request_fixture('json-ld.identity.txt'))

M yarn.lock => yarn.lock +563 -513
@@ 24,45 24,45 @@
    jsonpointer "^5.0.0"
    leven "^3.1.0"

"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4":
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.21.4":
  version "7.21.4"
  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39"
  integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==
  dependencies:
    "@babel/highlight" "^7.18.6"

"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5":
  version "7.21.7"
  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc"
  integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==
"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.22.0", "@babel/compat-data@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.3.tgz#cd502a6a0b6e37d7ad72ce7e71a7160a3ae36f7e"
  integrity sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==

"@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.21.8", "@babel/core@^7.7.2":
  version "7.21.8"
  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.8.tgz#2a8c7f0f53d60100ba4c32470ba0281c92aa9aa4"
  integrity sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==
"@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.22.1", "@babel/core@^7.7.2":
  version "7.22.1"
  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.1.tgz#5de51c5206f4c6f5533562838337a603c1033cfd"
  integrity sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==
  dependencies:
    "@ampproject/remapping" "^2.2.0"
    "@babel/code-frame" "^7.21.4"
    "@babel/generator" "^7.21.5"
    "@babel/helper-compilation-targets" "^7.21.5"
    "@babel/helper-module-transforms" "^7.21.5"
    "@babel/helpers" "^7.21.5"
    "@babel/parser" "^7.21.8"
    "@babel/template" "^7.20.7"
    "@babel/traverse" "^7.21.5"
    "@babel/types" "^7.21.5"
    "@babel/generator" "^7.22.0"
    "@babel/helper-compilation-targets" "^7.22.1"
    "@babel/helper-module-transforms" "^7.22.1"
    "@babel/helpers" "^7.22.0"
    "@babel/parser" "^7.22.0"
    "@babel/template" "^7.21.9"
    "@babel/traverse" "^7.22.1"
    "@babel/types" "^7.22.0"
    convert-source-map "^1.7.0"
    debug "^4.1.0"
    gensync "^1.0.0-beta.2"
    json5 "^2.2.2"
    semver "^6.3.0"

"@babel/generator@^7.21.5", "@babel/generator@^7.7.2":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f"
  integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==
"@babel/generator@^7.22.0", "@babel/generator@^7.22.3", "@babel/generator@^7.7.2":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.3.tgz#0ff675d2edb93d7596c5f6728b52615cfc0df01e"
  integrity sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==
  dependencies:
    "@babel/types" "^7.21.5"
    "@babel/types" "^7.22.3"
    "@jridgewell/gen-mapping" "^0.3.2"
    "@jridgewell/trace-mapping" "^0.3.17"
    jsesc "^2.5.1"


@@ 90,21 90,21 @@
    "@babel/helper-annotate-as-pure" "^7.18.6"
    "@babel/types" "^7.19.0"

"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.5":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366"
  integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==
"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.1":
  version "7.22.1"
  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz#bfcd6b7321ffebe33290d68550e2c9d7eb7c7a58"
  integrity sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==
  dependencies:
    "@babel/compat-data" "^7.21.5"
    "@babel/compat-data" "^7.22.0"
    "@babel/helper-validator-option" "^7.21.0"
    browserslist "^4.21.3"
    lru-cache "^5.1.1"
    semver "^6.3.0"

"@babel/helper-create-class-features-plugin@^7.18.6":
  version "7.21.0"
  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz#64f49ecb0020532f19b1d014b03bccaa1ab85fb9"
  integrity sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==
"@babel/helper-create-class-features-plugin@^7.21.0":
  version "7.21.4"
  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz#3a017163dc3c2ba7deb9a7950849a9586ea24c18"
  integrity sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q==
  dependencies:
    "@babel/helper-annotate-as-pure" "^7.18.6"
    "@babel/helper-environment-visitor" "^7.18.9"


@@ 115,19 115,20 @@
    "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
    "@babel/helper-split-export-declaration" "^7.18.6"

"@babel/helper-create-class-features-plugin@^7.21.0":
  version "7.21.4"
  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz#3a017163dc3c2ba7deb9a7950849a9586ea24c18"
  integrity sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q==
"@babel/helper-create-class-features-plugin@^7.22.1":
  version "7.22.1"
  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.1.tgz#ae3de70586cc757082ae3eba57240d42f468c41b"
  integrity sha512-SowrZ9BWzYFgzUMwUmowbPSGu6CXL5MSuuCkG3bejahSpSymioPmuLdhPxNOc9MjuNGjy7M/HaXvJ8G82Lywlw==
  dependencies:
    "@babel/helper-annotate-as-pure" "^7.18.6"
    "@babel/helper-environment-visitor" "^7.18.9"
    "@babel/helper-environment-visitor" "^7.22.1"
    "@babel/helper-function-name" "^7.21.0"
    "@babel/helper-member-expression-to-functions" "^7.21.0"
    "@babel/helper-member-expression-to-functions" "^7.22.0"
    "@babel/helper-optimise-call-expression" "^7.18.6"
    "@babel/helper-replace-supers" "^7.20.7"
    "@babel/helper-replace-supers" "^7.22.1"
    "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
    "@babel/helper-split-export-declaration" "^7.18.6"
    semver "^6.3.0"

"@babel/helper-create-regexp-features-plugin@^7.18.6":
  version "7.19.0"


@@ 137,18 138,19 @@
    "@babel/helper-annotate-as-pure" "^7.18.6"
    regexpu-core "^5.1.0"

"@babel/helper-create-regexp-features-plugin@^7.20.5":
  version "7.21.4"
  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz#40411a8ab134258ad2cf3a3d987ec6aa0723cee5"
  integrity sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA==
"@babel/helper-create-regexp-features-plugin@^7.22.1":
  version "7.22.1"
  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.1.tgz#a7ed9a8488b45b467fca353cd1a44dc5f0cf5c70"
  integrity sha512-WWjdnfR3LPIe+0EY8td7WmjhytxXtjKAEpnAxun/hkNiyOaPlvGK+NZaBFIdi9ndYV3Gav7BpFvtUwnaJlwi1w==
  dependencies:
    "@babel/helper-annotate-as-pure" "^7.18.6"
    regexpu-core "^5.3.1"
    semver "^6.3.0"

"@babel/helper-define-polyfill-provider@^0.3.3":
  version "0.3.3"
  resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a"
  integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==
"@babel/helper-define-polyfill-provider@^0.4.0":
  version "0.4.0"
  resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz#487053f103110f25b9755c5980e031e93ced24d8"
  integrity sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==
  dependencies:
    "@babel/helper-compilation-targets" "^7.17.7"
    "@babel/helper-plugin-utils" "^7.16.7"


@@ 162,10 164,10 @@
  resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
  integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==

"@babel/helper-environment-visitor@^7.21.5":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba"
  integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==
"@babel/helper-environment-visitor@^7.22.1":
  version "7.22.1"
  resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz#ac3a56dbada59ed969d712cf527bd8271fe3eba8"
  integrity sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==

"@babel/helper-explode-assignable-expression@^7.18.6":
  version "7.18.6"


@@ 211,6 213,13 @@
  dependencies:
    "@babel/types" "^7.21.0"

"@babel/helper-member-expression-to-functions@^7.22.0":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.3.tgz#4b77a12c1b4b8e9e28736ed47d8b91f00976911f"
  integrity sha512-Gl7sK04b/2WOb6OPVeNy9eFKeD3L6++CzL3ykPOWqTn08xgYYK0wz4TUh2feIImDXxcVW3/9WQ1NMKY66/jfZA==
  dependencies:
    "@babel/types" "^7.22.3"

"@babel/helper-module-imports@^7.0.0-beta.49", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4":
  version "7.21.4"
  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af"


@@ 218,19 227,19 @@
  dependencies:
    "@babel/types" "^7.21.4"

"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420"
  integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==
"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5", "@babel/helper-module-transforms@^7.22.1":
  version "7.22.1"
  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz#e0cad47fedcf3cae83c11021696376e2d5a50c63"
  integrity sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==
  dependencies:
    "@babel/helper-environment-visitor" "^7.21.5"
    "@babel/helper-environment-visitor" "^7.22.1"
    "@babel/helper-module-imports" "^7.21.4"
    "@babel/helper-simple-access" "^7.21.5"
    "@babel/helper-split-export-declaration" "^7.18.6"
    "@babel/helper-validator-identifier" "^7.19.1"
    "@babel/template" "^7.20.7"
    "@babel/traverse" "^7.21.5"
    "@babel/types" "^7.21.5"
    "@babel/template" "^7.21.9"
    "@babel/traverse" "^7.22.1"
    "@babel/types" "^7.22.0"

"@babel/helper-optimise-call-expression@^7.18.6":
  version "7.18.6"


@@ 266,6 275,18 @@
    "@babel/traverse" "^7.20.7"
    "@babel/types" "^7.20.7"

"@babel/helper-replace-supers@^7.22.1":
  version "7.22.1"
  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.1.tgz#38cf6e56f7dc614af63a21b45565dd623f0fdc95"
  integrity sha512-ut4qrkE4AuSfrwHSps51ekR1ZY/ygrP1tp0WFm8oVq6nzc/hvfV/22JylndIbsf2U2M9LOMwiSddr6y+78j+OQ==
  dependencies:
    "@babel/helper-environment-visitor" "^7.22.1"
    "@babel/helper-member-expression-to-functions" "^7.22.0"
    "@babel/helper-optimise-call-expression" "^7.18.6"
    "@babel/template" "^7.21.9"
    "@babel/traverse" "^7.22.1"
    "@babel/types" "^7.22.0"

"@babel/helper-simple-access@^7.21.5":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee"


@@ 297,7 318,7 @@
  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
  integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==

"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0":
"@babel/helper-validator-option@^7.21.0":
  version "7.21.0"
  resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180"
  integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==


@@ 312,14 333,14 @@
    "@babel/traverse" "^7.18.10"
    "@babel/types" "^7.18.10"

"@babel/helpers@^7.21.5":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.5.tgz#5bac66e084d7a4d2d9696bdf0175a93f7fb63c08"
  integrity sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==
"@babel/helpers@^7.22.0":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.3.tgz#53b74351da9684ea2f694bf0877998da26dd830e"
  integrity sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w==
  dependencies:
    "@babel/template" "^7.20.7"
    "@babel/traverse" "^7.21.5"
    "@babel/types" "^7.21.5"
    "@babel/template" "^7.21.9"
    "@babel/traverse" "^7.22.1"
    "@babel/types" "^7.22.3"

"@babel/highlight@^7.18.6":
  version "7.18.6"


@@ 330,10 351,10 @@
    chalk "^2.0.0"
    js-tokens "^4.0.0"

"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8":
  version "7.21.8"
  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
  integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.9", "@babel/parser@^7.22.0", "@babel/parser@^7.22.4":
  version "7.22.4"
  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.4.tgz#a770e98fd785c231af9d93f6459d36770993fb32"
  integrity sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==

"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
  version "7.18.6"


@@ 342,125 363,14 @@
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.6"

"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.20.7":
  version "7.20.7"
  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz#d9c85589258539a22a901033853101a6198d4ef1"
  integrity sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==
  dependencies:
    "@babel/helper-plugin-utils" "^7.20.2"
    "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
    "@babel/plugin-proposal-optional-chaining" "^7.20.7"

"@babel/plugin-proposal-async-generator-functions@^7.20.7":
  version "7.20.7"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326"
  integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==
  dependencies:
    "@babel/helper-environment-visitor" "^7.18.9"
    "@babel/helper-plugin-utils" "^7.20.2"
    "@babel/helper-remap-async-to-generator" "^7.18.9"
    "@babel/plugin-syntax-async-generators" "^7.8.4"

"@babel/plugin-proposal-class-properties@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3"
  integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==
  dependencies:
    "@babel/helper-create-class-features-plugin" "^7.18.6"
    "@babel/helper-plugin-utils" "^7.18.6"

"@babel/plugin-proposal-class-static-block@^7.21.0":
  version "7.21.0"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz#77bdd66fb7b605f3a61302d224bdfacf5547977d"
  integrity sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==
  dependencies:
    "@babel/helper-create-class-features-plugin" "^7.21.0"
    "@babel/helper-plugin-utils" "^7.20.2"
    "@babel/plugin-syntax-class-static-block" "^7.14.5"

"@babel/plugin-proposal-dynamic-import@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94"
  integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.6"
    "@babel/plugin-syntax-dynamic-import" "^7.8.3"

"@babel/plugin-proposal-export-namespace-from@^7.18.9":
  version "7.18.9"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203"
  integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.9"
    "@babel/plugin-syntax-export-namespace-from" "^7.8.3"

"@babel/plugin-proposal-json-strings@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b"
  integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.6"
    "@babel/plugin-syntax-json-strings" "^7.8.3"

"@babel/plugin-proposal-logical-assignment-operators@^7.20.7":
  version "7.20.7"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83"
  integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==
  dependencies:
    "@babel/helper-plugin-utils" "^7.20.2"
    "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"

"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1"
  integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.6"
    "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"

"@babel/plugin-proposal-numeric-separator@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75"
  integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.6"
    "@babel/plugin-syntax-numeric-separator" "^7.10.4"

"@babel/plugin-proposal-object-rest-spread@^7.20.7":
  version "7.20.7"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a"
  integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==
  dependencies:
    "@babel/compat-data" "^7.20.5"
    "@babel/helper-compilation-targets" "^7.20.7"
    "@babel/helper-plugin-utils" "^7.20.2"
    "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
    "@babel/plugin-transform-parameters" "^7.20.7"

"@babel/plugin-proposal-optional-catch-binding@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb"
  integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.3.tgz#a75be1365c0c3188c51399a662168c1c98108659"
  integrity sha512-6r4yRwEnorYByILoDRnEqxtojYKuiIv9FojW2E8GUKo9eWBwbKcd9IiZOZpdyXc64RmyGGyPu3/uAcrz/dq2kQ==
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.6"
    "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"

"@babel/plugin-proposal-optional-chaining@^7.20.7", "@babel/plugin-proposal-optional-chaining@^7.21.0":
  version "7.21.0"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea"
  integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==
  dependencies:
    "@babel/helper-plugin-utils" "^7.20.2"
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
    "@babel/plugin-syntax-optional-chaining" "^7.8.3"

"@babel/plugin-proposal-private-methods@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea"
  integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==
  dependencies:
    "@babel/helper-create-class-features-plugin" "^7.18.6"
    "@babel/helper-plugin-utils" "^7.18.6"
    "@babel/plugin-transform-optional-chaining" "^7.22.3"

"@babel/plugin-proposal-private-property-in-object@^7.21.0":
  version "7.21.0"


@@ 472,7 382,7 @@
    "@babel/helper-plugin-utils" "^7.20.2"
    "@babel/plugin-syntax-private-property-in-object" "^7.14.5"

"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
"@babel/plugin-proposal-unicode-property-regex@^7.4.4":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e"
  integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==


@@ 529,6 439,13 @@
  dependencies:
    "@babel/helper-plugin-utils" "^7.19.0"

"@babel/plugin-syntax-import-attributes@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.3.tgz#d7168f22b9b49a6cc1792cec78e06a18ad2e7b4b"
  integrity sha512-i35jZJv6aO7hxEbIWQ41adVfOzjm9dcYDNeWlBMd8p0ZQRtNUCBrmGwZt+H5lb+oOC9a3svp956KP0oWGA1YsA==
  dependencies:
    "@babel/helper-plugin-utils" "^7.21.5"

"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3":
  version "7.10.4"
  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"


@@ 543,7 460,7 @@
  dependencies:
    "@babel/helper-plugin-utils" "^7.8.0"

"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.18.6", "@babel/plugin-syntax-jsx@^7.21.4", "@babel/plugin-syntax-jsx@^7.7.2":
"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.21.4", "@babel/plugin-syntax-jsx@^7.7.2":
  version "7.21.4"
  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz#f264ed7bf40ffc9ec239edabc17a50c4f5b6fea2"
  integrity sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==


@@ 620,6 537,14 @@
  dependencies:
    "@babel/helper-plugin-utils" "^7.14.5"

"@babel/plugin-syntax-unicode-sets-regex@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357"
  integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==
  dependencies:
    "@babel/helper-create-regexp-features-plugin" "^7.18.6"
    "@babel/helper-plugin-utils" "^7.18.6"

"@babel/plugin-transform-arrow-functions@^7.21.5":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929"


@@ 627,6 552,16 @@
  dependencies:
    "@babel/helper-plugin-utils" "^7.21.5"

"@babel/plugin-transform-async-generator-functions@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.3.tgz#3ed99924c354fb9e80dabb2cc8d002c702e94527"
  integrity sha512-36A4Aq48t66btydbZd5Fk0/xJqbpg/v4QWI4AH4cYHBXy9Mu42UOupZpebKFiCFNT9S9rJFcsld0gsv0ayLjtA==
  dependencies:
    "@babel/helper-environment-visitor" "^7.22.1"
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/helper-remap-async-to-generator" "^7.18.9"
    "@babel/plugin-syntax-async-generators" "^7.8.4"

"@babel/plugin-transform-async-to-generator@^7.20.7":
  version "7.20.7"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354"


@@ 650,6 585,23 @@
  dependencies:
    "@babel/helper-plugin-utils" "^7.20.2"

"@babel/plugin-transform-class-properties@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.3.tgz#3407145e513830df77f0cef828b8b231c166fe4c"
  integrity sha512-mASLsd6rhOrLZ5F3WbCxkzl67mmOnqik0zrg5W6D/X0QMW7HtvnoL1dRARLKIbMP3vXwkwziuLesPqWVGIl6Bw==
  dependencies:
    "@babel/helper-create-class-features-plugin" "^7.22.1"
    "@babel/helper-plugin-utils" "^7.21.5"

"@babel/plugin-transform-class-static-block@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.3.tgz#e352cf33567385c731a8f21192efeba760358773"
  integrity sha512-5BirgNWNOx7cwbTJCOmKFJ1pZjwk5MUfMIwiBBvsirCJMZeQgs5pk6i1OlkVg+1Vef5LfBahFOrdCnAWvkVKMw==
  dependencies:
    "@babel/helper-create-class-features-plugin" "^7.22.1"
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/plugin-syntax-class-static-block" "^7.14.5"

"@babel/plugin-transform-classes@^7.21.0":
  version "7.21.0"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665"


@@ 695,6 647,14 @@
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.9"

"@babel/plugin-transform-dynamic-import@^7.22.1":
  version "7.22.1"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.1.tgz#6c56afaf896a07026330cf39714532abed8d9ed1"
  integrity sha512-rlhWtONnVBPdmt+jeewS0qSnMz/3yLFrqAP8hHC6EDcrYRSyuz9f9yQhHvVn2Ad6+yO9fHXac5piudeYrInxwQ==
  dependencies:
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/plugin-syntax-dynamic-import" "^7.8.3"

"@babel/plugin-transform-exponentiation-operator@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd"


@@ 703,6 663,14 @@
    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6"
    "@babel/helper-plugin-utils" "^7.18.6"

"@babel/plugin-transform-export-namespace-from@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.3.tgz#9b8700aa495007d3bebac8358d1c562434b680b9"
  integrity sha512-5Ti1cHLTDnt3vX61P9KZ5IG09bFXp4cDVFJIAeCZuxu9OXXJJZp5iP0n/rzM2+iAutJY+KWEyyHcRaHlpQ/P5g==
  dependencies:
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/plugin-syntax-export-namespace-from" "^7.8.3"

"@babel/plugin-transform-for-of@^7.21.5":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc"


@@ 719,6 687,14 @@
    "@babel/helper-function-name" "^7.18.9"
    "@babel/helper-plugin-utils" "^7.18.9"

"@babel/plugin-transform-json-strings@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.3.tgz#a181b8679cf7c93e9d0e3baa5b1776d65be601a9"
  integrity sha512-IuvOMdeOOY2X4hRNAT6kwbePtK21BUyrAEgLKviL8pL6AEEVUVcqtRdN/HJXBLGIbt9T3ETmXRnFedRRmQNTYw==
  dependencies:
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/plugin-syntax-json-strings" "^7.8.3"

"@babel/plugin-transform-literals@^7.18.9":
  version "7.18.9"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc"


@@ 726,6 702,14 @@
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.9"

"@babel/plugin-transform-logical-assignment-operators@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.3.tgz#9e021455810f33b0baccb82fb759b194f5dc36f0"
  integrity sha512-CbayIfOw4av2v/HYZEsH+Klks3NC2/MFIR3QR8gnpGNNPEaq2fdlVCRYG/paKs7/5hvBLQ+H70pGWOHtlNEWNA==
  dependencies:
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"

"@babel/plugin-transform-member-expression-literals@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e"


@@ 750,14 734,14 @@
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/helper-simple-access" "^7.21.5"

"@babel/plugin-transform-modules-systemjs@^7.20.11":
  version "7.20.11"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz#467ec6bba6b6a50634eea61c9c232654d8a4696e"
  integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==
"@babel/plugin-transform-modules-systemjs@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.3.tgz#cc507e03e88d87b016feaeb5dae941e6ef50d91e"
  integrity sha512-V21W3bKLxO3ZjcBJZ8biSvo5gQ85uIXW2vJfh7JSWf/4SLUSr1tOoHX3ruN4+Oqa2m+BKfsxTR1I+PsvkIWvNw==
  dependencies:
    "@babel/helper-hoist-variables" "^7.18.6"
    "@babel/helper-module-transforms" "^7.20.11"
    "@babel/helper-plugin-utils" "^7.20.2"
    "@babel/helper-module-transforms" "^7.22.1"
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/helper-validator-identifier" "^7.19.1"

"@babel/plugin-transform-modules-umd@^7.18.6":


@@ 768,20 752,47 @@
    "@babel/helper-module-transforms" "^7.18.6"
    "@babel/helper-plugin-utils" "^7.18.6"

"@babel/plugin-transform-named-capturing-groups-regex@^7.20.5":
  version "7.20.5"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8"
  integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==
"@babel/plugin-transform-named-capturing-groups-regex@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.3.tgz#db6fb77e6b3b53ec3b8d370246f0b7cf67d35ab4"
  integrity sha512-c6HrD/LpUdNNJsISQZpds3TXvfYIAbo+efE9aWmY/PmSRD0agrJ9cPMt4BmArwUQ7ZymEWTFjTyp+yReLJZh0Q==
  dependencies:
    "@babel/helper-create-regexp-features-plugin" "^7.20.5"
    "@babel/helper-plugin-utils" "^7.20.2"
    "@babel/helper-create-regexp-features-plugin" "^7.22.1"
    "@babel/helper-plugin-utils" "^7.21.5"

"@babel/plugin-transform-new-target@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8"
  integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==
"@babel/plugin-transform-new-target@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.3.tgz#deb0377d741cbee2f45305868b9026dcd6dd96e2"
  integrity sha512-5RuJdSo89wKdkRTqtM9RVVJzHum9c2s0te9rB7vZC1zKKxcioWIy+xcu4OoIAjyFZhb/bp5KkunuLin1q7Ct+w==
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.6"
    "@babel/helper-plugin-utils" "^7.21.5"

"@babel/plugin-transform-nullish-coalescing-operator@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.3.tgz#8c519f8bf5af94a9ca6f65cf422a9d3396e542b9"
  integrity sha512-CpaoNp16nX7ROtLONNuCyenYdY/l7ZsR6aoVa7rW7nMWisoNoQNIH5Iay/4LDyRjKMuElMqXiBoOQCDLTMGZiw==
  dependencies:
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"

"@babel/plugin-transform-numeric-separator@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.3.tgz#02493070ca6685884b0eee705363ee4da2132ab0"
  integrity sha512-+AF88fPDJrnseMh5vD9+SH6wq4ZMvpiTMHh58uLs+giMEyASFVhcT3NkoyO+NebFCNnpHJEq5AXO2txV4AGPDQ==
  dependencies:
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/plugin-syntax-numeric-separator" "^7.10.4"

"@babel/plugin-transform-object-rest-spread@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.3.tgz#da6fba693effb8c203d8c3bdf7bf4e2567e802e9"
  integrity sha512-38bzTsqMMCI46/TQnJwPPpy33EjLCc1Gsm2hRTF6zTMWnKsN61vdrpuzIEGQyKEhDSYDKyZHrrd5FMj4gcUHhw==
  dependencies:
    "@babel/compat-data" "^7.22.3"
    "@babel/helper-compilation-targets" "^7.22.1"
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
    "@babel/plugin-transform-parameters" "^7.22.3"

"@babel/plugin-transform-object-super@^7.18.6":
  version "7.18.6"


@@ 791,12 802,47 @@
    "@babel/helper-plugin-utils" "^7.18.6"
    "@babel/helper-replace-supers" "^7.18.6"

"@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.21.3":
  version "7.21.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz#18fc4e797cf6d6d972cb8c411dbe8a809fa157db"
  integrity sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==
"@babel/plugin-transform-optional-catch-binding@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.3.tgz#e971a083fc7d209d9cd18253853af1db6d8dc42f"
  integrity sha512-bnDFWXFzWY0BsOyqaoSXvMQ2F35zutQipugog/rqotL2S4ciFOKlRYUu9djt4iq09oh2/34hqfRR2k1dIvuu4g==
  dependencies:
    "@babel/helper-plugin-utils" "^7.20.2"
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"

"@babel/plugin-transform-optional-chaining@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.3.tgz#5fd24a4a7843b76da6aeec23c7f551da5d365290"
  integrity sha512-63v3/UFFxhPKT8j8u1jTTGVyITxl7/7AfOqK8C5gz1rHURPUGe3y5mvIf68eYKGoBNahtJnTxBKug4BQOnzeJg==
  dependencies:
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
    "@babel/plugin-syntax-optional-chaining" "^7.8.3"

"@babel/plugin-transform-parameters@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.3.tgz#24477acfd2fd2bc901df906c9bf17fbcfeee900d"
  integrity sha512-x7QHQJHPuD9VmfpzboyGJ5aHEr9r7DsAsdxdhJiTB3J3j8dyl+NFZ+rX5Q2RWFDCs61c06qBfS4ys2QYn8UkMw==
  dependencies:
    "@babel/helper-plugin-utils" "^7.21.5"

"@babel/plugin-transform-private-methods@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.3.tgz#adac38020bab5047482d3297107c1f58e9c574f6"
  integrity sha512-fC7jtjBPFqhqpPAE+O4LKwnLq7gGkD3ZmC2E3i4qWH34mH3gOg2Xrq5YMHUq6DM30xhqM1DNftiRaSqVjEG+ug==
  dependencies:
    "@babel/helper-create-class-features-plugin" "^7.22.1"
    "@babel/helper-plugin-utils" "^7.21.5"

"@babel/plugin-transform-private-property-in-object@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.3.tgz#031621b02c7b7d95389de1a3dba2fe9e8c548e56"
  integrity sha512-C7MMl4qWLpgVCbXfj3UW8rR1xeCnisQ0cU7YJHV//8oNBS0aCIVg1vFnZXxOckHhEpQyqNNkWmvSEWnMLlc+Vw==
  dependencies:
    "@babel/helper-annotate-as-pure" "^7.18.6"
    "@babel/helper-create-class-features-plugin" "^7.22.1"
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/plugin-syntax-private-property-in-object" "^7.14.5"

"@babel/plugin-transform-property-literals@^7.18.6":
  version "7.18.6"


@@ 827,16 873,16 @@
  dependencies:
    "@babel/plugin-transform-react-jsx" "^7.18.6"

"@babel/plugin-transform-react-jsx@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.6.tgz#2721e96d31df96e3b7ad48ff446995d26bc028ff"
  integrity sha512-Mz7xMPxoy9kPS/JScj6fJs03TZ/fZ1dJPlMjRAgTaxaS0fUBk8FV/A2rRgfPsVCZqALNwMexD+0Uaf5zlcKPpw==
"@babel/plugin-transform-react-jsx@^7.18.6", "@babel/plugin-transform-react-jsx@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.3.tgz#5a1f380df3703ba92eb1a930a539c6d88836f690"
  integrity sha512-JEulRWG2f04a7L8VWaOngWiK6p+JOSpB+DAtwfJgOaej1qdbNxqtK7MwTBHjUA10NeFcszlFNqCdbRcirzh2uQ==
  dependencies:
    "@babel/helper-annotate-as-pure" "^7.18.6"
    "@babel/helper-module-imports" "^7.18.6"
    "@babel/helper-plugin-utils" "^7.18.6"
    "@babel/plugin-syntax-jsx" "^7.18.6"
    "@babel/types" "^7.18.6"
    "@babel/helper-module-imports" "^7.21.4"
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/plugin-syntax-jsx" "^7.21.4"
    "@babel/types" "^7.22.3"

"@babel/plugin-transform-react-pure-annotations@^7.18.6":
  version "7.18.6"


@@ 861,16 907,16 @@
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.6"

"@babel/plugin-transform-runtime@^7.21.4":
  version "7.21.4"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.4.tgz#2e1da21ca597a7d01fc96b699b21d8d2023191aa"
  integrity sha512-1J4dhrw1h1PqnNNpzwxQ2UBymJUF8KuPjAAnlLwZcGhHAIqUigFW7cdK6GHoB64ubY4qXQNYknoUeks4Wz7CUA==
"@babel/plugin-transform-runtime@^7.22.4":
  version "7.22.4"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.4.tgz#f8353f313f18c3ce1315688631ec48657b97af42"
  integrity sha512-Urkiz1m4zqiRo17klj+l3nXgiRTFQng91Bc1eiLF7BMQu1e7wE5Gcq9xSv062IF068NHjcutSbIMev60gXxAvA==
  dependencies:
    "@babel/helper-module-imports" "^7.21.4"
    "@babel/helper-plugin-utils" "^7.20.2"
    babel-plugin-polyfill-corejs2 "^0.3.3"
    babel-plugin-polyfill-corejs3 "^0.6.0"
    babel-plugin-polyfill-regenerator "^0.4.1"
    "@babel/helper-plugin-utils" "^7.21.5"
    babel-plugin-polyfill-corejs2 "^0.4.3"
    babel-plugin-polyfill-corejs3 "^0.8.1"
    babel-plugin-polyfill-regenerator "^0.5.0"
    semver "^6.3.0"

"@babel/plugin-transform-shorthand-properties@^7.18.6":


@@ 926,6 972,14 @@
  dependencies:
    "@babel/helper-plugin-utils" "^7.21.5"

"@babel/plugin-transform-unicode-property-regex@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.3.tgz#597b6a614dc93eaae605ee293e674d79d32eb380"
  integrity sha512-5ScJ+OmdX+O6HRuMGW4kv7RL9vIKdtdAj9wuWUKy1wbHY3jaM/UlyIiC1G7J6UJiiyMukjjK0QwL3P0vBd0yYg==
  dependencies:
    "@babel/helper-create-regexp-features-plugin" "^7.22.1"
    "@babel/helper-plugin-utils" "^7.21.5"

"@babel/plugin-transform-unicode-regex@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca"


@@ 934,38 988,33 @@
    "@babel/helper-create-regexp-features-plugin" "^7.18.6"
    "@babel/helper-plugin-utils" "^7.18.6"

"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.21.5":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.5.tgz#db2089d99efd2297716f018aeead815ac3decffb"
  integrity sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg==
"@babel/plugin-transform-unicode-sets-regex@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.3.tgz#7c14ee33fa69782b0101d0f7143d3fc73ce00700"
  integrity sha512-hNufLdkF8vqywRp+P55j4FHXqAX2LRUccoZHH7AFn1pq5ZOO2ISKW9w13bFZVjBoTqeve2HOgoJCcaziJVhGNw==
  dependencies:
    "@babel/compat-data" "^7.21.5"
    "@babel/helper-compilation-targets" "^7.21.5"
    "@babel/helper-create-regexp-features-plugin" "^7.22.1"
    "@babel/helper-plugin-utils" "^7.21.5"

"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.22.4":
  version "7.22.4"
  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.4.tgz#c86a82630f0e8c61d9bb9327b7b896732028cbed"
  integrity sha512-c3lHOjbwBv0TkhYCr+XCR6wKcSZ1QbQTVdSkZUaVpLv8CVWotBMArWUi5UAJrcrQaEnleVkkvaV8F/pmc/STZQ==
  dependencies:
    "@babel/compat-data" "^7.22.3"
    "@babel/helper-compilation-targets" "^7.22.1"
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/helper-validator-option" "^7.21.0"
    "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6"
    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.20.7"
    "@babel/plugin-proposal-async-generator-functions" "^7.20.7"
    "@babel/plugin-proposal-class-properties" "^7.18.6"
    "@babel/plugin-proposal-class-static-block" "^7.21.0"
    "@babel/plugin-proposal-dynamic-import" "^7.18.6"
    "@babel/plugin-proposal-export-namespace-from" "^7.18.9"
    "@babel/plugin-proposal-json-strings" "^7.18.6"
    "@babel/plugin-proposal-logical-assignment-operators" "^7.20.7"
    "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6"
    "@babel/plugin-proposal-numeric-separator" "^7.18.6"
    "@babel/plugin-proposal-object-rest-spread" "^7.20.7"
    "@babel/plugin-proposal-optional-catch-binding" "^7.18.6"
    "@babel/plugin-proposal-optional-chaining" "^7.21.0"
    "@babel/plugin-proposal-private-methods" "^7.18.6"
    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.3"
    "@babel/plugin-proposal-private-property-in-object" "^7.21.0"
    "@babel/plugin-proposal-unicode-property-regex" "^7.18.6"
    "@babel/plugin-syntax-async-generators" "^7.8.4"
    "@babel/plugin-syntax-class-properties" "^7.12.13"
    "@babel/plugin-syntax-class-static-block" "^7.14.5"
    "@babel/plugin-syntax-dynamic-import" "^7.8.3"
    "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
    "@babel/plugin-syntax-import-assertions" "^7.20.0"
    "@babel/plugin-syntax-import-attributes" "^7.22.3"
    "@babel/plugin-syntax-import-meta" "^7.10.4"
    "@babel/plugin-syntax-json-strings" "^7.8.3"
    "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"


@@ 976,28 1025,43 @@
    "@babel/plugin-syntax-optional-chaining" "^7.8.3"
    "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
    "@babel/plugin-syntax-top-level-await" "^7.14.5"
    "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6"
    "@babel/plugin-transform-arrow-functions" "^7.21.5"
    "@babel/plugin-transform-async-generator-functions" "^7.22.3"
    "@babel/plugin-transform-async-to-generator" "^7.20.7"
    "@babel/plugin-transform-block-scoped-functions" "^7.18.6"
    "@babel/plugin-transform-block-scoping" "^7.21.0"
    "@babel/plugin-transform-class-properties" "^7.22.3"
    "@babel/plugin-transform-class-static-block" "^7.22.3"
    "@babel/plugin-transform-classes" "^7.21.0"
    "@babel/plugin-transform-computed-properties" "^7.21.5"
    "@babel/plugin-transform-destructuring" "^7.21.3"
    "@babel/plugin-transform-dotall-regex" "^7.18.6"
    "@babel/plugin-transform-duplicate-keys" "^7.18.9"
    "@babel/plugin-transform-dynamic-import" "^7.22.1"
    "@babel/plugin-transform-exponentiation-operator" "^7.18.6"
    "@babel/plugin-transform-export-namespace-from" "^7.22.3"
    "@babel/plugin-transform-for-of" "^7.21.5"
    "@babel/plugin-transform-function-name" "^7.18.9"
    "@babel/plugin-transform-json-strings" "^7.22.3"
    "@babel/plugin-transform-literals" "^7.18.9"
    "@babel/plugin-transform-logical-assignment-operators" "^7.22.3"
    "@babel/plugin-transform-member-expression-literals" "^7.18.6"
    "@babel/plugin-transform-modules-amd" "^7.20.11"
    "@babel/plugin-transform-modules-commonjs" "^7.21.5"
    "@babel/plugin-transform-modules-systemjs" "^7.20.11"
    "@babel/plugin-transform-modules-systemjs" "^7.22.3"
    "@babel/plugin-transform-modules-umd" "^7.18.6"
    "@babel/plugin-transform-named-capturing-groups-regex" "^7.20.5"
    "@babel/plugin-transform-new-target" "^7.18.6"
    "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.3"
    "@babel/plugin-transform-new-target" "^7.22.3"
    "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.3"
    "@babel/plugin-transform-numeric-separator" "^7.22.3"
    "@babel/plugin-transform-object-rest-spread" "^7.22.3"
    "@babel/plugin-transform-object-super" "^7.18.6"
    "@babel/plugin-transform-parameters" "^7.21.3"
    "@babel/plugin-transform-optional-catch-binding" "^7.22.3"
    "@babel/plugin-transform-optional-chaining" "^7.22.3"
    "@babel/plugin-transform-parameters" "^7.22.3"
    "@babel/plugin-transform-private-methods" "^7.22.3"
    "@babel/plugin-transform-private-property-in-object" "^7.22.3"
    "@babel/plugin-transform-property-literals" "^7.18.6"
    "@babel/plugin-transform-regenerator" "^7.21.5"
    "@babel/plugin-transform-reserved-words" "^7.18.6"


@@ 1007,13 1071,15 @@
    "@babel/plugin-transform-template-literals" "^7.18.9"
    "@babel/plugin-transform-typeof-symbol" "^7.18.9"
    "@babel/plugin-transform-unicode-escapes" "^7.21.5"
    "@babel/plugin-transform-unicode-property-regex" "^7.22.3"
    "@babel/plugin-transform-unicode-regex" "^7.18.6"
    "@babel/plugin-transform-unicode-sets-regex" "^7.22.3"
    "@babel/preset-modules" "^0.1.5"
    "@babel/types" "^7.21.5"
    babel-plugin-polyfill-corejs2 "^0.3.3"
    babel-plugin-polyfill-corejs3 "^0.6.0"
    babel-plugin-polyfill-regenerator "^0.4.1"
    core-js-compat "^3.25.1"
    "@babel/types" "^7.22.4"
    babel-plugin-polyfill-corejs2 "^0.4.3"
    babel-plugin-polyfill-corejs3 "^0.8.1"
    babel-plugin-polyfill-regenerator "^0.5.0"
    core-js-compat "^3.30.2"
    semver "^6.3.0"

"@babel/preset-modules@^0.1.5":


@@ 1027,15 1093,15 @@
    "@babel/types" "^7.4.4"
    esutils "^2.0.2"

"@babel/preset-react@^7.18.6":
  version "7.18.6"
  resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d"
  integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==
"@babel/preset-react@^7.22.3":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.22.3.tgz#2ec7f91d0c924fa2ea0c7cfbbf690bc62b79cd84"
  integrity sha512-lxDz1mnZ9polqClBCVBjIVUypoB4qV3/tZUDb/IlYbW1kiiLaXaX+bInbRjl+lNQ/iUZraQ3+S8daEmoELMWug==
  dependencies:
    "@babel/helper-plugin-utils" "^7.18.6"
    "@babel/helper-validator-option" "^7.18.6"
    "@babel/helper-plugin-utils" "^7.21.5"
    "@babel/helper-validator-option" "^7.21.0"
    "@babel/plugin-transform-react-display-name" "^7.18.6"
    "@babel/plugin-transform-react-jsx" "^7.18.6"
    "@babel/plugin-transform-react-jsx" "^7.22.3"
    "@babel/plugin-transform-react-jsx-development" "^7.18.6"
    "@babel/plugin-transform-react-pure-annotations" "^7.18.6"



@@ 1062,42 1128,42 @@
  dependencies:
    regenerator-runtime "^0.12.0"

"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
  integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
  version "7.22.3"
  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb"
  integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==
  dependencies:
    regenerator-runtime "^0.13.11"

"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3":
  version "7.20.7"
  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
  integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==
"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.21.9", "@babel/template@^7.3.3":
  version "7.21.9"
  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.21.9.tgz#bf8dad2859130ae46088a99c1f265394877446fb"
  integrity sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==
  dependencies:
    "@babel/code-frame" "^7.18.6"
    "@babel/parser" "^7.20.7"
    "@babel/types" "^7.20.7"
    "@babel/code-frame" "^7.21.4"
    "@babel/parser" "^7.21.9"
    "@babel/types" "^7.21.5"

"@babel/traverse@^7.18.10", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.5", "@babel/traverse@^7.7.2":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133"
  integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==
"@babel/traverse@^7.18.10", "@babel/traverse@^7.20.7", "@babel/traverse@^7.22.1", "@babel/traverse@^7.7.2":
  version "7.22.4"
  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.4.tgz#c3cf96c5c290bd13b55e29d025274057727664c0"
  integrity sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==
  dependencies:
    "@babel/code-frame" "^7.21.4"
    "@babel/generator" "^7.21.5"
    "@babel/helper-environment-visitor" "^7.21.5"
    "@babel/generator" "^7.22.3"
    "@babel/helper-environment-visitor" "^7.22.1"
    "@babel/helper-function-name" "^7.21.0"
    "@babel/helper-hoist-variables" "^7.18.6"
    "@babel/helper-split-export-declaration" "^7.18.6"
    "@babel/parser" "^7.21.5"
    "@babel/types" "^7.21.5"
    "@babel/parser" "^7.22.4"
    "@babel/types" "^7.22.4"
    debug "^4.1.0"
    globals "^11.1.0"

"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
  version "7.21.5"
  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6"
  integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==
"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3", "@babel/types@^7.22.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
  version "7.22.4"
  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.4.tgz#56a2653ae7e7591365dabf20b76295410684c071"
  integrity sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==
  dependencies:
    "@babel/helper-string-parser" "^7.21.5"
    "@babel/helper-validator-identifier" "^7.19.1"


@@ 1867,21 1933,10 @@
  resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc"
  integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==

"@types/babel__core@^7.1.12", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.3":
  version "7.1.18"
  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.18.tgz#1a29abcc411a9c05e2094c98f9a1b7da6cdf49f8"
  integrity sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==
  dependencies:
    "@babel/parser" "^7.1.0"
    "@babel/types" "^7.0.0"
    "@types/babel__generator" "*"
    "@types/babel__template" "*"
    "@types/babel__traverse" "*"

"@types/babel__core@^7.20.0":
  version "7.20.0"
  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891"
  integrity sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==
"@types/babel__core@^7.1.12", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.3", "@types/babel__core@^7.20.1":
  version "7.20.1"
  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b"
  integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==
  dependencies:
    "@babel/parser" "^7.20.7"
    "@babel/types" "^7.20.7"


@@ 2087,10 2142,10 @@
  resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
  integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=

"@types/lodash@^4.14.194":
  version "4.14.194"
  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.194.tgz#b71eb6f7a0ff11bff59fc987134a093029258a76"
  integrity sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==
"@types/lodash@^4.14.195":
  version "4.14.195"
  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632"
  integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==

"@types/mime@*":
  version "3.0.1"


@@ 2156,12 2211,7 @@
  resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0"
  integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==

"@types/prop-types@*":
  version "15.7.3"
  resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
  integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==

"@types/prop-types@^15.7.5":
"@types/prop-types@*", "@types/prop-types@^15.7.5":
  version "15.7.5"
  resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
  integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==


@@ 2208,10 2258,10 @@
  resolved "https://registry.yarnpkg.com/@types/react-intl/-/react-intl-2.3.18.tgz#fd2d8b7f4d0a1dd05b5f1784ab0d7fe1786a690d"
  integrity sha512-DVNJs49zUxKRZng8VuILE886Yihdsf3yLr5vHk9zJrmF8SyRSK3sxNSvikAKxNkv9hX55XBTJShz6CkJnbNjgg==

"@types/react-motion@^0.0.33":
  version "0.0.33"
  resolved "https://registry.yarnpkg.com/@types/react-motion/-/react-motion-0.0.33.tgz#c156c400ace995584990344cc0239e41f411f425"
  integrity sha512-R9grd4EwdDBcKKq7Zhszd8ukyy2BLKN6ooNI0V39nUl/sui+m7VI94cdebYemBteoPHmO7J7BZk+cIf+Xnk4TA==
"@types/react-motion@^0.0.34":
  version "0.0.34"
  resolved "https://registry.yarnpkg.com/@types/react-motion/-/react-motion-0.0.34.tgz#789ff2063e2f7fbb6085d291135c442e8b35291a"
  integrity sha512-/rFI22Vg4Xzb47hXtS06WkzUGRu+Vb3yDleuxiqzGj0JbXYXQUCgwSa2ZU12K7ubKi4C8xsdIN3xt4Z4fjSdPw==
  dependencies:
    "@types/react" "*"



@@ 2288,10 2338,10 @@
  dependencies:
    "@types/react" "*"

"@types/react@*", "@types/react@>=16.9.11", "@types/react@^18.0.26":
  version "18.2.6"
  resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.6.tgz#5cd53ee0d30ffc193b159d3516c8c8ad2f19d571"
  integrity sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^18.0.26", "@types/react@^18.2.7":
  version "18.2.7"
  resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.7.tgz#dfb4518042a3117a045b8c222316f83414a783b3"
  integrity sha512-ojrXpSH2XFCmHm7Jy3q44nXDyN54+EYKP2lBhJ2bqfyPj6cIUW/FZW/Csdia34NQgq7KYcAlHi5184m4X88+yw==
  dependencies:
    "@types/prop-types" "*"
    "@types/scheduler" "*"


@@ 2434,15 2484,15 @@
  dependencies:
    "@types/yargs-parser" "*"

"@typescript-eslint/eslint-plugin@^5.59.7":
  version "5.59.7"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.7.tgz#e470af414f05ecfdc05a23e9ce6ec8f91db56fe2"
  integrity sha512-BL+jYxUFIbuYwy+4fF86k5vdT9lT0CNJ6HtwrIvGh0PhH8s0yy5rjaKH2fDCrz5ITHy07WCzVGNvAmjJh4IJFA==
"@typescript-eslint/eslint-plugin@^5.59.8":
  version "5.59.8"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.8.tgz#1e7a3e5318ece22251dfbc5c9c6feeb4793cc509"
  integrity sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==
  dependencies:
    "@eslint-community/regexpp" "^4.4.0"
    "@typescript-eslint/scope-manager" "5.59.7"
    "@typescript-eslint/type-utils" "5.59.7"
    "@typescript-eslint/utils" "5.59.7"
    "@typescript-eslint/scope-manager" "5.59.8"
    "@typescript-eslint/type-utils" "5.59.8"
    "@typescript-eslint/utils" "5.59.8"
    debug "^4.3.4"
    grapheme-splitter "^1.0.4"
    ignore "^5.2.0"


@@ 2450,31 2500,31 @@
    semver "^7.3.7"
    tsutils "^3.21.0"

"@typescript-eslint/parser@^5.59.7":
  version "5.59.7"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.7.tgz#02682554d7c1028b89aa44a48bf598db33048caa"
  integrity sha512-VhpsIEuq/8i5SF+mPg9jSdIwgMBBp0z9XqjiEay+81PYLJuroN+ET1hM5IhkiYMJd9MkTz8iJLt7aaGAgzWUbQ==
"@typescript-eslint/parser@^5.59.8":
  version "5.59.8"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.8.tgz#60cbb00671d86cf746044ab797900b1448188567"
  integrity sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw==
  dependencies:
    "@typescript-eslint/scope-manager" "5.59.7"
    "@typescript-eslint/types" "5.59.7"
    "@typescript-eslint/typescript-estree" "5.59.7"
    "@typescript-eslint/scope-manager" "5.59.8"
    "@typescript-eslint/types" "5.59.8"
    "@typescript-eslint/typescript-estree" "5.59.8"
    debug "^4.3.4"

"@typescript-eslint/scope-manager@5.59.7":
  version "5.59.7"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz#0243f41f9066f3339d2f06d7f72d6c16a16769e2"
  integrity sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==
"@typescript-eslint/scope-manager@5.59.8":
  version "5.59.8"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.8.tgz#ff4ad4fec6433647b817c4a7d4b4165d18ea2fa8"
  integrity sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==
  dependencies:
    "@typescript-eslint/types" "5.59.7"
    "@typescript-eslint/visitor-keys" "5.59.7"
    "@typescript-eslint/types" "5.59.8"
    "@typescript-eslint/visitor-keys" "5.59.8"

"@typescript-eslint/type-utils@5.59.7":
  version "5.59.7"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.7.tgz#89c97291371b59eb18a68039857c829776f1426d"
  integrity sha512-ozuz/GILuYG7osdY5O5yg0QxXUAEoI4Go3Do5xeu+ERH9PorHBPSdvD3Tjp2NN2bNLh1NJQSsQu2TPu/Ly+HaQ==
"@typescript-eslint/type-utils@5.59.8":
  version "5.59.8"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.8.tgz#aa6c029a9d7706d26bbd25eb4666398781df6ea2"
  integrity sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==
  dependencies:
    "@typescript-eslint/typescript-estree" "5.59.7"
    "@typescript-eslint/utils" "5.59.7"
    "@typescript-eslint/typescript-estree" "5.59.8"
    "@typescript-eslint/utils" "5.59.8"
    debug "^4.3.4"
    tsutils "^3.21.0"



@@ 2483,10 2533,10 @@
  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.0.tgz#3fcdac7dbf923ec5251545acdd9f1d42d7c4fe32"
  integrity sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA==

"@typescript-eslint/types@5.59.7":
  version "5.59.7"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.7.tgz#6f4857203fceee91d0034ccc30512d2939000742"
  integrity sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==
"@typescript-eslint/types@5.59.8":
  version "5.59.8"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.8.tgz#212e54414733618f5d0fd50b2da2717f630aebf8"
  integrity sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==

"@typescript-eslint/typescript-estree@5.59.0":
  version "5.59.0"


@@ 2501,30 2551,30 @@
    semver "^7.3.7"
    tsutils "^3.21.0"

"@typescript-eslint/typescript-estree@5.59.7":
  version "5.59.7"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz#b887acbd4b58e654829c94860dbff4ac55c5cff8"
  integrity sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==
"@typescript-eslint/typescript-estree@5.59.8":
  version "5.59.8"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.8.tgz#801a7b1766481629481b3b0878148bd7a1f345d7"
  integrity sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==
  dependencies:
    "@typescript-eslint/types" "5.59.7"
    "@typescript-eslint/visitor-keys" "5.59.7"
    "@typescript-eslint/types" "5.59.8"
    "@typescript-eslint/visitor-keys" "5.59.8"
    debug "^4.3.4"
    globby "^11.1.0"
    is-glob "^4.0.3"
    semver "^7.3.7"
    tsutils "^3.21.0"

"@typescript-eslint/utils@5.59.7":
  version "5.59.7"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.7.tgz#7adf068b136deae54abd9a66ba5a8780d2d0f898"
  integrity sha512-yCX9WpdQKaLufz5luG4aJbOpdXf/fjwGMcLFXZVPUz3QqLirG5QcwwnIHNf8cjLjxK4qtzTO8udUtMQSAToQnQ==
"@typescript-eslint/utils@5.59.8":
  version "5.59.8"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.8.tgz#34d129f35a2134c67fdaf024941e8f96050dca2b"
  integrity sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==
  dependencies:
    "@eslint-community/eslint-utils" "^4.2.0"
    "@types/json-schema" "^7.0.9"
    "@types/semver" "^7.3.12"
    "@typescript-eslint/scope-manager" "5.59.7"
    "@typescript-eslint/types" "5.59.7"
    "@typescript-eslint/typescript-estree" "5.59.7"
    "@typescript-eslint/scope-manager" "5.59.8"
    "@typescript-eslint/types" "5.59.8"
    "@typescript-eslint/typescript-estree" "5.59.8"
    eslint-scope "^5.1.1"
    semver "^7.3.7"



@@ 2536,12 2586,12 @@
    "@typescript-eslint/types" "5.59.0"
    eslint-visitor-keys "^3.3.0"

"@typescript-eslint/visitor-keys@5.59.7":
  version "5.59.7"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz#09c36eaf268086b4fbb5eb9dc5199391b6485fc5"
  integrity sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==
"@typescript-eslint/visitor-keys@5.59.8":
  version "5.59.8"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz#aa6a7ef862add919401470c09e1609392ef3cc40"
  integrity sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==
  dependencies:
    "@typescript-eslint/types" "5.59.7"
    "@typescript-eslint/types" "5.59.8"
    eslint-visitor-keys "^3.3.0"

"@webassemblyjs/ast@1.9.0":


@@ 3219,29 3269,29 @@ babel-plugin-macros@^3.0.1:
    cosmiconfig "^7.0.0"
    resolve "^1.19.0"

babel-plugin-polyfill-corejs2@^0.3.3:
  version "0.3.3"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122"
  integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==
babel-plugin-polyfill-corejs2@^0.4.3:
  version "0.4.3"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz#75044d90ba5043a5fb559ac98496f62f3eb668fd"
  integrity sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==
  dependencies:
    "@babel/compat-data" "^7.17.7"
    "@babel/helper-define-polyfill-provider" "^0.3.3"
    "@babel/helper-define-polyfill-provider" "^0.4.0"
    semver "^6.1.1"

babel-plugin-polyfill-corejs3@^0.6.0:
  version "0.6.0"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a"
  integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==
babel-plugin-polyfill-corejs3@^0.8.1:
  version "0.8.1"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz#39248263c38191f0d226f928d666e6db1b4b3a8a"
  integrity sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==
  dependencies:
    "@babel/helper-define-polyfill-provider" "^0.3.3"
    core-js-compat "^3.25.1"
    "@babel/helper-define-polyfill-provider" "^0.4.0"
    core-js-compat "^3.30.1"

babel-plugin-polyfill-regenerator@^0.4.1:
  version "0.4.1"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747"
  integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==
babel-plugin-polyfill-regenerator@^0.5.0:
  version "0.5.0"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz#e7344d88d9ef18a3c47ded99362ae4a757609380"
  integrity sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==
  dependencies:
    "@babel/helper-define-polyfill-provider" "^0.3.3"
    "@babel/helper-define-polyfill-provider" "^0.4.0"

babel-plugin-preval@^5.1.0:
  version "5.1.0"


@@ 4093,12 4143,12 @@ copy-descriptor@^0.1.0:
  resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
  integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=

core-js-compat@^3.25.1:
  version "3.25.2"
  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.2.tgz#7875573586809909c69e03ef310810c1969ee138"
  integrity sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ==
core-js-compat@^3.30.1, core-js-compat@^3.30.2:
  version "3.30.2"
  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.2.tgz#83f136e375babdb8c80ad3c22d67c69098c1dd8b"
  integrity sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==
  dependencies:
    browserslist "^4.21.4"
    browserslist "^4.21.5"

core-js@^2.5.0:
  version "2.6.12"


@@ 5080,10 5130,10 @@ eslint-plugin-import@~2.27.5:
    semver "^6.3.0"
    tsconfig-paths "^3.14.1"

eslint-plugin-jsdoc@^44.2.5:
  version "44.2.5"
  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-44.2.5.tgz#f3186f57f112a230b3b7af34bf236d207bc8d5d7"
  integrity sha512-KtuhaYy2GmdY2IQE5t+1lup8O4P05c+V4gKcj45PCxFM0OxmRq2uQlfOS1AgYVgPYIBKGE86DxrbKP24HKpORA==
eslint-plugin-jsdoc@^45.0.0:
  version "45.0.0"
  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-45.0.0.tgz#6be84e4842a7138cc571a907ea9c31c42eaac5c0"
  integrity sha512-l2+Jcs/Ps7oFA+SWY+0sweU/e5LgricnEl6EsDlyRTF5y0+NWL1y9Qwz9PHwHAxtdJq6lxPjEQWmYLMkvhzD4g==
  dependencies:
    "@es-joy/jsdoccomment" "~0.39.4"
    are-docs-informative "^0.0.2"


@@ 7475,10 7525,10 @@ jsdom@^20.0.0:
    ws "^8.11.0"
    xml-name-validator "^4.0.0"

jsdom@^22.0.0:
  version "22.0.0"
  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.0.0.tgz#3295c6992c70089c4b8f5cf060489fddf7ee9816"
  integrity sha512-p5ZTEb5h+O+iU02t0GfEjAnkdYPrQSkfuTSMkMYyIoMvUNEHsbG0bHHbfXIcfTqD2UfvjQX7mmgiFsyRwGscVw==
jsdom@^22.1.0:
  version "22.1.0"
  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.1.0.tgz#0fca6d1a37fbeb7f4aac93d1090d782c56b611c8"
  integrity sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==
  dependencies:
    abab "^2.0.6"
    cssstyle "^3.0.0"


@@ 9256,10 9306,10 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
  resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
  integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==

postcss@^8.2.15, postcss@^8.4.23:
  version "8.4.23"
  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"
  integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==
postcss@^8.2.15, postcss@^8.4.23, postcss@^8.4.24:
  version "8.4.24"
  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df"
  integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==
  dependencies:
    nanoid "^3.3.6"
    picocolors "^1.0.0"


@@ 12140,25 12190,25 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
  resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
  integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==

workbox-background-sync@6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz#3141afba3cc8aa2ae14c24d0f6811374ba8ff6a9"
  integrity sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==
workbox-background-sync@6.6.1:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.6.1.tgz#08d603a33717ce663e718c30cc336f74909aff2f"
  integrity sha512-trJd3ovpWCvzu4sW0E8rV3FUyIcC0W8G+AZ+VcqzzA890AsWZlUGOTSxIMmIHVusUw/FDq1HFWfy/kC/WTRqSg==
  dependencies:
    idb "^7.0.1"
    workbox-core "6.5.4"
    workbox-core "6.6.1"

workbox-broadcast-update@6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz#8441cff5417cd41f384ba7633ca960a7ffe40f66"
  integrity sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==
workbox-broadcast-update@6.6.1:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.6.1.tgz#0fad9454cf8e4ace0c293e5617c64c75d8a8c61e"
  integrity sha512-fBhffRdaANdeQ1V8s692R9l/gzvjjRtydBOvR6WCSB0BNE2BacA29Z4r9/RHd9KaXCPl6JTdI9q0bR25YKP8TQ==
  dependencies:
    workbox-core "6.5.4"
    workbox-core "6.6.1"

workbox-build@6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.5.4.tgz#7d06d31eb28a878817e1c991c05c5b93409f0389"
  integrity sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==
workbox-build@6.6.1:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.6.1.tgz#6010e9ce550910156761448f2dbea8cfcf759cb0"
  integrity sha512-INPgDx6aRycAugUixbKgiEQBWD0MPZqU5r0jyr24CehvNuLPSXp/wGOpdRJmts656lNiXwqV7dC2nzyrzWEDnw==
  dependencies:
    "@apideck/better-ajv-errors" "^0.3.1"
    "@babel/core" "^7.11.1"


@@ 12182,132 12232,132 @@ workbox-build@6.5.4:
    strip-comments "^2.0.1"
    tempy "^0.6.0"
    upath "^1.2.0"
    workbox-background-sync "6.5.4"
    workbox-broadcast-update "6.5.4"
    workbox-cacheable-response "6.5.4"
    workbox-core "6.5.4"
    workbox-expiration "6.5.4"
    workbox-google-analytics "6.5.4"
    workbox-navigation-preload "6.5.4"
    workbox-precaching "6.5.4"
    workbox-range-requests "6.5.4"
    workbox-recipes "6.5.4"
    workbox-routing "6.5.4"
    workbox-strategies "6.5.4"
    workbox-streams "6.5.4"
    workbox-sw "6.5.4"
    workbox-window "6.5.4"

workbox-cacheable-response@6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz#a5c6ec0c6e2b6f037379198d4ef07d098f7cf137"
  integrity sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==
  dependencies:
    workbox-core "6.5.4"

workbox-core@6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.5.4.tgz#df48bf44cd58bb1d1726c49b883fb1dffa24c9ba"
  integrity sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==

workbox-expiration@6.5.4, workbox-expiration@^6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.5.4.tgz#501056f81e87e1d296c76570bb483ce5e29b4539"
  integrity sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==
    workbox-background-sync "6.6.1"
    workbox-broadcast-update "6.6.1"
    workbox-cacheable-response "6.6.1"
    workbox-core "6.6.1"
    workbox-expiration "6.6.1"
    workbox-google-analytics "6.6.1"
    workbox-navigation-preload "6.6.1"
    workbox-precaching "6.6.1"
    workbox-range-requests "6.6.1"
    workbox-recipes "6.6.1"
    workbox-routing "6.6.1"
    workbox-strategies "6.6.1"
    workbox-streams "6.6.1"
    workbox-sw "6.6.1"
    workbox-window "6.6.1"

workbox-cacheable-response@6.6.1:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.6.1.tgz#284c2b86be3f4fd191970ace8c8e99797bcf58e9"
  integrity sha512-85LY4veT2CnTCDxaVG7ft3NKaFbH6i4urZXgLiU4AiwvKqS2ChL6/eILiGRYXfZ6gAwDnh5RkuDbr/GMS4KSag==
  dependencies:
    workbox-core "6.6.1"

workbox-core@6.6.1:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.6.1.tgz#7184776d4134c5ed2f086878c882728fc9084265"
  integrity sha512-ZrGBXjjaJLqzVothoE12qTbVnOAjFrHDXpZe7coCb6q65qI/59rDLwuFMO4PcZ7jcbxY+0+NhUVztzR/CbjEFw==

workbox-expiration@6.6.1, workbox-expiration@^6.6.0:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.6.1.tgz#a841fa36676104426dbfb9da1ef6a630b4f93739"
  integrity sha512-qFiNeeINndiOxaCrd2DeL1Xh1RFug3JonzjxUHc5WkvkD2u5abY3gZL1xSUNt3vZKsFFGGORItSjVTVnWAZO4A==
  dependencies:
    idb "^7.0.1"
    workbox-core "6.5.4"
    workbox-core "6.6.1"

workbox-google-analytics@6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz#c74327f80dfa4c1954cbba93cd7ea640fe7ece7d"
  integrity sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==
workbox-google-analytics@6.6.1:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.6.1.tgz#a07a6655ab33d89d1b0b0a935ffa5dea88618c5d"
  integrity sha512-1TjSvbFSLmkpqLcBsF7FuGqqeDsf+uAXO/pjiINQKg3b1GN0nBngnxLcXDYo1n/XxK4N7RaRrpRlkwjY/3ocuA==
  dependencies:
    workbox-background-sync "6.5.4"
    workbox-core "6.5.4"
    workbox-routing "6.5.4"
    workbox-strategies "6.5.4"
    workbox-background-sync "6.6.1"
    workbox-core "6.6.1"
    workbox-routing "6.6.1"
    workbox-strategies "6.6.1"

workbox-navigation-preload@6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz#ede56dd5f6fc9e860a7e45b2c1a8f87c1c793212"
  integrity sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==
workbox-navigation-preload@6.6.1:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.6.1.tgz#61a34fe125558dd88cf09237f11bd966504ea059"
  integrity sha512-DQCZowCecO+wRoIxJI2V6bXWK6/53ff+hEXLGlQL4Rp9ZaPDLrgV/32nxwWIP7QpWDkVEtllTAK5h6cnhxNxDA==
  dependencies:
    workbox-core "6.5.4"
    workbox-core "6.6.1"

workbox-precaching@6.5.4, workbox-precaching@^6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.5.4.tgz#740e3561df92c6726ab5f7471e6aac89582cab72"
  integrity sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==
workbox-precaching@6.6.1, workbox-precaching@^6.6.0:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.6.1.tgz#dedeeba10a2d163d990bf99f1c2066ac0d1a19e2"
  integrity sha512-K4znSJ7IKxCnCYEdhNkMr7X1kNh8cz+mFgx9v5jFdz1MfI84pq8C2zG+oAoeE5kFrUf7YkT5x4uLWBNg0DVZ5A==
  dependencies:
    workbox-core "6.5.4"
    workbox-routing "6.5.4"
    workbox-strategies "6.5.4"
    workbox-core "6.6.1"
    workbox-routing "6.6.1"
    workbox-strategies "6.6.1"

workbox-range-requests@6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz#86b3d482e090433dab38d36ae031b2bb0bd74399"
  integrity sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==
workbox-range-requests@6.6.1:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.6.1.tgz#ddaf7e73af11d362fbb2f136a9063a4c7f507a39"
  integrity sha512-4BDzk28govqzg2ZpX0IFkthdRmCKgAKreontYRC5YsAPB2jDtPNxqx3WtTXgHw1NZalXpcH/E4LqUa9+2xbv1g==
  dependencies:
    workbox-core "6.5.4"
    workbox-core "6.6.1"

workbox-recipes@6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.5.4.tgz#cca809ee63b98b158b2702dcfb741b5cc3e24acb"
  integrity sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==
workbox-recipes@6.6.1:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.6.1.tgz#ea70d2b2b0b0bce8de0a9d94f274d4a688e69fae"
  integrity sha512-/oy8vCSzromXokDA+X+VgpeZJvtuf8SkQ8KL0xmRivMgJZrjwM3c2tpKTJn6PZA6TsbxGs3Sc7KwMoZVamcV2g==
  dependencies:
    workbox-cacheable-response "6.5.4"
    workbox-core "6.5.4"
    workbox-expiration "6.5.4"
    workbox-precaching "6.5.4"
    workbox-routing "6.5.4"
    workbox-strategies "6.5.4"
    workbox-cacheable-response "6.6.1"
    workbox-core "6.6.1"
    workbox-expiration "6.6.1"
    workbox-precaching "6.6.1"
    workbox-routing "6.6.1"
    workbox-strategies "6.6.1"

workbox-routing@6.5.4, workbox-routing@^6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.5.4.tgz#6a7fbbd23f4ac801038d9a0298bc907ee26fe3da"
  integrity sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==
workbox-routing@6.6.1, workbox-routing@^6.6.0:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.6.1.tgz#cba9a1c7e0d1ea11e24b6f8c518840efdc94f581"
  integrity sha512-j4ohlQvfpVdoR8vDYxTY9rA9VvxTHogkIDwGdJ+rb2VRZQ5vt1CWwUUZBeD/WGFAni12jD1HlMXvJ8JS7aBWTg==
  dependencies:
    workbox-core "6.5.4"
    workbox-core "6.6.1"

workbox-strategies@6.5.4, workbox-strategies@^6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.5.4.tgz#4edda035b3c010fc7f6152918370699334cd204d"
  integrity sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==
workbox-strategies@6.6.1, workbox-strategies@^6.6.0:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.6.1.tgz#38d0f0fbdddba97bd92e0c6418d0b1a2ccd5b8bf"
  integrity sha512-WQLXkRnsk4L81fVPkkgon1rZNxnpdO5LsO+ws7tYBC6QQQFJVI6v98klrJEjFtZwzw/mB/HT5yVp7CcX0O+mrw==
  dependencies:
    workbox-core "6.5.4"
    workbox-core "6.6.1"

workbox-streams@6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.5.4.tgz#1cb3c168a6101df7b5269d0353c19e36668d7d69"
  integrity sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==
workbox-streams@6.6.1:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.6.1.tgz#b2f7ba7b315c27a6e3a96a476593f99c5d227d26"
  integrity sha512-maKG65FUq9e4BLotSKWSTzeF0sgctQdYyTMq529piEN24Dlu9b6WhrAfRpHdCncRS89Zi2QVpW5V33NX8PgH3Q==
  dependencies:
    workbox-core "6.5.4"
    workbox-routing "6.5.4"
    workbox-core "6.6.1"
    workbox-routing "6.6.1"

workbox-sw@6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.5.4.tgz#d93e9c67924dd153a61367a4656ff4d2ae2ed736"
  integrity sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==
workbox-sw@6.6.1:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.6.1.tgz#d4c4ca3125088e8b9fd7a748ed537fa0247bd72c"
  integrity sha512-R7whwjvU2abHH/lR6kQTTXLHDFU2izht9kJOvBRYK65FbwutT4VvnUAJIgHvfWZ/fokrOPhfoWYoPCMpSgUKHQ==

workbox-webpack-plugin@^6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz#baf2d3f4b8f435f3469887cf4fba2b7fac3d0fd7"
  integrity sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==
workbox-webpack-plugin@^6.6.0:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.1.tgz#4f81cc1ad4e5d2cd7477a86ba83c84ee2d187531"
  integrity sha512-zpZ+ExFj9NmiI66cFEApyjk7hGsfJ1YMOaLXGXBoZf0v7Iu6hL0ZBe+83mnDq3YYWAfA3fnyFejritjOHkFcrA==
  dependencies:
    fast-json-stable-stringify "^2.1.0"
    pretty-bytes "^5.4.1"
    upath "^1.2.0"
    webpack-sources "^1.4.3"
    workbox-build "6.5.4"
    workbox-build "6.6.1"

workbox-window@6.5.4, workbox-window@^6.5.4:
  version "6.5.4"
  resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.5.4.tgz#d991bc0a94dff3c2dbb6b84558cff155ca878e91"
  integrity sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==
workbox-window@6.6.1, workbox-window@^6.6.0:
  version "6.6.1"
  resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.6.1.tgz#f22a394cbac36240d0dadcbdebc35f711bb7b89e"
  integrity sha512-wil4nwOY58nTdCvif/KEZjQ2NP8uk3gGeRNy2jPBbzypU4BT4D9L8xiwbmDBpZlSgJd2xsT9FvSNU0gsxV51JQ==
  dependencies:
    "@types/trusted-types" "^2.0.2"
    workbox-core "6.5.4"
    workbox-core "6.6.1"

wrap-ansi@^5.1.0:
  version "5.1.0"