commit
fce885f271
5
Gemfile
5
Gemfile
|
@ -111,7 +111,7 @@ group :production, :test do
|
|||
end
|
||||
|
||||
group :test do
|
||||
gem 'capybara', '~> 3.24'
|
||||
gem 'capybara', '~> 3.25'
|
||||
gem 'climate_control', '~> 0.2'
|
||||
gem 'faker', '~> 1.9'
|
||||
gem 'microformats', '~> 4.1'
|
||||
|
@ -131,7 +131,7 @@ group :development do
|
|||
gem 'letter_opener', '~> 1.7'
|
||||
gem 'letter_opener_web', '~> 1.3'
|
||||
gem 'memory_profiler'
|
||||
gem 'rubocop', '~> 0.71', require: false
|
||||
gem 'rubocop', '~> 0.72', require: false
|
||||
gem 'rubocop-rails', '~> 2.0', require: false
|
||||
gem 'brakeman', '~> 4.5', require: false
|
||||
gem 'bundler-audit', '~> 0.6', require: false
|
||||
|
@ -151,3 +151,4 @@ group :production do
|
|||
end
|
||||
|
||||
gem 'concurrent-ruby', require: false
|
||||
gem 'connection_pool', require: false
|
||||
|
|
17
Gemfile.lock
17
Gemfile.lock
|
@ -106,7 +106,7 @@ GEM
|
|||
brakeman (4.5.1)
|
||||
browser (2.5.3)
|
||||
builder (3.2.3)
|
||||
bullet (6.0.0)
|
||||
bullet (6.0.1)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
bundler-audit (0.6.1)
|
||||
|
@ -129,7 +129,7 @@ GEM
|
|||
sshkit (~> 1.3)
|
||||
capistrano-yarn (2.0.2)
|
||||
capistrano (~> 3.0)
|
||||
capybara (3.24.0)
|
||||
capybara (3.25.0)
|
||||
addressable
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.8)
|
||||
|
@ -289,7 +289,7 @@ GEM
|
|||
idn-ruby (0.1.0)
|
||||
ipaddress (0.8.3)
|
||||
iso-639 (0.2.8)
|
||||
jaro_winkler (1.5.2)
|
||||
jaro_winkler (1.5.3)
|
||||
jmespath (1.4.0)
|
||||
json (2.1.0)
|
||||
json-ld (3.0.2)
|
||||
|
@ -338,7 +338,7 @@ GEM
|
|||
mimemagic (~> 0.3.2)
|
||||
mario-redis-lock (1.2.1)
|
||||
redis (>= 3.0.5)
|
||||
memory_profiler (0.9.13)
|
||||
memory_profiler (0.9.14)
|
||||
method_source (0.9.2)
|
||||
microformats (4.1.0)
|
||||
json (~> 2.1)
|
||||
|
@ -422,7 +422,7 @@ GEM
|
|||
pry (~> 0.10)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (3.1.0)
|
||||
public_suffix (3.1.1)
|
||||
puma (3.12.1)
|
||||
pundit (2.0.1)
|
||||
activesupport (>= 3.0.0)
|
||||
|
@ -527,7 +527,7 @@ GEM
|
|||
rspec-core (~> 3.0, >= 3.0.0)
|
||||
sidekiq (>= 2.4.0)
|
||||
rspec-support (3.8.0)
|
||||
rubocop (0.71.0)
|
||||
rubocop (0.72.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.6)
|
||||
|
@ -663,12 +663,13 @@ DEPENDENCIES
|
|||
capistrano-rails (~> 1.4)
|
||||
capistrano-rbenv (~> 2.1)
|
||||
capistrano-yarn (~> 2.0)
|
||||
capybara (~> 3.24)
|
||||
capybara (~> 3.25)
|
||||
charlock_holmes (~> 0.7.6)
|
||||
chewy (~> 5.0)
|
||||
cld3 (~> 3.2.4)
|
||||
climate_control (~> 0.2)
|
||||
concurrent-ruby
|
||||
connection_pool
|
||||
derailed_benchmarks
|
||||
devise (~> 4.6)
|
||||
devise-two-factor (~> 3.0)
|
||||
|
@ -742,7 +743,7 @@ DEPENDENCIES
|
|||
rqrcode (~> 0.10)
|
||||
rspec-rails (~> 3.8)
|
||||
rspec-sidekiq (~> 3.0)
|
||||
rubocop (~> 0.71)
|
||||
rubocop (~> 0.72)
|
||||
rubocop-rails (~> 2.0)
|
||||
sanitize (~> 5.0)
|
||||
sidekiq (~> 5.2)
|
||||
|
|
|
@ -5,8 +5,8 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
|
||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :destroy]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy]
|
||||
before_action :require_user!, except: [:show, :context, :card]
|
||||
before_action :set_status, only: [:show, :context, :card]
|
||||
before_action :require_user!, except: [:show, :context]
|
||||
before_action :set_status, only: [:show, :context]
|
||||
|
||||
respond_to :json
|
||||
|
||||
|
@ -33,16 +33,6 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
def card
|
||||
@card = @status.preview_cards.first
|
||||
|
||||
if @card.nil?
|
||||
render_empty
|
||||
else
|
||||
render json: @card, serializer: REST::PreviewCardSerializer
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@status = PostStatusService.new.call(current_user.account,
|
||||
text: status_params[:status],
|
||||
|
|
|
@ -197,7 +197,10 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
|
||||
handleFocus = () => {
|
||||
if (this.composeForm && !this.props.singleColumn) {
|
||||
this.composeForm.scrollIntoView();
|
||||
const { left, right } = this.composeForm.getBoundingClientRect();
|
||||
if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) {
|
||||
this.composeForm.scrollIntoView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
|||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchStatus } from 'flavours/glitch/actions/statuses';
|
||||
import MissingIndicator from 'flavours/glitch/components/missing_indicator';
|
||||
import DetailedStatus from './components/detailed_status';
|
||||
|
@ -61,39 +62,58 @@ const messages = defineMessages({
|
|||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
|
||||
const getAncestorsIds = createSelector([
|
||||
(_, { id }) => id,
|
||||
state => state.getIn(['contexts', 'inReplyTos']),
|
||||
], (statusId, inReplyTos) => {
|
||||
let ancestorsIds = Immutable.List();
|
||||
ancestorsIds = ancestorsIds.withMutations(mutable => {
|
||||
let id = statusId;
|
||||
|
||||
while (id) {
|
||||
mutable.unshift(id);
|
||||
id = inReplyTos.get(id);
|
||||
}
|
||||
});
|
||||
|
||||
return ancestorsIds;
|
||||
});
|
||||
|
||||
const getDescendantsIds = createSelector([
|
||||
(_, { id }) => id,
|
||||
state => state.getIn(['contexts', 'replies']),
|
||||
], (statusId, contextReplies) => {
|
||||
let descendantsIds = Immutable.List();
|
||||
descendantsIds = descendantsIds.withMutations(mutable => {
|
||||
const ids = [statusId];
|
||||
|
||||
while (ids.length > 0) {
|
||||
let id = ids.shift();
|
||||
const replies = contextReplies.get(id);
|
||||
|
||||
if (statusId !== id) {
|
||||
mutable.push(id);
|
||||
}
|
||||
|
||||
if (replies) {
|
||||
replies.reverse().forEach(reply => {
|
||||
ids.unshift(reply);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return descendantsIds;
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const status = getStatus(state, { id: props.params.statusId });
|
||||
let ancestorsIds = Immutable.List();
|
||||
let descendantsIds = Immutable.List();
|
||||
|
||||
if (status) {
|
||||
ancestorsIds = ancestorsIds.withMutations(mutable => {
|
||||
let id = status.get('in_reply_to_id');
|
||||
|
||||
while (id) {
|
||||
mutable.unshift(id);
|
||||
id = state.getIn(['contexts', 'inReplyTos', id]);
|
||||
}
|
||||
});
|
||||
|
||||
descendantsIds = descendantsIds.withMutations(mutable => {
|
||||
const ids = [status.get('id')];
|
||||
|
||||
while (ids.length > 0) {
|
||||
let id = ids.shift();
|
||||
const replies = state.getIn(['contexts', 'replies', id]);
|
||||
|
||||
if (status.get('id') !== id) {
|
||||
mutable.push(id);
|
||||
}
|
||||
|
||||
if (replies) {
|
||||
replies.reverse().forEach(reply => {
|
||||
ids.unshift(reply);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') });
|
||||
descendantsIds = getDescendantsIds(state, { id: status.get('id') });
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -275,6 +275,12 @@ const expandMentions = status => {
|
|||
return fragment.innerHTML;
|
||||
};
|
||||
|
||||
const expiresInFromExpiresAt = expires_at => {
|
||||
if (!expires_at) return 24 * 3600;
|
||||
const delta = (new Date(expires_at).getTime() - Date.now()) / 1000;
|
||||
return [300, 1800, 3600, 21600, 86400, 259200, 604800].find(expires_in => expires_in >= delta) || 24 * 3600;
|
||||
};
|
||||
|
||||
export default function compose(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case STORE_HYDRATE:
|
||||
|
@ -456,7 +462,7 @@ export default function compose(state = initialState, action) {
|
|||
map.set('poll', ImmutableMap({
|
||||
options: action.status.getIn(['poll', 'options']).map(x => x.get('title')),
|
||||
multiple: action.status.getIn(['poll', 'multiple']),
|
||||
expires_in: 24 * 3600,
|
||||
expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])),
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -118,7 +118,10 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
|
||||
handleFocus = () => {
|
||||
if (this.composeForm && !this.props.singleColumn) {
|
||||
this.composeForm.scrollIntoView();
|
||||
const { left, right } = this.composeForm.getBoundingClientRect();
|
||||
if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) {
|
||||
this.composeForm.scrollIntoView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
|||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchStatus } from '../../actions/statuses';
|
||||
import MissingIndicator from '../../components/missing_indicator';
|
||||
import DetailedStatus from './components/detailed_status';
|
||||
|
@ -63,39 +64,58 @@ const messages = defineMessages({
|
|||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
|
||||
const getAncestorsIds = createSelector([
|
||||
(_, { id }) => id,
|
||||
state => state.getIn(['contexts', 'inReplyTos']),
|
||||
], (statusId, inReplyTos) => {
|
||||
let ancestorsIds = Immutable.List();
|
||||
ancestorsIds = ancestorsIds.withMutations(mutable => {
|
||||
let id = statusId;
|
||||
|
||||
while (id) {
|
||||
mutable.unshift(id);
|
||||
id = inReplyTos.get(id);
|
||||
}
|
||||
});
|
||||
|
||||
return ancestorsIds;
|
||||
});
|
||||
|
||||
const getDescendantsIds = createSelector([
|
||||
(_, { id }) => id,
|
||||
state => state.getIn(['contexts', 'replies']),
|
||||
], (statusId, contextReplies) => {
|
||||
let descendantsIds = Immutable.List();
|
||||
descendantsIds = descendantsIds.withMutations(mutable => {
|
||||
const ids = [statusId];
|
||||
|
||||
while (ids.length > 0) {
|
||||
let id = ids.shift();
|
||||
const replies = contextReplies.get(id);
|
||||
|
||||
if (statusId !== id) {
|
||||
mutable.push(id);
|
||||
}
|
||||
|
||||
if (replies) {
|
||||
replies.reverse().forEach(reply => {
|
||||
ids.unshift(reply);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return descendantsIds;
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const status = getStatus(state, { id: props.params.statusId });
|
||||
let ancestorsIds = Immutable.List();
|
||||
let descendantsIds = Immutable.List();
|
||||
|
||||
if (status) {
|
||||
ancestorsIds = ancestorsIds.withMutations(mutable => {
|
||||
let id = status.get('in_reply_to_id');
|
||||
|
||||
while (id) {
|
||||
mutable.unshift(id);
|
||||
id = state.getIn(['contexts', 'inReplyTos', id]);
|
||||
}
|
||||
});
|
||||
|
||||
descendantsIds = descendantsIds.withMutations(mutable => {
|
||||
const ids = [status.get('id')];
|
||||
|
||||
while (ids.length > 0) {
|
||||
let id = ids.shift();
|
||||
const replies = state.getIn(['contexts', 'replies', id]);
|
||||
|
||||
if (status.get('id') !== id) {
|
||||
mutable.push(id);
|
||||
}
|
||||
|
||||
if (replies) {
|
||||
replies.reverse().forEach(reply => {
|
||||
ids.unshift(reply);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') });
|
||||
descendantsIds = getDescendantsIds(state, { id: status.get('id') });
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -195,6 +195,12 @@ const expandMentions = status => {
|
|||
return fragment.innerHTML;
|
||||
};
|
||||
|
||||
const expiresInFromExpiresAt = expires_at => {
|
||||
if (!expires_at) return 24 * 3600;
|
||||
const delta = (new Date(expires_at).getTime() - Date.now()) / 1000;
|
||||
return [300, 1800, 3600, 21600, 86400, 259200, 604800].find(expires_in => expires_in >= delta) || 24 * 3600;
|
||||
};
|
||||
|
||||
export default function compose(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case STORE_HYDRATE:
|
||||
|
@ -353,7 +359,7 @@ export default function compose(state = initialState, action) {
|
|||
map.set('poll', ImmutableMap({
|
||||
options: action.status.getIn(['poll', 'options']).map(x => x.get('title')),
|
||||
multiple: action.status.getIn(['poll', 'multiple']),
|
||||
expires_in: 24 * 3600,
|
||||
expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])),
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'connection_pool'
|
||||
require_relative './shared_timed_stack'
|
||||
|
||||
class ConnectionPool::SharedConnectionPool < ConnectionPool
|
||||
def initialize(options = {}, &block)
|
||||
super(options, &block)
|
||||
|
||||
@available = ConnectionPool::SharedTimedStack.new(@size, &block)
|
||||
end
|
||||
|
||||
delegate :size, :flush, to: :@available
|
||||
|
||||
def with(preferred_tag, options = {})
|
||||
Thread.handle_interrupt(Exception => :never) do
|
||||
conn = checkout(preferred_tag, options)
|
||||
|
||||
begin
|
||||
Thread.handle_interrupt(Exception => :immediate) do
|
||||
yield conn
|
||||
end
|
||||
ensure
|
||||
checkin(preferred_tag)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def checkout(preferred_tag, options = {})
|
||||
if ::Thread.current[key(preferred_tag)]
|
||||
::Thread.current[key_count(preferred_tag)] += 1
|
||||
::Thread.current[key(preferred_tag)]
|
||||
else
|
||||
::Thread.current[key_count(preferred_tag)] = 1
|
||||
::Thread.current[key(preferred_tag)] = @available.pop(preferred_tag, options[:timeout] || @timeout)
|
||||
end
|
||||
end
|
||||
|
||||
def checkin(preferred_tag)
|
||||
if ::Thread.current[key(preferred_tag)]
|
||||
if ::Thread.current[key_count(preferred_tag)] == 1
|
||||
@available.push(::Thread.current[key(preferred_tag)])
|
||||
::Thread.current[key(preferred_tag)] = nil
|
||||
else
|
||||
::Thread.current[key_count(preferred_tag)] -= 1
|
||||
end
|
||||
else
|
||||
raise ConnectionPool::Error, 'no connections are checked out'
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def key(tag)
|
||||
:"#{@key}-#{tag}"
|
||||
end
|
||||
|
||||
def key_count(tag)
|
||||
:"#{@key_count}-#{tag}"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,95 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ConnectionPool::SharedTimedStack
|
||||
def initialize(max = 0, &block)
|
||||
@create_block = block
|
||||
@max = max
|
||||
@created = 0
|
||||
@queue = []
|
||||
@tagged_queue = Hash.new { |hash, key| hash[key] = [] }
|
||||
@mutex = Mutex.new
|
||||
@resource = ConditionVariable.new
|
||||
end
|
||||
|
||||
def push(connection)
|
||||
@mutex.synchronize do
|
||||
store_connection(connection)
|
||||
@resource.broadcast
|
||||
end
|
||||
end
|
||||
|
||||
alias << push
|
||||
|
||||
def pop(preferred_tag, timeout = 5.0)
|
||||
deadline = current_time + timeout
|
||||
|
||||
@mutex.synchronize do
|
||||
loop do
|
||||
return fetch_preferred_connection(preferred_tag) unless @tagged_queue[preferred_tag].empty?
|
||||
|
||||
connection = try_create(preferred_tag)
|
||||
return connection if connection
|
||||
|
||||
to_wait = deadline - current_time
|
||||
raise Timeout::Error, "Waited #{timeout} sec" if to_wait <= 0
|
||||
|
||||
@resource.wait(@mutex, to_wait)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def empty?
|
||||
size.zero?
|
||||
end
|
||||
|
||||
def size
|
||||
@mutex.synchronize do
|
||||
@queue.size
|
||||
end
|
||||
end
|
||||
|
||||
def flush
|
||||
@mutex.synchronize do
|
||||
@queue.delete_if do |connection|
|
||||
delete = !connection.in_use && (connection.dead || connection.seconds_idle >= RequestPool::MAX_IDLE_TIME)
|
||||
|
||||
if delete
|
||||
@tagged_queue[connection.site].delete(connection)
|
||||
connection.close
|
||||
@created -= 1
|
||||
end
|
||||
|
||||
delete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def try_create(preferred_tag)
|
||||
if @created == @max && !@queue.empty?
|
||||
throw_away_connection = @queue.pop
|
||||
@tagged_queue[throw_away_connection.site].delete(throw_away_connection)
|
||||
@create_block.call(preferred_tag)
|
||||
elsif @created != @max
|
||||
connection = @create_block.call(preferred_tag)
|
||||
@created += 1
|
||||
connection
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_preferred_connection(preferred_tag)
|
||||
connection = @tagged_queue[preferred_tag].pop
|
||||
@queue.delete(connection)
|
||||
connection
|
||||
end
|
||||
|
||||
def current_time
|
||||
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
end
|
||||
|
||||
def store_connection(connection)
|
||||
@tagged_queue[connection.site].push(connection)
|
||||
@queue.push(connection)
|
||||
end
|
||||
end
|
|
@ -17,15 +17,22 @@ end
|
|||
class Request
|
||||
REQUEST_TARGET = '(request-target)'
|
||||
|
||||
# We enforce a 5s timeout on DNS resolving, 5s timeout on socket opening
|
||||
# and 5s timeout on the TLS handshake, meaning the worst case should take
|
||||
# about 15s in total
|
||||
TIMEOUT = { connect: 5, read: 10, write: 10 }.freeze
|
||||
|
||||
include RoutingHelper
|
||||
|
||||
def initialize(verb, url, **options)
|
||||
raise ArgumentError if url.blank?
|
||||
|
||||
@verb = verb
|
||||
@url = Addressable::URI.parse(url).normalize
|
||||
@options = options.merge(use_proxy? ? Rails.configuration.x.http_client_proxy : { socket_class: Socket })
|
||||
@headers = {}
|
||||
@verb = verb
|
||||
@url = Addressable::URI.parse(url).normalize
|
||||
@http_client = options.delete(:http_client)
|
||||
@options = options.merge(socket_class: use_proxy? ? ProxySocket : Socket)
|
||||
@options = @options.merge(Rails.configuration.x.http_client_proxy) if use_proxy?
|
||||
@headers = {}
|
||||
|
||||
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if block_hidden_service?
|
||||
|
||||
|
@ -50,15 +57,24 @@ class Request
|
|||
|
||||
def perform
|
||||
begin
|
||||
response = http_client.headers(headers).public_send(@verb, @url.to_s, @options)
|
||||
response = http_client.public_send(@verb, @url.to_s, @options.merge(headers: headers))
|
||||
rescue => e
|
||||
raise e.class, "#{e.message} on #{@url}", e.backtrace[0]
|
||||
end
|
||||
|
||||
begin
|
||||
yield response.extend(ClientLimit) if block_given?
|
||||
response = response.extend(ClientLimit)
|
||||
|
||||
# If we are using a persistent connection, we have to
|
||||
# read every response to be able to move forward at all.
|
||||
# However, simply calling #to_s or #flush may not be safe,
|
||||
# as the response body, if malicious, could be too big
|
||||
# for our memory. So we use the #body_with_limit method
|
||||
response.body_with_limit if http_client.persistent?
|
||||
|
||||
yield response if block_given?
|
||||
ensure
|
||||
http_client.close
|
||||
http_client.close unless http_client.persistent?
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -76,6 +92,10 @@ class Request
|
|||
|
||||
%w(http https).include?(parsed_url.scheme) && parsed_url.host.present?
|
||||
end
|
||||
|
||||
def http_client
|
||||
HTTP.use(:auto_inflate).timeout(:per_operation, TIMEOUT.dup).follow(max_hops: 2)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -116,16 +136,8 @@ class Request
|
|||
end
|
||||
end
|
||||
|
||||
def timeout
|
||||
# We enforce a 1s timeout on DNS resolving, 10s timeout on socket opening
|
||||
# and 5s timeout on the TLS handshake, meaning the worst case should take
|
||||
# about 16s in total
|
||||
|
||||
{ connect: 5, read: 10, write: 10 }
|
||||
end
|
||||
|
||||
def http_client
|
||||
@http_client ||= HTTP.use(:auto_inflate).timeout(:per_operation, timeout).follow(max_hops: 2)
|
||||
@http_client ||= Request.http_client
|
||||
end
|
||||
|
||||
def use_proxy?
|
||||
|
@ -166,26 +178,49 @@ class Request
|
|||
class Socket < TCPSocket
|
||||
class << self
|
||||
def open(host, *args)
|
||||
return super(host, *args) if thru_hidden_service?(host)
|
||||
|
||||
outer_e = nil
|
||||
port = args.first
|
||||
|
||||
Resolv::DNS.open do |dns|
|
||||
dns.timeouts = 5
|
||||
addresses = []
|
||||
begin
|
||||
addresses = [IPAddr.new(host)]
|
||||
rescue IPAddr::InvalidAddressError
|
||||
Resolv::DNS.open do |dns|
|
||||
dns.timeouts = 5
|
||||
addresses = dns.getaddresses(host).take(2)
|
||||
end
|
||||
end
|
||||
|
||||
addresses = dns.getaddresses(host).take(2)
|
||||
time_slot = 10.0 / addresses.size
|
||||
addresses.each do |address|
|
||||
begin
|
||||
check_private_address(address)
|
||||
|
||||
sock = ::Socket.new(address.is_a?(Resolv::IPv6) ? ::Socket::AF_INET6 : ::Socket::AF_INET, ::Socket::SOCK_STREAM, 0)
|
||||
sockaddr = ::Socket.pack_sockaddr_in(port, address.to_s)
|
||||
|
||||
sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
|
||||
|
||||
addresses.each do |address|
|
||||
begin
|
||||
raise Mastodon::HostValidationError if PrivateAddressCheck.private_address?(IPAddr.new(address.to_s))
|
||||
|
||||
::Timeout.timeout(time_slot, HTTP::TimeoutError) do
|
||||
return super(address.to_s, *args)
|
||||
sock.connect_nonblock(sockaddr)
|
||||
rescue IO::WaitWritable
|
||||
if IO.select(nil, [sock], nil, Request::TIMEOUT[:connect])
|
||||
begin
|
||||
sock.connect_nonblock(sockaddr)
|
||||
rescue Errno::EISCONN
|
||||
# Yippee!
|
||||
rescue
|
||||
sock.close
|
||||
raise
|
||||
end
|
||||
else
|
||||
sock.close
|
||||
raise HTTP::TimeoutError, "Connect timed out after #{Request::TIMEOUT[:connect]} seconds"
|
||||
end
|
||||
rescue => e
|
||||
outer_e = e
|
||||
end
|
||||
|
||||
return sock
|
||||
rescue => e
|
||||
outer_e = e
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -198,11 +233,21 @@ class Request
|
|||
|
||||
alias new open
|
||||
|
||||
def thru_hidden_service?(host)
|
||||
Rails.configuration.x.access_to_hidden_service && /\.(onion|i2p)$/.match(host)
|
||||
def check_private_address(address)
|
||||
raise Mastodon::HostValidationError if PrivateAddressCheck.private_address?(IPAddr.new(address.to_s))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private_constant :ClientLimit, :Socket
|
||||
class ProxySocket < Socket
|
||||
class << self
|
||||
def check_private_address(_address)
|
||||
# Accept connections to private addresses as HTTP proxies will usually
|
||||
# be on local addresses
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private_constant :ClientLimit, :Socket, :ProxySocket
|
||||
end
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative './connection_pool/shared_connection_pool'
|
||||
|
||||
class RequestPool
|
||||
def self.current
|
||||
@current ||= RequestPool.new
|
||||
end
|
||||
|
||||
class Reaper
|
||||
attr_reader :pool, :frequency
|
||||
|
||||
def initialize(pool, frequency)
|
||||
@pool = pool
|
||||
@frequency = frequency
|
||||
end
|
||||
|
||||
def run
|
||||
return unless frequency&.positive?
|
||||
|
||||
Thread.new(frequency, pool) do |t, p|
|
||||
loop do
|
||||
sleep t
|
||||
p.flush
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
MAX_IDLE_TIME = 30
|
||||
WAIT_TIMEOUT = 5
|
||||
MAX_POOL_SIZE = ENV.fetch('MAX_REQUEST_POOL_SIZE', 512).to_i
|
||||
|
||||
class Connection
|
||||
attr_reader :site, :last_used_at, :created_at, :in_use, :dead, :fresh
|
||||
|
||||
def initialize(site)
|
||||
@site = site
|
||||
@http_client = http_client
|
||||
@last_used_at = nil
|
||||
@created_at = current_time
|
||||
@dead = false
|
||||
@fresh = true
|
||||
end
|
||||
|
||||
def use
|
||||
@last_used_at = current_time
|
||||
@in_use = true
|
||||
|
||||
retries = 0
|
||||
|
||||
begin
|
||||
yield @http_client
|
||||
rescue HTTP::ConnectionError
|
||||
# It's possible the connection was closed, so let's
|
||||
# try re-opening it once
|
||||
|
||||
close
|
||||
|
||||
if @fresh || retries.positive?
|
||||
raise
|
||||
else
|
||||
@http_client = http_client
|
||||
retries += 1
|
||||
retry
|
||||
end
|
||||
rescue StandardError
|
||||
# If this connection raises errors of any kind, it's
|
||||
# better if it gets reaped as soon as possible
|
||||
|
||||
close
|
||||
@dead = true
|
||||
raise
|
||||
end
|
||||
ensure
|
||||
@fresh = false
|
||||
@in_use = false
|
||||
end
|
||||
|
||||
def seconds_idle
|
||||
current_time - (@last_used_at || @created_at)
|
||||
end
|
||||
|
||||
def close
|
||||
@http_client.close
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def http_client
|
||||
Request.http_client.persistent(@site, timeout: MAX_IDLE_TIME)
|
||||
end
|
||||
|
||||
def current_time
|
||||
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize
|
||||
@pool = ConnectionPool::SharedConnectionPool.new(size: MAX_POOL_SIZE, timeout: WAIT_TIMEOUT) { |site| Connection.new(site) }
|
||||
@reaper = Reaper.new(self, 30)
|
||||
@reaper.run
|
||||
end
|
||||
|
||||
def with(site, &block)
|
||||
@pool.with(site) do |connection|
|
||||
ActiveSupport::Notifications.instrument('with.request_pool', miss: connection.fresh, host: connection.site) do
|
||||
connection.use(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
delegate :size, :flush, to: :@pool
|
||||
end
|
|
@ -3,9 +3,11 @@
|
|||
class SidekiqErrorHandler
|
||||
def call(*)
|
||||
yield
|
||||
rescue Mastodon::HostValidationError => e
|
||||
Rails.logger.error "#{e.class}: #{e.message}"
|
||||
Rails.logger.error e.backtrace.join("\n")
|
||||
rescue Mastodon::HostValidationError
|
||||
# Do not retry
|
||||
ensure
|
||||
socket = Thread.current[:statsd_socket]
|
||||
socket&.close
|
||||
Thread.current[:statsd_socket] = nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,10 +17,13 @@ class Admin::AccountAction
|
|||
:type,
|
||||
:text,
|
||||
:report_id,
|
||||
:warning_preset_id,
|
||||
:send_email_notification
|
||||
:warning_preset_id
|
||||
|
||||
attr_reader :warning
|
||||
attr_reader :warning, :send_email_notification
|
||||
|
||||
def send_email_notification=(value)
|
||||
@send_email_notification = ActiveModel::Type::Boolean.new.cast(value)
|
||||
end
|
||||
|
||||
def save!
|
||||
ApplicationRecord.transaction do
|
||||
|
|
|
@ -60,7 +60,9 @@ module Attachmentable
|
|||
end
|
||||
|
||||
def calculated_content_type(attachment)
|
||||
Paperclip.run('file', '-b --mime :file', file: attachment.queued_for_write[:original].path).split(/[:;\s]+/).first.chomp
|
||||
content_type = Paperclip.run('file', '-b --mime :file', file: attachment.queued_for_write[:original].path).split(/[:;\s]+/).first.chomp
|
||||
content_type = 'video/mp4' if content_type == 'video/x-m4v'
|
||||
content_type
|
||||
rescue Terrapin::CommandLineError
|
||||
''
|
||||
end
|
||||
|
|
|
@ -35,6 +35,13 @@ class CustomFilter < ApplicationRecord
|
|||
before_validation :clean_up_contexts
|
||||
after_commit :remove_cache
|
||||
|
||||
def expires_in
|
||||
return @expires_in if defined?(@expires_in)
|
||||
return nil if expires_at.nil?
|
||||
|
||||
[30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].find { |expires_in| expires_in.from_now >= expires_at }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clean_up_contexts
|
||||
|
|
|
@ -15,6 +15,8 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
@domain = domain
|
||||
@collections = {}
|
||||
|
||||
return if auto_suspend?
|
||||
|
||||
RedisLock.acquire(lock_options) do |lock|
|
||||
if lock.acquired?
|
||||
@account = Account.find_remote(@username, @domain)
|
||||
|
@ -55,7 +57,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
@account.domain = @domain
|
||||
@account.private_key = nil
|
||||
@account.suspended_at = domain_block.created_at if auto_suspend?
|
||||
@account.silenced_at = domain_block.created_at if auto_silence?
|
||||
@account.silenced_at = domain_block.created_at if auto_silence?
|
||||
end
|
||||
|
||||
def update_account
|
||||
|
|
|
@ -48,7 +48,7 @@ class ResolveAccountService < BaseService
|
|||
return
|
||||
end
|
||||
|
||||
return if links_missing?
|
||||
return if links_missing? || auto_suspend?
|
||||
return Account.find_local(@username) if TagManager.instance.local_domain?(@domain)
|
||||
|
||||
RedisLock.acquire(lock_options) do |lock|
|
||||
|
|
|
@ -17,6 +17,7 @@ class ActivityPub::DeliveryWorker
|
|||
@json = json
|
||||
@source_account = Account.find(source_account_id)
|
||||
@inbox_url = inbox_url
|
||||
@host = Addressable::URI.parse(inbox_url).normalized_site
|
||||
|
||||
perform_request
|
||||
|
||||
|
@ -28,16 +29,18 @@ class ActivityPub::DeliveryWorker
|
|||
|
||||
private
|
||||
|
||||
def build_request
|
||||
request = Request.new(:post, @inbox_url, body: @json)
|
||||
def build_request(http_client)
|
||||
request = Request.new(:post, @inbox_url, body: @json, http_client: http_client)
|
||||
request.on_behalf_of(@source_account, :uri, sign_with: @options[:sign_with])
|
||||
request.add_headers(HEADERS)
|
||||
end
|
||||
|
||||
def perform_request
|
||||
light = Stoplight(@inbox_url) do
|
||||
build_request.perform do |response|
|
||||
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response)
|
||||
request_pool.with(@host) do |http_client|
|
||||
build_request(http_client).perform do |response|
|
||||
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -51,10 +54,14 @@ class ActivityPub::DeliveryWorker
|
|||
end
|
||||
|
||||
def response_error_unsalvageable?(response)
|
||||
(400...500).cover?(response.code) && ![401, 408, 429].include?(response.code)
|
||||
response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code))
|
||||
end
|
||||
|
||||
def failure_tracker
|
||||
@failure_tracker ||= DeliveryFailureTracker.new(@inbox_url)
|
||||
end
|
||||
|
||||
def request_pool
|
||||
RequestPool.current
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
instrumentation_hostname = ENV.fetch('INSTRUMENTATION_HOSTNAME') { 'localhost' }
|
||||
|
||||
ActiveSupport::Notifications.subscribe(/process_action.action_controller/) do |*args|
|
||||
event = ActiveSupport::Notifications::Event.new(*args)
|
||||
controller = event.payload[:controller]
|
||||
action = event.payload[:action]
|
||||
format = event.payload[:format] || 'all'
|
||||
format = 'all' if format == '*/*'
|
||||
status = event.payload[:status]
|
||||
key = "#{controller}.#{action}.#{format}.#{instrumentation_hostname}"
|
||||
|
||||
ActiveSupport::Notifications.instrument :performance, action: :measure, measurement: "#{key}.total_duration", value: event.duration
|
||||
ActiveSupport::Notifications.instrument :performance, action: :measure, measurement: "#{key}.db_time", value: event.payload[:db_runtime]
|
||||
ActiveSupport::Notifications.instrument :performance, action: :measure, measurement: "#{key}.view_time", value: event.payload[:view_runtime]
|
||||
ActiveSupport::Notifications.instrument :performance, measurement: "#{key}.status.#{status}"
|
||||
end
|
|
@ -3,10 +3,10 @@
|
|||
if ENV['STATSD_ADDR'].present?
|
||||
host, port = ENV['STATSD_ADDR'].split(':')
|
||||
|
||||
statsd = ::Statsd.new(host, port)
|
||||
statsd.namespace = ENV.fetch('STATSD_NAMESPACE') { ['Mastodon', Rails.env].join('.') }
|
||||
$statsd = ::Statsd.new(host, port)
|
||||
$statsd.namespace = ENV.fetch('STATSD_NAMESPACE') { ['Mastodon', Rails.env].join('.') }
|
||||
|
||||
::NSA.inform_statsd(statsd) do |informant|
|
||||
::NSA.inform_statsd($statsd) do |informant|
|
||||
informant.collect(:action_controller, :web)
|
||||
informant.collect(:active_record, :db)
|
||||
informant.collect(:active_support_cache, :cache)
|
||||
|
|
|
@ -299,7 +299,6 @@ Rails.application.routes.draw do
|
|||
|
||||
member do
|
||||
get :context
|
||||
get :card
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -362,7 +361,6 @@ Rails.application.routes.draw do
|
|||
resources :notifications, only: [:index, :show, :destroy] do
|
||||
collection do
|
||||
post :clear
|
||||
post :dismiss # Deprecated
|
||||
delete :destroy_multiple
|
||||
end
|
||||
|
||||
|
|
|
@ -164,7 +164,7 @@
|
|||
"webpack": "^4.34.0",
|
||||
"webpack-assets-manifest": "^3.1.1",
|
||||
"webpack-bundle-analyzer": "^3.3.2",
|
||||
"webpack-cli": "^3.3.4",
|
||||
"webpack-cli": "^3.3.5",
|
||||
"webpack-merge": "^4.2.1",
|
||||
"websocket.js": "^0.1.12"
|
||||
},
|
||||
|
@ -176,7 +176,7 @@
|
|||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-import": "~2.17.3",
|
||||
"eslint-plugin-jsx-a11y": "~6.2.1",
|
||||
"eslint-plugin-promise": "~4.1.1",
|
||||
"eslint-plugin-promise": "~4.2.1",
|
||||
"eslint-plugin-react": "~7.12.1",
|
||||
"jest": "^24.8.0",
|
||||
"raf": "^3.4.1",
|
||||
|
|
|
@ -91,13 +91,6 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
|
|||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #card' do
|
||||
it 'returns http unautharized' do
|
||||
get :card, params: { id: status.id }
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a public status' do
|
||||
|
@ -120,13 +113,6 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
|
|||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #card' do
|
||||
it 'returns http success' do
|
||||
get :card, params: { id: status.id }
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ConnectionPool::SharedConnectionPool do
|
||||
class MiniConnection
|
||||
attr_reader :site
|
||||
|
||||
def initialize(site)
|
||||
@site = site
|
||||
end
|
||||
end
|
||||
|
||||
subject { described_class.new(size: 5, timeout: 5) { |site| MiniConnection.new(site) } }
|
||||
|
||||
describe '#with' do
|
||||
it 'runs a block with a connection' do
|
||||
block_run = false
|
||||
|
||||
subject.with('foo') do |connection|
|
||||
expect(connection).to be_a MiniConnection
|
||||
block_run = true
|
||||
end
|
||||
|
||||
expect(block_run).to be true
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ConnectionPool::SharedTimedStack do
|
||||
class MiniConnection
|
||||
attr_reader :site
|
||||
|
||||
def initialize(site)
|
||||
@site = site
|
||||
end
|
||||
end
|
||||
|
||||
subject { described_class.new(5) { |site| MiniConnection.new(site) } }
|
||||
|
||||
describe '#push' do
|
||||
it 'keeps the connection in the stack' do
|
||||
subject.push(MiniConnection.new('foo'))
|
||||
expect(subject.size).to eq 1
|
||||
end
|
||||
end
|
||||
|
||||
describe '#pop' do
|
||||
it 'returns a connection' do
|
||||
expect(subject.pop('foo')).to be_a MiniConnection
|
||||
end
|
||||
|
||||
it 'returns the same connection that was pushed in' do
|
||||
connection = MiniConnection.new('foo')
|
||||
subject.push(connection)
|
||||
expect(subject.pop('foo')).to be connection
|
||||
end
|
||||
|
||||
it 'does not create more than maximum amount of connections' do
|
||||
expect { 6.times { subject.pop('foo', 0) } }.to raise_error Timeout::Error
|
||||
end
|
||||
|
||||
it 'repurposes a connection for a different site when maximum amount is reached' do
|
||||
5.times { subject.push(MiniConnection.new('foo')) }
|
||||
expect(subject.pop('bar')).to be_a MiniConnection
|
||||
end
|
||||
end
|
||||
|
||||
describe '#empty?' do
|
||||
it 'returns true when no connections on the stack' do
|
||||
expect(subject.empty?).to be true
|
||||
end
|
||||
|
||||
it 'returns false when there are connections on the stack' do
|
||||
subject.push(MiniConnection.new('foo'))
|
||||
expect(subject.empty?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#size' do
|
||||
it 'returns the number of connections on the stack' do
|
||||
2.times { subject.push(MiniConnection.new('foo')) }
|
||||
expect(subject.size).to eq 2
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe RequestPool do
|
||||
subject { described_class.new }
|
||||
|
||||
describe '#with' do
|
||||
it 'returns a HTTP client for a host' do
|
||||
subject.with('http://example.com') do |http_client|
|
||||
expect(http_client).to be_a HTTP::Client
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns the same instance of HTTP client within the same thread for the same host' do
|
||||
test_client = nil
|
||||
|
||||
subject.with('http://example.com') { |http_client| test_client = http_client }
|
||||
expect(test_client).to_not be_nil
|
||||
subject.with('http://example.com') { |http_client| expect(http_client).to be test_client }
|
||||
end
|
||||
|
||||
it 'returns different HTTP clients for different hosts' do
|
||||
test_client = nil
|
||||
|
||||
subject.with('http://example.com') { |http_client| test_client = http_client }
|
||||
expect(test_client).to_not be_nil
|
||||
subject.with('http://example.org') { |http_client| expect(http_client).to_not be test_client }
|
||||
end
|
||||
|
||||
it 'grows to the number of threads accessing it' do
|
||||
stub_request(:get, 'http://example.com/').to_return(status: 200, body: 'Hello!')
|
||||
|
||||
subject
|
||||
|
||||
threads = 20.times.map do |i|
|
||||
Thread.new do
|
||||
20.times do
|
||||
subject.with('http://example.com') do |http_client|
|
||||
http_client.get('/').flush
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
threads.map(&:join)
|
||||
|
||||
expect(subject.size).to be > 1
|
||||
end
|
||||
|
||||
it 'closes idle connections' do
|
||||
stub_request(:get, 'http://example.com/').to_return(status: 200, body: 'Hello!')
|
||||
|
||||
subject.with('http://example.com') do |http_client|
|
||||
http_client.get('/').flush
|
||||
end
|
||||
|
||||
expect(subject.size).to eq 1
|
||||
sleep RequestPool::MAX_IDLE_TIME + 30 + 1
|
||||
expect(subject.size).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
185
yarn.lock
185
yarn.lock
|
@ -2274,6 +2274,15 @@ caseless@~0.12.0:
|
|||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||
|
||||
chalk@2.4.2, chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
||||
|
@ -2285,15 +2294,6 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
|
|||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chardet@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
|
@ -2408,6 +2408,15 @@ cliui@^4.0.0:
|
|||
strip-ansi "^4.0.0"
|
||||
wrap-ansi "^2.0.0"
|
||||
|
||||
cliui@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
|
||||
integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
|
||||
dependencies:
|
||||
string-width "^3.1.0"
|
||||
strip-ansi "^5.2.0"
|
||||
wrap-ansi "^5.1.0"
|
||||
|
||||
clone-deep@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713"
|
||||
|
@ -2741,7 +2750,7 @@ cross-env@^5.1.4:
|
|||
cross-spawn "^6.0.5"
|
||||
is-windows "^1.0.0"
|
||||
|
||||
cross-spawn@^6.0.0, cross-spawn@^6.0.5:
|
||||
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
|
||||
|
@ -3428,7 +3437,7 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
|
|||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
enhanced-resolve@^4.1.0:
|
||||
enhanced-resolve@4.1.0, enhanced-resolve@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
|
||||
integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==
|
||||
|
@ -3688,10 +3697,10 @@ eslint-plugin-jsx-a11y@~6.2.1:
|
|||
has "^1.0.3"
|
||||
jsx-ast-utils "^2.0.1"
|
||||
|
||||
eslint-plugin-promise@~4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz#1e08cb68b5b2cd8839f8d5864c796f56d82746db"
|
||||
integrity sha512-faAHw7uzlNPy7b45J1guyjazw28M+7gJokKUjC5JSFoYfUEyy6Gw/i7YQvmv2Yk00sUjWcmzXQLpU1Ki/C2IZQ==
|
||||
eslint-plugin-promise@~4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
|
||||
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
|
||||
|
||||
eslint-plugin-react@~7.12.1:
|
||||
version "7.12.1"
|
||||
|
@ -4208,13 +4217,13 @@ find-up@^3.0.0:
|
|||
dependencies:
|
||||
locate-path "^3.0.0"
|
||||
|
||||
findup-sync@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc"
|
||||
integrity sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=
|
||||
findup-sync@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
|
||||
integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==
|
||||
dependencies:
|
||||
detect-file "^1.0.0"
|
||||
is-glob "^3.1.0"
|
||||
is-glob "^4.0.0"
|
||||
micromatch "^3.0.4"
|
||||
resolve-dir "^1.0.1"
|
||||
|
||||
|
@ -4437,6 +4446,11 @@ get-caller-file@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
|
||||
integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
|
||||
|
||||
get-caller-file@^2.0.1:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-stream@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
|
||||
|
@ -4476,6 +4490,13 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1:
|
|||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
global-modules@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
|
||||
integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
|
||||
dependencies:
|
||||
global-prefix "^3.0.0"
|
||||
|
||||
global-modules@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
|
||||
|
@ -4496,6 +4517,15 @@ global-prefix@^1.0.1:
|
|||
is-windows "^1.0.1"
|
||||
which "^1.2.14"
|
||||
|
||||
global-prefix@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97"
|
||||
integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==
|
||||
dependencies:
|
||||
ini "^1.3.5"
|
||||
kind-of "^6.0.2"
|
||||
which "^1.3.1"
|
||||
|
||||
globals@^11.1.0, globals@^11.7.0:
|
||||
version "11.12.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
||||
|
@ -4921,7 +4951,7 @@ import-from@^2.1.0:
|
|||
dependencies:
|
||||
resolve-from "^3.0.0"
|
||||
|
||||
import-local@^2.0.0:
|
||||
import-local@2.0.0, import-local@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
|
||||
integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==
|
||||
|
@ -4970,7 +5000,7 @@ inherits@2.0.1:
|
|||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
|
||||
integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
|
||||
|
||||
ini@^1.3.4, ini@~1.3.0:
|
||||
ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
|
@ -5021,7 +5051,7 @@ internal-ip@^4.3.0:
|
|||
default-gateway "^4.2.0"
|
||||
ipaddr.js "^1.9.0"
|
||||
|
||||
interpret@^1.1.0:
|
||||
interpret@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
|
||||
integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
|
||||
|
@ -6119,7 +6149,7 @@ loader-utils@0.2.x:
|
|||
json5 "^0.5.0"
|
||||
object-assign "^4.0.1"
|
||||
|
||||
loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.2, loader-utils@^1.2.3:
|
||||
loader-utils@1.2.3, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.2, loader-utils@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
|
||||
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
|
||||
|
@ -7034,7 +7064,7 @@ os-homedir@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
|
||||
integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
|
||||
|
||||
os-locale@^3.0.0:
|
||||
os-locale@^3.0.0, os-locale@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
|
||||
integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
|
||||
|
@ -7831,11 +7861,6 @@ prepend-http@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
||||
|
||||
prettier@^1.17.0:
|
||||
version "1.18.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
|
||||
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
|
||||
|
||||
pretty-format@^24.8.0:
|
||||
version "24.8.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2"
|
||||
|
@ -8650,6 +8675,11 @@ require-main-filename@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
|
||||
integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
|
||||
|
||||
require-main-filename@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
|
||||
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
|
||||
|
||||
require-package-name@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/require-package-name/-/require-package-name-2.0.1.tgz#c11e97276b65b8e2923f75dabf5fb2ef0c3841b9"
|
||||
|
@ -9411,7 +9441,7 @@ string-width@^1.0.1:
|
|||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
string-width@^3.0.0:
|
||||
string-width@^3.0.0, string-width@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
|
||||
integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
|
||||
|
@ -9464,7 +9494,7 @@ strip-ansi@^4.0.0:
|
|||
dependencies:
|
||||
ansi-regex "^3.0.0"
|
||||
|
||||
strip-ansi@^5.0.0, strip-ansi@^5.1.0:
|
||||
strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
|
||||
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
|
||||
|
@ -9515,6 +9545,13 @@ substring-trie@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/substring-trie/-/substring-trie-1.0.2.tgz#7b42592391628b4f2cb17365c6cce4257c7b7af5"
|
||||
integrity sha1-e0JZI5Fii08ssXNlxszkJXx7evU=
|
||||
|
||||
supports-color@6.1.0, supports-color@^6.0.0, supports-color@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
|
||||
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||
|
@ -9527,20 +9564,13 @@ supports-color@^3.2.3:
|
|||
dependencies:
|
||||
has-flag "^1.0.0"
|
||||
|
||||
supports-color@^5.3.0, supports-color@^5.5.0:
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^6.0.0, supports-color@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
|
||||
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
svgo@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985"
|
||||
|
@ -10017,10 +10047,10 @@ uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2:
|
|||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
||||
|
||||
v8-compile-cache@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c"
|
||||
integrity sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==
|
||||
v8-compile-cache@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
|
||||
integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
|
||||
|
||||
validate-npm-package-license@^3.0.1:
|
||||
version "3.0.4"
|
||||
|
@ -10142,23 +10172,22 @@ webpack-bundle-analyzer@^3.3.2:
|
|||
opener "^1.5.1"
|
||||
ws "^6.0.0"
|
||||
|
||||
webpack-cli@^3.3.4:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.4.tgz#de27e281c48a897b8c219cb093e261d5f6afe44a"
|
||||
integrity sha512-ubJGQEKMtBSpT+LiL5hXvn2GIOWiRWItR1DGUqJRhwRBeGhpRXjvF5f0erqdRJLErkfqS5/Ldkkedh4AL5Q1ZQ==
|
||||
webpack-cli@^3.3.5:
|
||||
version "3.3.5"
|
||||
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.5.tgz#f4d1238a66a2843d9cebf189835ea22142e72767"
|
||||
integrity sha512-w0j/s42c5UhchwTmV/45MLQnTVwRoaUTu9fM5LuyOd/8lFoCNCELDogFoecx5NzRUndO0yD/gF2b02XKMnmAWQ==
|
||||
dependencies:
|
||||
chalk "^2.4.1"
|
||||
cross-spawn "^6.0.5"
|
||||
enhanced-resolve "^4.1.0"
|
||||
findup-sync "^2.0.0"
|
||||
global-modules "^1.0.0"
|
||||
import-local "^2.0.0"
|
||||
interpret "^1.1.0"
|
||||
loader-utils "^1.1.0"
|
||||
prettier "^1.17.0"
|
||||
supports-color "^5.5.0"
|
||||
v8-compile-cache "^2.0.2"
|
||||
yargs "^12.0.5"
|
||||
chalk "2.4.2"
|
||||
cross-spawn "6.0.5"
|
||||
enhanced-resolve "4.1.0"
|
||||
findup-sync "3.0.0"
|
||||
global-modules "2.0.0"
|
||||
import-local "2.0.0"
|
||||
interpret "1.2.0"
|
||||
loader-utils "1.2.3"
|
||||
supports-color "6.1.0"
|
||||
v8-compile-cache "2.0.3"
|
||||
yargs "13.2.4"
|
||||
|
||||
webpack-dev-middleware@^3.7.0:
|
||||
version "3.7.0"
|
||||
|
@ -10320,7 +10349,7 @@ which-module@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
||||
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
|
||||
|
||||
which@^1.2.14, which@^1.2.9, which@^1.3.0:
|
||||
which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
|
||||
|
@ -10359,6 +10388,15 @@ wrap-ansi@^2.0.0:
|
|||
string-width "^1.0.1"
|
||||
strip-ansi "^3.0.1"
|
||||
|
||||
wrap-ansi@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
|
||||
integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.0"
|
||||
string-width "^3.0.0"
|
||||
strip-ansi "^5.0.0"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
|
@ -10429,6 +10467,14 @@ yargs-parser@^11.1.1:
|
|||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^13.1.0:
|
||||
version "13.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
|
||||
integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
|
||||
dependencies:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs@12.0.5, yargs@^12.0.2, yargs@^12.0.5:
|
||||
version "12.0.5"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
|
||||
|
@ -10446,3 +10492,20 @@ yargs@12.0.5, yargs@^12.0.2, yargs@^12.0.5:
|
|||
which-module "^2.0.0"
|
||||
y18n "^3.2.1 || ^4.0.0"
|
||||
yargs-parser "^11.1.1"
|
||||
|
||||
yargs@13.2.4:
|
||||
version "13.2.4"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83"
|
||||
integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==
|
||||
dependencies:
|
||||
cliui "^5.0.0"
|
||||
find-up "^3.0.0"
|
||||
get-caller-file "^2.0.1"
|
||||
os-locale "^3.1.0"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^2.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^3.0.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^13.1.0"
|
||||
|
|
Loading…
Reference in New Issue