Merge commit '14bb6bb29a
' into kb_migration
This commit is contained in:
commit
5b8ca44e3d
20 changed files with 726 additions and 169 deletions
|
@ -896,7 +896,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'
|
||||
|
@ -958,7 +957,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).
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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') || '/auth/sign_up',
|
||||
signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up',
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
|
|
@ -168,8 +168,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;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ const SignInBanner = () => {
|
|||
|
||||
let signupButton;
|
||||
|
||||
const signupUrl = useAppSelector((state) => state.getIn(['server', 'server', 'registrations', 'url'], '/auth/sign_up') || '/auth/sign_up');
|
||||
const signupUrl = useAppSelector((state) => state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up');
|
||||
|
||||
if (registrationsOpen) {
|
||||
signupButton = (
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -225,9 +225,8 @@ module Mastodon::CLI
|
|||
@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.'
|
||||
|
||||
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
|
||||
|
||||
|
@ -552,7 +551,16 @@ module Mastodon::CLI
|
|||
@prompt.warn 'All those accounts are distinct accounts but only the most recently-created one is fully-functional.'
|
||||
|
||||
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')
|
||||
@prompt.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.'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
14
package.json
14
package.json
|
@ -73,7 +73,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",
|
||||
|
@ -83,7 +83,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",
|
||||
|
@ -150,18 +150,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",
|
||||
|
@ -175,8 +175,8 @@
|
|||
"@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",
|
||||
|
|
|
@ -4,9 +4,577 @@ 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
|
||||
end
|
||||
|
|
143
yarn.lock
143
yarn.lock
|
@ -2087,10 +2087,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 +2156,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 +2203,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" "*"
|
||||
|
||||
|
@ -2298,10 +2293,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" "*"
|
||||
|
@ -2444,15 +2439,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"
|
||||
|
@ -2460,31 +2455,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"
|
||||
|
||||
|
@ -2493,10 +2488,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"
|
||||
|
@ -2511,30 +2506,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"
|
||||
|
||||
|
@ -2546,12 +2541,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":
|
||||
|
@ -7477,10 +7472,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"
|
||||
|
@ -9263,10 +9258,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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue