diff --git a/Gemfile b/Gemfile index 51308c24d..930c5352c 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ ruby '>= 3.0.0' gem 'pkg-config', '~> 1.5' -gem 'puma', '~> 6.2' +gem 'puma', '~> 6.3' gem 'rails', '~> 6.1.7' gem 'sprockets', '~> 3.7.2' gem 'thor', '~> 1.2' @@ -17,7 +17,7 @@ gem 'makara', '~> 0.5' gem 'pghero' gem 'dotenv-rails', '~> 2.8' -gem 'aws-sdk-s3', '~> 1.122', require: false +gem 'aws-sdk-s3', '~> 1.123', require: false gem 'fog-core', '<= 2.4.0' gem 'fog-openstack', '~> 0.3', require: false gem 'kt-paperclip', '~> 7.1', github: 'kreeti/kt-paperclip', ref: '11abf222dc31bff71160a1d138b445214f434b2b' diff --git a/Gemfile.lock b/Gemfile.lock index 36f7d7201..7d04d875c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -109,17 +109,17 @@ GEM attr_required (1.0.1) awrence (1.2.1) aws-eventstream (1.2.0) - aws-partitions (1.761.0) - aws-sdk-core (3.172.0) + aws-partitions (1.772.0) + aws-sdk-core (3.174.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.64.0) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-kms (1.65.0) + aws-sdk-core (~> 3, >= 3.174.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.122.0) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-s3 (1.123.0) + aws-sdk-core (~> 3, >= 3.174.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.5.2) @@ -501,7 +501,7 @@ GEM premailer (~> 1.7, >= 1.7.9) private_address_check (0.5.0) public_suffix (5.0.1) - puma (6.2.2) + puma (6.3.0) nio4r (~> 2.0) pundit (2.3.0) activesupport (>= 3.0.0) @@ -544,8 +544,9 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) rails-i18n (6.0.0) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 7) @@ -588,7 +589,7 @@ GEM rspec-mocks (3.12.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-rails (6.0.2) + rspec-rails (6.0.3) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) @@ -648,7 +649,7 @@ GEM redis (>= 4.5.0, < 5) sidekiq-bulk (0.2.0) sidekiq - sidekiq-scheduler (5.0.2) + sidekiq-scheduler (5.0.3) rufus-scheduler (~> 3.2) sidekiq (>= 6, < 8) tilt (>= 1.4.0) @@ -770,7 +771,7 @@ DEPENDENCIES active_model_serializers (~> 0.10) addressable (~> 2.8) annotate (~> 3.2) - aws-sdk-s3 (~> 1.122) + aws-sdk-s3 (~> 1.123) better_errors (~> 2.9) binding_of_caller (~> 1.0) blurhash (~> 0.1) @@ -846,7 +847,7 @@ DEPENDENCIES premailer-rails private_address_check (~> 0.5) public_suffix (~> 5.0) - puma (~> 6.2) + puma (~> 6.3) pundit (~> 2.3) rack (~> 2.2.7) rack-attack (~> 6.6) diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb index bdbf8796f..983caf22f 100644 --- a/app/controllers/settings/imports_controller.rb +++ b/app/controllers/settings/imports_controller.rb @@ -12,6 +12,7 @@ class Settings::ImportsController < Settings::BaseController muting: 'muted_accounts_failures.csv', domain_blocking: 'blocked_domains_failures.csv', bookmarks: 'bookmarks_failures.csv', + lists: 'lists_failures.csv', }.freeze TYPE_TO_HEADERS_MAP = { @@ -20,6 +21,7 @@ class Settings::ImportsController < Settings::BaseController muting: ['Account address', 'Hide notifications'], domain_blocking: false, bookmarks: false, + lists: false, }.freeze def index @@ -49,6 +51,8 @@ class Settings::ImportsController < Settings::BaseController csv << [row.data['domain']] when :bookmarks csv << [row.data['uri']] + when :lists + csv << [row.data['list_name'], row.data['acct']] end end end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 3d5592867..ae89cec78 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -5,10 +5,6 @@ module SettingsHelper LanguagesHelper::SUPPORTED_LOCALES.keys end - def hash_to_object(hash) - HashObject.new(hash) - end - def session_device_icon(session) device = session.detection.device diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx index 68a456c1b..ea863f5d1 100644 --- a/app/javascript/mastodon/components/account.jsx +++ b/app/javascript/mastodon/components/account.jsx @@ -143,7 +143,7 @@ class Account extends ImmutablePureComponent { const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at')); if (firstVerifiedField) { - verification = <>· ; + verification = ; } return ( @@ -154,9 +154,13 @@ class Account extends ImmutablePureComponent { -
+
- {!minimal && <> {verification} {muteTimeRemaining}} + {!minimal && ( +
+ {verification} {muteTimeRemaining} +
+ )}
diff --git a/app/javascript/mastodon/components/poll.jsx b/app/javascript/mastodon/components/poll.jsx index fd2efd59c..dfc4034fa 100644 --- a/app/javascript/mastodon/components/poll.jsx +++ b/app/javascript/mastodon/components/poll.jsx @@ -57,9 +57,9 @@ class Poll extends ImmutablePureComponent { }; static getDerivedStateFromProps (props, state) { - const { poll, intl } = props; + const { poll } = props; const expires_at = poll.get('expires_at'); - const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < intl.now(); + const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < Date.now(); return (expired === state.expired) ? null : { expired }; } @@ -76,10 +76,10 @@ class Poll extends ImmutablePureComponent { } _setupTimer () { - const { poll, intl } = this.props; + const { poll } = this.props; clearTimeout(this._timer); if (!this.state.expired) { - const delay = (new Date(poll.get('expires_at'))).getTime() - intl.now(); + const delay = (new Date(poll.get('expires_at'))).getTime() - Date.now(); this._timer = setTimeout(() => { this.setState({ expired: true }); }, delay); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index fff9cb7ea..def4058e6 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -7814,13 +7814,28 @@ noscript { } } +.account__contents { + overflow: hidden; +} + +.account__details { + display: flex; + flex-wrap: wrap; + column-gap: 1em; +} + .verified-badge { display: inline-flex; align-items: center; color: $valid-value-color; gap: 4px; overflow: hidden; - text-overflow: ellipsis; + white-space: nowrap; + + > span { + overflow: hidden; + text-overflow: ellipsis; + } a { color: inherit; diff --git a/app/lib/hash_object.rb b/app/lib/hash_object.rb deleted file mode 100644 index 274c020ad..000000000 --- a/app/lib/hash_object.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -class HashObject - def initialize(hash) - hash.each do |k, v| - instance_variable_set("@#{k}", v) - self.class.send(:define_method, k, proc { instance_variable_get("@#{k}") }) - end - end -end diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb index af9a9670b..810e47184 100644 --- a/app/models/bulk_import.rb +++ b/app/models/bulk_import.rb @@ -30,6 +30,7 @@ class BulkImport < ApplicationRecord muting: 2, domain_blocking: 3, bookmarks: 4, + lists: 5, } enum state: { diff --git a/app/models/form/import.rb b/app/models/form/import.rb index 750ef84be..2fc74715b 100644 --- a/app/models/form/import.rb +++ b/app/models/form/import.rb @@ -18,6 +18,7 @@ class Form::Import muting: ['Account address', 'Hide notifications'], domain_blocking: ['#domain'], bookmarks: ['#uri'], + lists: ['List name', 'Account address'], }.freeze KNOWN_FIRST_HEADERS = EXPECTED_HEADERS_BY_TYPE.values.map(&:first).uniq.freeze @@ -30,6 +31,7 @@ class Form::Import 'Hide notifications' => 'hide_notifications', '#domain' => 'domain', '#uri' => 'uri', + 'List name' => 'list_name', }.freeze class EmptyFileError < StandardError; end @@ -48,6 +50,7 @@ class Form::Import return :muting if data.original_filename&.start_with?('mutes') || data.original_filename&.start_with?('muted_accounts') return :domain_blocking if data.original_filename&.start_with?('domain_blocks') || data.original_filename&.start_with?('blocked_domains') return :bookmarks if data.original_filename&.start_with?('bookmarks') + return :lists if data.original_filename&.start_with?('lists') end # Whether the uploaded CSV file seems to correspond to a different import type than the one selected @@ -76,14 +79,16 @@ class Form::Import private - def default_csv_header + def default_csv_headers case type.to_sym when :following, :blocking, :muting - 'Account address' + ['Account address'] when :domain_blocking - '#domain' + ['#domain'] when :bookmarks - '#uri' + ['#uri'] + when :lists + ['List name', 'Account address'] end end @@ -98,7 +103,7 @@ class Form::Import field&.split(',')&.map(&:strip)&.presence when 'Account address' field.strip.gsub(/\A@/, '') - when '#domain', '#uri' + when '#domain', '#uri', 'List name' field.strip else field @@ -109,7 +114,7 @@ class Form::Import @csv_data.take(1) # Ensure the headers are read raise EmptyFileError if @csv_data.headers == true - @csv_data = CSV.open(data.path, encoding: 'UTF-8', skip_blanks: true, headers: [default_csv_header], converters: csv_converter) unless KNOWN_FIRST_HEADERS.include?(@csv_data.headers&.first) + @csv_data = CSV.open(data.path, encoding: 'UTF-8', skip_blanks: true, headers: default_csv_headers, converters: csv_converter) unless KNOWN_FIRST_HEADERS.include?(@csv_data.headers&.first) @csv_data end @@ -133,7 +138,7 @@ class Form::Import def validate_data return if data.nil? return errors.add(:data, I18n.t('imports.errors.too_large')) if data.size > FILE_SIZE_LIMIT - return errors.add(:data, I18n.t('imports.errors.incompatible_type')) unless csv_data.headers.include?(default_csv_header) + return errors.add(:data, I18n.t('imports.errors.incompatible_type')) unless default_csv_headers.all? { |header| csv_data.headers.include?(header) } errors.add(:data, I18n.t('imports.errors.over_rows_processing_limit', count: ROWS_PROCESSING_LIMIT)) if csv_row_count > ROWS_PROCESSING_LIMIT diff --git a/app/services/bulk_import_row_service.rb b/app/services/bulk_import_row_service.rb index 4046ef4ee..ef4c18e78 100644 --- a/app/services/bulk_import_row_service.rb +++ b/app/services/bulk_import_row_service.rb @@ -7,7 +7,7 @@ class BulkImportRowService @type = row.bulk_import.type.to_sym case @type - when :following, :blocking, :muting + when :following, :blocking, :muting, :lists target_acct = @data['acct'] target_domain = domain(target_acct) @target_account = stoplight_wrap_request(target_domain) { ResolveAccountService.new.call(target_acct, { check_delivery_availability: true }) } @@ -33,6 +33,12 @@ class BulkImportRowService return false unless StatusPolicy.new(@account, @target_status).show? @account.bookmarks.find_or_create_by!(status: @target_status) + when :lists + list = @account.owned_lists.find_or_create_by!(title: @data['list_name']) + + FollowService.new.call(@account, @target_account) unless @account.id == @target_account.id + + list.accounts << @target_account end true diff --git a/app/services/bulk_import_service.rb b/app/services/bulk_import_service.rb index 2701b0c7e..5c14adc49 100644 --- a/app/services/bulk_import_service.rb +++ b/app/services/bulk_import_service.rb @@ -16,6 +16,8 @@ class BulkImportService < BaseService import_domain_blocks! when :bookmarks import_bookmarks! + when :lists + import_lists! end @import.update!(state: :finished, finished_at: Time.now.utc) if @import.processed_items == @import.total_items @@ -157,4 +159,24 @@ class BulkImportService < BaseService [row.id] end end + + def import_lists! + rows = @import.rows.to_a + + if @import.overwrite? + included_lists = rows.map { |row| row.data['list_name'] }.uniq + + @account.owned_lists.where.not(title: included_lists).destroy_all + + # As list membership changes do not retroactively change timeline + # contents, simplify things by just clearing everything + @account.owned_lists.find_each do |list| + list.list_accounts.destroy_all + end + end + + Import::RowWorker.push_bulk(rows) do |row| + [row.id] + end + end end diff --git a/app/views/settings/imports/index.html.haml b/app/views/settings/imports/index.html.haml index 02c3f4eb3..5f7950b59 100644 --- a/app/views/settings/imports/index.html.haml +++ b/app/views/settings/imports/index.html.haml @@ -3,7 +3,7 @@ = simple_form_for @import, url: settings_imports_path do |f| .field-group - = f.input :type, as: :grouped_select, collection: { constructive: %i(following bookmarks), destructive: %i(muting blocking domain_blocking) }, wrapper: :with_block_label, include_blank: false, label_method: ->(type) { I18n.t("imports.types.#{type}") }, group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") }, group_method: :last, hint: t('imports.preface') + = f.input :type, as: :grouped_select, collection: { constructive: %i(following bookmarks lists), destructive: %i(muting blocking domain_blocking) }, wrapper: :with_block_label, include_blank: false, label_method: ->(type) { I18n.t("imports.types.#{type}") }, group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") }, group_method: :last, hint: t('imports.preface') .fields-row .fields-group.fields-row__column.fields-row__column-6 diff --git a/config/initializers/twitter_regex.rb b/config/initializers/twitter_regex.rb index 6a7723fd2..e65b05dfd 100644 --- a/config/initializers/twitter_regex.rb +++ b/config/initializers/twitter_regex.rb @@ -25,7 +25,7 @@ module Twitter::TwitterText \) /iox UCHARS = '\u{A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E1000}-\u{EFFFD}\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}' - REGEXEN[:valid_url_query_chars] = /[a-z0-9!?\*'\(\);:&=\+\$\/%#\[\]\-_\.,~|@#{UCHARS}]/iou + REGEXEN[:valid_url_query_chars] = /[a-z0-9!?\*'\(\);:&=\+\$\/%#\[\]\-_\.,~|@\^#{UCHARS}]/iou REGEXEN[:valid_url_query_ending_chars] = /[a-z0-9_&=#\/\-#{UCHARS}]/iou REGEXEN[:valid_url_path] = /(?: (?: diff --git a/db/migrate/20230531153942_add_primary_key_to_accounts_tags_join_table.rb b/db/migrate/20230531153942_add_primary_key_to_accounts_tags_join_table.rb new file mode 100644 index 000000000..0c3c17a36 --- /dev/null +++ b/db/migrate/20230531153942_add_primary_key_to_accounts_tags_join_table.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class AddPrimaryKeyToAccountsTagsJoinTable < ActiveRecord::Migration[6.1] + disable_ddl_transaction! + + def up + ActiveRecord::Base.transaction do + safety_assured do + execute 'ALTER TABLE accounts_tags ADD PRIMARY KEY USING INDEX index_accounts_tags_on_tag_id_and_account_id' + + # Rename for consistency as the primary key's name is not represented in db/schema.rb + execute 'ALTER INDEX index_accounts_tags_on_tag_id_and_account_id RENAME TO accounts_tags_pkey' + end + end + end + + def down + safety_assured do + # I have found no way to demote the primary key to an index, instead, re-create the index + execute 'CREATE UNIQUE INDEX CONCURRENTLY index_accounts_tags_on_tag_id_and_account_id ON accounts_tags (tag_id, account_id)' + execute 'ALTER TABLE accounts_tags DROP CONSTRAINT accounts_tags_pkey' + end + end +end diff --git a/db/migrate/20230531154811_add_primary_key_to_statuses_tags_join_table.rb b/db/migrate/20230531154811_add_primary_key_to_statuses_tags_join_table.rb new file mode 100644 index 000000000..6581943da --- /dev/null +++ b/db/migrate/20230531154811_add_primary_key_to_statuses_tags_join_table.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class AddPrimaryKeyToStatusesTagsJoinTable < ActiveRecord::Migration[6.1] + disable_ddl_transaction! + + def up + ActiveRecord::Base.transaction do + safety_assured do + execute 'ALTER TABLE statuses_tags ADD PRIMARY KEY USING INDEX index_statuses_tags_on_tag_id_and_status_id' + + # Rename for consistency as the primary key's name is not represented in db/schema.rb + execute 'ALTER INDEX index_statuses_tags_on_tag_id_and_status_id RENAME TO statuses_tags_pkey' + end + end + end + + def down + safety_assured do + # I have found no way to demote the primary key to an index, instead, re-create the index + execute 'CREATE UNIQUE INDEX CONCURRENTLY index_statuses_tags_on_tag_id_and_status_id ON statuses_tags (tag_id, status_id)' + execute 'ALTER TABLE statuses_tags DROP CONSTRAINT statuses_tags_pkey' + end + end +end diff --git a/db/schema.rb b/db/schema.rb index b1fbaecab..11b5fb8d6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_05_24_194155) do +ActiveRecord::Schema.define(version: 2023_05_31_154811) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -194,11 +194,10 @@ ActiveRecord::Schema.define(version: 2023_05_24_194155) do t.index ["url"], name: "index_accounts_on_url", opclass: :text_pattern_ops, where: "(url IS NOT NULL)" end - create_table "accounts_tags", id: false, force: :cascade do |t| + create_table "accounts_tags", primary_key: ["tag_id", "account_id"], force: :cascade do |t| t.bigint "account_id", null: false t.bigint "tag_id", null: false t.index ["account_id", "tag_id"], name: "index_accounts_tags_on_account_id_and_tag_id" - t.index ["tag_id", "account_id"], name: "index_accounts_tags_on_tag_id_and_account_id", unique: true end create_table "admin_action_logs", force: :cascade do |t| @@ -982,11 +981,10 @@ ActiveRecord::Schema.define(version: 2023_05_24_194155) do t.index ["uri"], name: "index_statuses_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)" end - create_table "statuses_tags", id: false, force: :cascade do |t| + create_table "statuses_tags", primary_key: ["tag_id", "status_id"], force: :cascade do |t| t.bigint "status_id", null: false t.bigint "tag_id", null: false t.index ["status_id"], name: "index_statuses_tags_on_status_id" - t.index ["tag_id", "status_id"], name: "index_statuses_tags_on_tag_id_and_status_id", unique: true end create_table "system_keys", force: :cascade do |t| diff --git a/lib/mastodon/cli/base.rb b/lib/mastodon/cli/base.rb index f3c9fea92..32aff2fcc 100644 --- a/lib/mastodon/cli/base.rb +++ b/lib/mastodon/cli/base.rb @@ -4,16 +4,39 @@ require_relative '../../../config/boot' require_relative '../../../config/environment' require 'thor' -require_relative 'helper' +require_relative 'progress_helper' module Mastodon module CLI class Base < Thor - include CLI::Helper + include ProgressHelper def self.exit_on_failure? true end + + private + + def pastel + @pastel ||= Pastel.new + end + + def dry_run? + options[:dry_run] + end + + def dry_run_mode_suffix + dry_run? ? ' (DRY RUN)' : '' + end + + def reset_connection_pools! + ActiveRecord::Base.establish_connection( + ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first.configuration_hash + .dup + .tap { |config| config['pool'] = options[:concurrency] + 1 } + ) + RedisConfiguration.establish_pool(options[:concurrency]) + end end end end diff --git a/lib/mastodon/cli/helper.rb b/lib/mastodon/cli/progress_helper.rb similarity index 80% rename from lib/mastodon/cli/helper.rb rename to lib/mastodon/cli/progress_helper.rb index 78931b9a2..1fa2745c1 100644 --- a/lib/mastodon/cli/helper.rb +++ b/lib/mastodon/cli/progress_helper.rb @@ -9,23 +9,19 @@ HttpLog.configuration.logger = dev_null Paperclip.options[:log] = false Chewy.logger = dev_null -module Mastodon::CLI - module Helper - def dry_run? - options[:dry_run] - end +require 'ruby-progressbar/outputs/null' - def dry_run_mode_suffix - dry_run? ? ' (DRY RUN)' : '' - end +module Mastodon::CLI + module ProgressHelper + PROGRESS_FORMAT = '%c/%u |%b%i| %e' def create_progress_bar(total = nil) - ProgressBar.create(total: total, format: '%c/%u |%b%i| %e') - end - - def reset_connection_pools! - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env].dup.tap { |config| config['pool'] = options[:concurrency] + 1 }) - RedisConfiguration.establish_pool(options[:concurrency]) + ProgressBar.create( + { + total: total, + format: PROGRESS_FORMAT, + }.merge(progress_output_options) + ) end def parallelize_with_progress(scope) @@ -82,8 +78,10 @@ module Mastodon::CLI [total.value, aggregate.value] end - def pastel - @pastel ||= Pastel.new + private + + def progress_output_options + Rails.env.test? ? { output: ProgressBar::Outputs::Null } : {} end end end diff --git a/lib/mastodon/cli/search.rb b/lib/mastodon/cli/search.rb index 6f48dcb09..8d7b7202f 100644 --- a/lib/mastodon/cli/search.rb +++ b/lib/mastodon/cli/search.rb @@ -29,15 +29,7 @@ module Mastodon::CLI database will be imported into the indices, unless overridden with --no-import. LONG_DESC def deploy - if options[:concurrency] < 1 - say('Cannot run with this concurrency setting, must be at least 1', :red) - exit(1) - end - - if options[:batch_size] < 1 - say('Cannot run with this batch_size setting, must be at least 1', :red) - exit(1) - end + verify_deploy_options! indices = if options[:only] options[:only].map { |str| "#{str.camelize}Index".constantize } @@ -98,5 +90,26 @@ module Mastodon::CLI say("Indexed #{added} records, de-indexed #{removed}", :green, true) end + + private + + def verify_deploy_options! + verify_deploy_concurrency! + verify_deploy_batch_size! + end + + def verify_deploy_concurrency! + return unless options[:concurrency] < 1 + + say('Cannot run with this concurrency setting, must be at least 1', :red) + exit(1) + end + + def verify_deploy_batch_size! + return unless options[:batch_size] < 1 + + say('Cannot run with this batch_size setting, must be at least 1', :red) + exit(1) + end end end diff --git a/package.json b/package.json index 7fffd8a5c..f42a66cb0 100644 --- a/package.json +++ b/package.json @@ -140,12 +140,12 @@ "webpack-cli": "^3.3.12", "webpack-merge": "^5.9.0", "wicg-inert": "^3.1.2", - "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", + "workbox-expiration": "^7.0.0", + "workbox-precaching": "^7.0.0", + "workbox-routing": "^7.0.0", + "workbox-strategies": "^7.0.0", + "workbox-webpack-plugin": "^7.0.0", + "workbox-window": "^7.0.0", "ws": "^8.12.1" }, "devDependencies": { @@ -158,7 +158,7 @@ "@types/express": "^4.17.17", "@types/http-link-header": "^1.0.3", "@types/intl": "^1.2.0", - "@types/jest": "^29.5.1", + "@types/jest": "^29.5.2", "@types/js-yaml": "^4.0.5", "@types/lodash": "^4.14.195", "@types/npmlog": "^4.1.4", @@ -192,7 +192,7 @@ "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-formatjs": "^4.10.1", "eslint-plugin-import": "~2.27.5", - "eslint-plugin-jsdoc": "^45.0.0", + "eslint-plugin-jsdoc": "^46.1.0", "eslint-plugin-jsx-a11y": "~6.7.1", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "~6.1.1", diff --git a/spec/fixtures/files/lists.csv b/spec/fixtures/files/lists.csv new file mode 100644 index 000000000..3155ed6d5 --- /dev/null +++ b/spec/fixtures/files/lists.csv @@ -0,0 +1,3 @@ +Mastodon project,gargron@example.com +Mastodon project,mastodon@example.com +test,foo@example.com diff --git a/spec/lib/hash_object_spec.rb b/spec/lib/hash_object_spec.rb deleted file mode 100644 index ce1806520..000000000 --- a/spec/lib/hash_object_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe HashObject do - it 'has methods corresponding to hash properties' do - expect(HashObject.new(key: 'value').key).to eq 'value' - end -end diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb index 8eee6f538..ba49e480a 100644 --- a/spec/lib/mastodon/cli/accounts_spec.rb +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -662,4 +662,340 @@ describe Mastodon::CLI::Accounts do end end end + + describe '#refresh' do + context 'with --all option' do + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account_example_com) { Fabricate(:account, domain: 'example.com') } + let!(:account_example_net) { Fabricate(:account, domain: 'example.net') } + let(:scope) { Account.remote } + + before do + allow(cli).to receive(:parallelize_with_progress).and_yield(remote_account_example_com) + .and_yield(account_example_net) + .and_return([2, nil]) + cli.options = { all: true } + end + + it 'refreshes the avatar for all remote accounts' do + allow(remote_account_example_com).to receive(:reset_avatar!) + allow(account_example_net).to receive(:reset_avatar!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(remote_account_example_com).to have_received(:reset_avatar!).once + expect(account_example_net).to have_received(:reset_avatar!).once + end + + it 'does not refresh avatar for local accounts' do + allow(local_account).to receive(:reset_avatar!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(local_account).to_not have_received(:reset_avatar!) + end + + it 'refreshes the header for all remote accounts' do + allow(remote_account_example_com).to receive(:reset_header!) + allow(account_example_net).to receive(:reset_header!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(remote_account_example_com).to have_received(:reset_header!).once + expect(account_example_net).to have_received(:reset_header!).once + end + + it 'does not refresh the header for local accounts' do + allow(local_account).to receive(:reset_header!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(local_account).to_not have_received(:reset_header!) + end + + it 'displays a successful message' do + expect { cli.refresh }.to output( + a_string_including('Refreshed 2 accounts') + ).to_stdout + end + + context 'with --dry-run option' do + before do + cli.options = { all: true, dry_run: true } + end + + it 'does not refresh the avatar for any account' do + allow(local_account).to receive(:reset_avatar!) + allow(remote_account_example_com).to receive(:reset_avatar!) + allow(account_example_net).to receive(:reset_avatar!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(local_account).to_not have_received(:reset_avatar!) + expect(remote_account_example_com).to_not have_received(:reset_avatar!) + expect(account_example_net).to_not have_received(:reset_avatar!) + end + + it 'does not refresh the header for any account' do + allow(local_account).to receive(:reset_header!) + allow(remote_account_example_com).to receive(:reset_header!) + allow(account_example_net).to receive(:reset_header!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(local_account).to_not have_received(:reset_header!) + expect(remote_account_example_com).to_not have_received(:reset_header!) + expect(account_example_net).to_not have_received(:reset_header!) + end + + it 'displays a successful message with (DRY RUN)' do + expect { cli.refresh }.to output( + a_string_including('Refreshed 2 accounts (DRY RUN)') + ).to_stdout + end + end + end + + context 'with a list of accts' do + let!(:account_example_com_a) { Fabricate(:account, domain: 'example.com') } + let!(:account_example_com_b) { Fabricate(:account, domain: 'example.com') } + let!(:account_example_net) { Fabricate(:account, domain: 'example.net') } + let(:arguments) { [account_example_com_a.acct, account_example_com_b.acct] } + + before do + allow(Account).to receive(:find_remote).with(account_example_com_a.username, account_example_com_a.domain).and_return(account_example_com_a) + allow(Account).to receive(:find_remote).with(account_example_com_b.username, account_example_com_b.domain).and_return(account_example_com_b) + allow(Account).to receive(:find_remote).with(account_example_net.username, account_example_net.domain).and_return(account_example_net) + end + + it 'resets the avatar for the specified accounts' do + allow(account_example_com_a).to receive(:reset_avatar!) + allow(account_example_com_b).to receive(:reset_avatar!) + + cli.refresh(*arguments) + + expect(account_example_com_a).to have_received(:reset_avatar!).once + expect(account_example_com_b).to have_received(:reset_avatar!).once + end + + it 'does not reset the avatar for unspecified accounts' do + allow(account_example_net).to receive(:reset_avatar!) + + cli.refresh(*arguments) + + expect(account_example_net).to_not have_received(:reset_avatar!) + end + + it 'resets the header for the specified accounts' do + allow(account_example_com_a).to receive(:reset_header!) + allow(account_example_com_b).to receive(:reset_header!) + + cli.refresh(*arguments) + + expect(account_example_com_a).to have_received(:reset_header!).once + expect(account_example_com_b).to have_received(:reset_header!).once + end + + it 'does not reset the header for unspecified accounts' do + allow(account_example_net).to receive(:reset_header!) + + cli.refresh(*arguments) + + expect(account_example_net).to_not have_received(:reset_header!) + end + + context 'when an UnexpectedResponseError is raised' do + it 'displays a failure message' do + allow(account_example_com_a).to receive(:reset_avatar!).and_raise(Mastodon::UnexpectedResponseError) + + expect { cli.refresh(*arguments) } + .to output( + a_string_including("Account failed: #{account_example_com_a.username}@#{account_example_com_a.domain}") + ).to_stdout + end + end + + context 'when a specified account is not found' do + it 'exits with an error message' do + allow(Account).to receive(:find_remote).with(account_example_com_b.username, account_example_com_b.domain).and_return(nil) + + expect { cli.refresh(*arguments) }.to output( + a_string_including('No such account') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'with --dry-run option' do + before do + cli.options = { dry_run: true } + end + + it 'does not refresh the avatar for any account' do + allow(account_example_com_a).to receive(:reset_avatar!) + allow(account_example_com_b).to receive(:reset_avatar!) + + cli.refresh(*arguments) + + expect(account_example_com_a).to_not have_received(:reset_avatar!) + expect(account_example_com_b).to_not have_received(:reset_avatar!) + end + + it 'does not refresh the header for any account' do + allow(account_example_com_a).to receive(:reset_header!) + allow(account_example_com_b).to receive(:reset_header!) + + cli.refresh(*arguments) + + expect(account_example_com_a).to_not have_received(:reset_header!) + expect(account_example_com_b).to_not have_received(:reset_header!) + end + end + end + + context 'with --domain option' do + let!(:account_example_com_a) { Fabricate(:account, domain: 'example.com') } + let!(:account_example_com_b) { Fabricate(:account, domain: 'example.com') } + let!(:account_example_net) { Fabricate(:account, domain: 'example.net') } + let(:domain) { 'example.com' } + let(:scope) { Account.remote.where(domain: domain) } + + before do + allow(cli).to receive(:parallelize_with_progress).and_yield(account_example_com_a) + .and_yield(account_example_com_b) + .and_return([2, nil]) + + cli.options = { domain: domain } + end + + it 'refreshes the avatar for all accounts on specified domain' do + allow(account_example_com_a).to receive(:reset_avatar!) + allow(account_example_com_b).to receive(:reset_avatar!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(account_example_com_a).to have_received(:reset_avatar!).once + expect(account_example_com_b).to have_received(:reset_avatar!).once + end + + it 'does not refresh the avatar for accounts outside specified domain' do + allow(account_example_net).to receive(:reset_avatar!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(account_example_net).to_not have_received(:reset_avatar!) + end + + it 'refreshes the header for all accounts on specified domain' do + allow(account_example_com_a).to receive(:reset_header!) + allow(account_example_com_b).to receive(:reset_header!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope) + expect(account_example_com_a).to have_received(:reset_header!).once + expect(account_example_com_b).to have_received(:reset_header!).once + end + + it 'does not refresh the header for accounts outside specified domain' do + allow(account_example_net).to receive(:reset_header!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(account_example_net).to_not have_received(:reset_header!) + end + end + + context 'when neither a list of accts nor options are provided' do + it 'exits with an error message' do + expect { cli.refresh }.to output( + a_string_including('No account(s) given') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + describe '#rotate' do + context 'when neither username nor --all option are given' do + it 'exits with an error message' do + expect { cli.rotate }.to output( + a_string_including('No account(s) given') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when a username is given' do + let(:account) { Fabricate(:account) } + + it 'correctly rotates keys for the specified account' do + old_private_key = account.private_key + old_public_key = account.public_key + + cli.rotate(account.username) + account.reload + + expect(account.private_key).to_not eq(old_private_key) + expect(account.public_key).to_not eq(old_public_key) + end + + it 'broadcasts the new keys for the specified account' do + allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in) + + cli.rotate(account.username) + + expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once + end + + context 'when the given username is not found' do + it 'exits with an error message when the specified username is not found' do + expect { cli.rotate('non_existent_username') }.to output( + a_string_including('No such account') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + context 'when --all option is provided' do + let(:accounts) { Fabricate.times(3, :account) } + let(:options) { { all: true } } + + before do + allow(Account).to receive(:local).and_return(Account.where(id: accounts.map(&:id))) + cli.options = { all: true } + end + + it 'correctly rotates keys for all local accounts' do + old_private_keys = accounts.map(&:private_key) + old_public_keys = accounts.map(&:public_key) + + cli.rotate + accounts.each(&:reload) + + expect(accounts.map(&:private_key)).to_not eq(old_private_keys) + expect(accounts.map(&:public_key)).to_not eq(old_public_keys) + end + + it 'broadcasts the new keys for each account' do + allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in) + + cli.rotate + + accounts.each do |account| + expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once + end + end + end + end end diff --git a/spec/models/form/import_spec.rb b/spec/models/form/import_spec.rb index e1fea4205..52cf1c96e 100644 --- a/spec/models/form/import_spec.rb +++ b/spec/models/form/import_spec.rb @@ -86,6 +86,7 @@ RSpec.describe Form::Import do it_behaves_like 'too many CSV rows', 'muting', 'imports.txt', 1 it_behaves_like 'too many CSV rows', 'domain_blocking', 'domain_blocks.csv', 2 it_behaves_like 'too many CSV rows', 'bookmarks', 'bookmark-imports.txt', 3 + it_behaves_like 'too many CSV rows', 'lists', 'lists.csv', 2 # Importing list of addresses with no headers into various types it_behaves_like 'valid import', 'following', 'imports.txt' @@ -98,6 +99,9 @@ RSpec.describe Form::Import do # Importing bookmarks list with no headers into expected type it_behaves_like 'valid import', 'bookmarks', 'bookmark-imports.txt' + # Importing lists with no headers into expected type + it_behaves_like 'valid import', 'lists', 'lists.csv' + # Importing followed accounts with headers into various compatible types it_behaves_like 'valid import', 'following', 'following_accounts.csv' it_behaves_like 'valid import', 'blocking', 'following_accounts.csv' @@ -273,6 +277,12 @@ RSpec.describe Form::Import do { 'acct' => 'user@test.com', 'hide_notifications' => false }, ] + it_behaves_like 'on successful import', 'lists', 'merge', 'lists.csv', [ + { 'acct' => 'gargron@example.com', 'list_name' => 'Mastodon project' }, + { 'acct' => 'mastodon@example.com', 'list_name' => 'Mastodon project' }, + { 'acct' => 'foo@example.com', 'list_name' => 'test' }, + ] + # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users # # https://github.com/mastodon/mastodon/issues/20571 diff --git a/spec/services/bulk_import_row_service_spec.rb b/spec/services/bulk_import_row_service_spec.rb index 5bbe6b004..5e09845b5 100644 --- a/spec/services/bulk_import_row_service_spec.rb +++ b/spec/services/bulk_import_row_service_spec.rb @@ -91,5 +91,77 @@ RSpec.describe BulkImportRowService do end end end + + context 'when importing a list row' do + let(:import_type) { 'lists' } + let(:target_account) { Fabricate(:account) } + let(:data) do + { 'acct' => target_account.acct, 'list_name' => 'my list' } + end + + shared_examples 'common behavior' do + context 'when the target account is already followed' do + before do + account.follow!(target_account) + end + + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + + context 'when the user already requested to follow the target account' do + before do + account.request_follow!(target_account) + end + + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + + context 'when the target account is neither followed nor requested' do + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + + context 'when the target account is the user themself' do + let(:target_account) { account } + + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + end + + context 'when the list does not exist yet' do + include_examples 'common behavior' + end + + context 'when the list exists' do + before do + Fabricate(:list, account: account, title: 'my list') + end + + include_examples 'common behavior' + end + end end end diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb index 7016ecd3f..6495b323c 100644 --- a/spec/services/fetch_link_card_service_spec.rb +++ b/spec/services/fetch_link_card_service_spec.rb @@ -12,6 +12,7 @@ RSpec.describe FetchLinkCardService, type: :service do stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt')) stub_request(:get, 'http://example.com/日本語').to_return(request_fixture('sjis.txt')) stub_request(:get, 'https://github.com/qbi/WannaCry').to_return(status: 404) + stub_request(:get, 'http://example.com/test?data=file.gpx%5E1').to_return(status: 200) stub_request(:get, 'http://example.com/test-').to_return(request_fixture('idn.txt')) stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt')) @@ -87,6 +88,15 @@ RSpec.describe FetchLinkCardService, type: :service do expect(a_request(:get, 'http://example.com/sjis')).to_not have_been_made end end + + context do + let(:status) { Fabricate(:status, text: 'test http://example.com/test?data=file.gpx^1') } + + it 'does fetch URLs with a caret in search params' do + expect(a_request(:get, 'http://example.com/test?data=file.gpx')).to_not have_been_made + expect(a_request(:get, 'http://example.com/test?data=file.gpx%5E1')).to have_been_made.once + end + end end context 'with a remote status' do diff --git a/yarn.lock b/yarn.lock index 959fa8bd6..0cdfceccf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1743,6 +1743,15 @@ "@jridgewell/set-array" "^1.0.0" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping@^0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" @@ -1767,6 +1776,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/source-map@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda" + integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/sourcemap-codec@1.4.14": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" @@ -2231,10 +2248,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@*", "@types/jest@^29.5.1": - version "29.5.1" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.1.tgz#83c818aa9a87da27d6da85d3378e5a34d2f31a47" - integrity sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ== +"@types/jest@*", "@types/jest@^29.5.2": + version "29.5.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.2.tgz#86b4afc86e3a8f3005b297ed8a72494f89e6395b" + integrity sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -3007,9 +3024,9 @@ ansi-regex@^2.0.0: integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" @@ -5264,10 +5281,10 @@ eslint-plugin-import@~2.27.5: semver "^6.3.0" tsconfig-paths "^3.14.1" -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== +eslint-plugin-jsdoc@^46.1.0: + version "46.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.1.0.tgz#3ff932b70bc25f3745049f525a789faed7c948da" + integrity sha512-NpjpSuWR+Wwxzmssji7AVty1Vu0JvI7v+cTj+Rw1nKVjGv2eMvLGM/SI4VpgTXp82JbLtFOsA2QYLHT3YSmASA== dependencies: "@es-joy/jsdoccomment" "~0.39.4" are-docs-informative "^0.0.2" @@ -7860,9 +7877,9 @@ loader-utils@^1.2.3, loader-utils@^1.4.0: json5 "^1.0.1" loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" @@ -10782,7 +10799,7 @@ source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== -source-map@^0.8.0-beta.0, source-map@~0.8.0-beta.0: +source-map@^0.8.0-beta.0: version "0.8.0-beta.0" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== @@ -10863,9 +10880,9 @@ sprintf-js@~1.0.2: integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= ssri@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" - integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== dependencies: minipass "^3.1.1" @@ -11359,13 +11376,13 @@ terser-webpack-plugin@^1.4.3, terser-webpack-plugin@^4.2.3: webpack-sources "^1.4.3" terser@^5.0.0, terser@^5.3.4: - version "5.13.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.13.1.tgz#66332cdc5a01b04a224c9fad449fc1a18eaa1799" - integrity sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA== + version "5.17.6" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.6.tgz#d810e75e1bb3350c799cd90ebefe19c9412c12de" + integrity sha512-V8QHcs8YuyLkLHsJO5ucyff1ykrLVsR4dNnS//L5Y3NiSXpbK1J+WMVUs67eI0KTxs9JtHhgEQpXQVHlHI92DQ== dependencies: + "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" commander "^2.20.0" - source-map "~0.8.0-beta.0" source-map-support "~0.5.20" tesseract.js-core@^2.2.0: @@ -12267,25 +12284,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.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== +workbox-background-sync@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz#2b84b96ca35fec976e3bd2794b70e4acec46b3a5" + integrity sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA== dependencies: idb "^7.0.1" - workbox-core "6.6.1" + workbox-core "7.0.0" -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== +workbox-broadcast-update@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-7.0.0.tgz#7f611ca1a94ba8ac0aa40fa171c9713e0f937d22" + integrity sha512-oUuh4jzZrLySOo0tC0WoKiSg90bVAcnE98uW7F8GFiSOXnhogfNDGZelPJa+6KpGBO5+Qelv04Hqx2UD+BJqNQ== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -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== +workbox-build@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-7.0.0.tgz#02ab5ef2991b3369b8b9395703f08912212769b4" + integrity sha512-CttE7WCYW9sZC+nUYhQg3WzzGPr4IHmrPnjKiu3AMXsiNQKx+l4hHl63WTrnicLmKEKHScWDH8xsGBdrYgtBzg== dependencies: "@apideck/better-ajv-errors" "^0.3.1" "@babel/core" "^7.11.1" @@ -12309,132 +12326,132 @@ workbox-build@6.6.1: strip-comments "^2.0.1" tempy "^0.6.0" upath "^1.2.0" - 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-background-sync "7.0.0" + workbox-broadcast-update "7.0.0" + workbox-cacheable-response "7.0.0" + workbox-core "7.0.0" + workbox-expiration "7.0.0" + workbox-google-analytics "7.0.0" + workbox-navigation-preload "7.0.0" + workbox-precaching "7.0.0" + workbox-range-requests "7.0.0" + workbox-recipes "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" + workbox-streams "7.0.0" + workbox-sw "7.0.0" + workbox-window "7.0.0" -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== +workbox-cacheable-response@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-7.0.0.tgz#ee27c036728189eed69d25a135013053277482d2" + integrity sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -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-core@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-7.0.0.tgz#dec114ec923cc2adc967dd9be1b8a0bed50a3545" + integrity sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ== -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== +workbox-expiration@7.0.0, workbox-expiration@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-7.0.0.tgz#3d90bcf2a7577241de950f89784f6546b66c2baa" + integrity sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ== dependencies: idb "^7.0.1" - workbox-core "6.6.1" + workbox-core "7.0.0" -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== +workbox-google-analytics@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-7.0.0.tgz#603b2c4244af1e85de0fb26287d4e17d3293452a" + integrity sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg== dependencies: - workbox-background-sync "6.6.1" - workbox-core "6.6.1" - workbox-routing "6.6.1" - workbox-strategies "6.6.1" + workbox-background-sync "7.0.0" + workbox-core "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" -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== +workbox-navigation-preload@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-7.0.0.tgz#4913878dbbd97057181d57baa18d2bbdde085c6c" + integrity sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -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== +workbox-precaching@7.0.0, workbox-precaching@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-7.0.0.tgz#3979ba8033aadf3144b70e9fe631d870d5fbaa03" + integrity sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA== dependencies: - workbox-core "6.6.1" - workbox-routing "6.6.1" - workbox-strategies "6.6.1" + workbox-core "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" -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== +workbox-range-requests@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-7.0.0.tgz#97511901e043df27c1aa422adcc999a7751f52ed" + integrity sha512-SxAzoVl9j/zRU9OT5+IQs7pbJBOUOlriB8Gn9YMvi38BNZRbM+RvkujHMo8FOe9IWrqqwYgDFBfv6sk76I1yaQ== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -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== +workbox-recipes@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-7.0.0.tgz#1a6a01c8c2dfe5a41eef0fed3fe517e8a45c6514" + integrity sha512-DntcK9wuG3rYQOONWC0PejxYYIDHyWWZB/ueTbOUDQgefaeIj1kJ7pdP3LZV2lfrj8XXXBWt+JDRSw1lLLOnww== dependencies: - 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-cacheable-response "7.0.0" + workbox-core "7.0.0" + workbox-expiration "7.0.0" + workbox-precaching "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" -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== +workbox-routing@7.0.0, workbox-routing@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-7.0.0.tgz#6668438a06554f60645aedc77244a4fe3a91e302" + integrity sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -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== +workbox-strategies@7.0.0, workbox-strategies@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-7.0.0.tgz#dcba32b3f3074476019049cc490fe1a60ea73382" + integrity sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -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== +workbox-streams@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-7.0.0.tgz#36722aecd04785f88b6f709e541c094fc658c0f9" + integrity sha512-moVsh+5to//l6IERWceYKGiftc+prNnqOp2sgALJJFbnNVpTXzKISlTIsrWY+ogMqt+x1oMazIdHj25kBSq/HQ== dependencies: - workbox-core "6.6.1" - workbox-routing "6.6.1" + workbox-core "7.0.0" + workbox-routing "7.0.0" -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-sw@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-7.0.0.tgz#7350126411e3de1409f7ec243df8d06bb5b08b86" + integrity sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA== -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== +workbox-webpack-plugin@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-7.0.0.tgz#6c61661a2cacde1239192a5877a041a2943d1a55" + integrity sha512-R1ZzCHPfzeJjLK2/TpKUhxSQ3fFDCxlWxgRhhSjMQLz3G2MlBnyw/XeYb34e7SGgSv0qG22zEhMIzjMNqNeKbw== 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.6.1" + workbox-build "7.0.0" -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== +workbox-window@7.0.0, workbox-window@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-7.0.0.tgz#a683ab33c896e4f16786794eac7978fc98a25d08" + integrity sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA== dependencies: "@types/trusted-types" "^2.0.2" - workbox-core "6.6.1" + workbox-core "7.0.0" wrap-ansi@^5.1.0: version "5.1.0" @@ -12485,9 +12502,9 @@ write-file-atomic@^5.0.1: signal-exit "^4.0.1" ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + version "6.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== dependencies: async-limiter "~1.0.0"