From c3f7f9dce9a54507a9ca74de6bac906a2ba23006 Mon Sep 17 00:00:00 2001 From: mattwr18 Date: Fri, 14 Jun 2024 12:47:32 +0200 Subject: [PATCH 01/10] Fix signal onboarding by sending welcome message we don't receive the signal number from someone we haven't sent a message to, only the uuid, which we don't have in our db. Sending them the welcome message after successful onboarding, as we do with most messengers seems to work and then when we receive a message, we get the number in the envelope --- app/adapters/signal_adapter/inbound.rb | 5 +---- app/adapters/signal_adapter/outbound.rb | 2 +- app/controllers/onboarding/signal_controller.rb | 4 ---- app/jobs/signal_adapter/receive_polling_job.rb | 2 -- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/app/adapters/signal_adapter/inbound.rb b/app/adapters/signal_adapter/inbound.rb index f6128b293..2222fc5b4 100644 --- a/app/adapters/signal_adapter/inbound.rb +++ b/app/adapters/signal_adapter/inbound.rb @@ -62,10 +62,7 @@ def initialize_sender(signal_message) return nil end - if sender.signal_onboarding_completed_at.blank? - trigger(CONNECT, sender) - return nil - end + trigger(CONNECT, sender) if sender.replies.blank? sender end diff --git a/app/adapters/signal_adapter/outbound.rb b/app/adapters/signal_adapter/outbound.rb index 7d8fc5a7a..e130a9aa6 100644 --- a/app/adapters/signal_adapter/outbound.rb +++ b/app/adapters/signal_adapter/outbound.rb @@ -42,7 +42,7 @@ def send_resubscribe_error_message!(contributor) end def contributor_can_receive_messages?(recipient) - recipient&.signal_phone_number.present? && recipient.signal_onboarding_completed_at.present? + recipient&.signal_phone_number.present? end end end diff --git a/app/controllers/onboarding/signal_controller.rb b/app/controllers/onboarding/signal_controller.rb index bb7d82f1a..ef8a6ba94 100644 --- a/app/controllers/onboarding/signal_controller.rb +++ b/app/controllers/onboarding/signal_controller.rb @@ -12,10 +12,6 @@ def attr_name :signal_phone_number end - def redirect_to_success - redirect_to onboarding_signal_link_path(jwt: nil) - end - def complete_onboarding(contributor) SignalAdapter::CreateContactJob.perform_later(contributor) end diff --git a/app/jobs/signal_adapter/receive_polling_job.rb b/app/jobs/signal_adapter/receive_polling_job.rb index b9a1f3d02..13caa6e78 100644 --- a/app/jobs/signal_adapter/receive_polling_job.rb +++ b/app/jobs/signal_adapter/receive_polling_job.rb @@ -74,8 +74,6 @@ def queue_empty? end def handle_connect(contributor) - contributor.update!(signal_onboarding_completed_at: Time.zone.now) - SignalAdapter::Outbound.send_welcome_message!(contributor) SignalAdapter::AttachContributorsAvatarJob.perform_later(contributor) end From b9f90ac2b41bf306fc726511536f116277459d7d Mon Sep 17 00:00:00 2001 From: mattwr18 Date: Wed, 19 Jun 2024 10:55:19 +0200 Subject: [PATCH 02/10] Adapt onboarding to use Signal username - have not worked through it entirely --- app/adapters/signal_adapter/inbound.rb | 35 ++++++----- .../unknown_contributor_error.rb | 4 +- .../contributor_signal_settings.html.erb | 33 ++-------- .../onboarding_signal_form.html.erb | 4 +- .../onboarding_signal_link.html.erb | 39 ------------ .../onboarding_signal_link.rb | 5 -- .../onboarding/signal_controller.rb | 4 -- app/controllers/onboarding_controller.rb | 3 - .../attach_contributors_avatar_job.rb | 7 ++- app/jobs/signal_adapter/create_contact_job.rb | 7 ++- .../signal_adapter/receive_polling_job.rb | 61 +++++++++++-------- app/models/contributor.rb | 2 +- app/views/onboarding/signal/link.html.erb | 5 -- config/locales/de.yml | 7 --- ...327_update_signal_attrs_on_contributors.rb | 11 ++++ db/schema.rb | 5 +- docker-compose.yml | 2 +- spec/adapters/signal_adapter/inbound_spec.rb | 16 ++--- spec/adapters/signal_adapter/outbound_spec.rb | 21 +------ .../contributor_signal_settings_spec.rb | 12 ---- .../components/onboarding_signal_link_spec.rb | 12 ---- spec/factories/contributors.rb | 1 - spec/jobs/resubscribe_contributor_job_spec.rb | 4 +- .../receive_polling_job_spec.rb | 41 +++---------- spec/jobs/unsubscribe_contributor_job_spec.rb | 2 +- spec/models/contributor_spec.rb | 7 +-- spec/requests/onboarding/signal_spec.rb | 18 ++++-- spec/system/requests/sending_images_spec.rb | 2 +- 28 files changed, 121 insertions(+), 249 deletions(-) delete mode 100644 app/components/onboarding_signal_link/onboarding_signal_link.html.erb delete mode 100644 app/components/onboarding_signal_link/onboarding_signal_link.rb delete mode 100644 app/views/onboarding/signal/link.html.erb create mode 100644 db/migrate/20240618054327_update_signal_attrs_on_contributors.rb delete mode 100644 spec/components/onboarding_signal_link_spec.rb diff --git a/app/adapters/signal_adapter/inbound.rb b/app/adapters/signal_adapter/inbound.rb index 2222fc5b4..9755a3579 100644 --- a/app/adapters/signal_adapter/inbound.rb +++ b/app/adapters/signal_adapter/inbound.rb @@ -25,12 +25,12 @@ def on(callback, &block) def consume(signal_message) signal_message = signal_message.with_indifferent_access - @sender = initialize_sender(signal_message) - return unless @sender - delivery_receipt = initialize_delivery_receipt(signal_message) return if delivery_receipt + @sender = initialize_sender(signal_message) + return unless @sender + remove_emoji = signal_message.dig(:envelope, :dataMessage, :reaction, :isRemove) return if remove_emoji @@ -53,28 +53,31 @@ def trigger(event, *args) @callbacks[event].call(*args) end - def initialize_sender(signal_message) - signal_phone_number = signal_message.dig(:envelope, :source) + def initialize_delivery_receipt(signal_message) + delivery_receipt = signal_message.dig(:envelope, :receiptMessage) + return nil unless delivery_receipt + + signal_phone_number = signal_message.dig(:envelope, :sourceNumber) sender = Contributor.find_by(signal_phone_number: signal_phone_number) + Rails.logger.debug sender, signal_phone_number + return unless sender + + trigger(HANDLE_DELIVERY_RECEIPT, signal_message, sender) + delivery_receipt + end + + def initialize_sender(signal_message) + signal_uuid = signal_message.dig(:envelope, :sourceUuid) + sender = Contributor.find_by(signal_uuid: signal_uuid) unless sender - trigger(UNKNOWN_CONTRIBUTOR, signal_phone_number) + trigger(UNKNOWN_CONTRIBUTOR, signal_uuid) return nil end - trigger(CONNECT, sender) if sender.replies.blank? - sender end - def initialize_delivery_receipt(signal_message) - delivery_receipt = signal_message.dig(:envelope, :receiptMessage) - return nil unless delivery_receipt - - trigger(HANDLE_DELIVERY_RECEIPT, delivery_receipt, sender) - delivery_receipt - end - def initialize_message(signal_message) is_data_message = signal_message.dig(:envelope, :dataMessage) return nil unless is_data_message diff --git a/app/adapters/signal_adapter/unknown_contributor_error.rb b/app/adapters/signal_adapter/unknown_contributor_error.rb index 0ff21246f..c61df1793 100644 --- a/app/adapters/signal_adapter/unknown_contributor_error.rb +++ b/app/adapters/signal_adapter/unknown_contributor_error.rb @@ -2,8 +2,8 @@ module SignalAdapter class UnknownContributorError < StandardError - def initialize(signal_phone_number:) - super("Received a message on signal from an unknown sender: #{signal_phone_number}") + def initialize(signal_uuid:) + super("Received a message on signal from an unknown sender: #{signal_uuid}") end end end diff --git a/app/components/contributor_signal_settings/contributor_signal_settings.html.erb b/app/components/contributor_signal_settings/contributor_signal_settings.html.erb index 3cbf152ef..91f6e639b 100644 --- a/app/components/contributor_signal_settings/contributor_signal_settings.html.erb +++ b/app/components/contributor_signal_settings/contributor_signal_settings.html.erb @@ -3,32 +3,11 @@ <%= t('.heading') %> <% end %> - <% if contributor.signal_onboarding_completed_at.present? %> -

- <%= t('.complete.text', - name: contributor.name, - phone_number: contributor.signal_phone_number.phony_formatted - ) %> -

- <% else %> - <%= c 'callout', style: :warning do %> - <%= c 'stack', space: :small do %> -

- <%= t('.incomplete.text', - name: contributor.name, - first_name: contributor.first_name, - date: l(contributor.created_at.to_date), - phone_number: contributor.signal_phone_number.phony_formatted, - ) %> -

- - <%= c 'copy_button', - style: :secondary, - copy: onboarding_signal_link_url, - label: t('.incomplete.action') - %> - <% end %> - <% end %> - <% end %> +

+ <%= t('.complete.text', + name: contributor.name, + phone_number: contributor.signal_phone_number.phony_formatted + ) %> +

<% end %> diff --git a/app/components/onboarding_signal_form/onboarding_signal_form.html.erb b/app/components/onboarding_signal_form/onboarding_signal_form.html.erb index 2c93a0dc3..1930319b0 100644 --- a/app/components/onboarding_signal_form/onboarding_signal_form.html.erb +++ b/app/components/onboarding_signal_form/onboarding_signal_form.html.erb @@ -18,8 +18,8 @@ <%= c 'input', field.input_defaults.merge(required: true) %> <% end %> - <%= c 'field', object: contributor, attr: :signal_phone_number do |field| %> - <%= c 'input', field.input_defaults.merge(placeholder: '', required: true, type: :tel) %> + <%= c 'field', object: contributor, attr: :signal_username do |field| %> + <%= c 'input', field.input_defaults.merge(placeholder: '', required: true) %> <% end %> <%= c 'onboarding_consent', contributor: contributor %> diff --git a/app/components/onboarding_signal_link/onboarding_signal_link.html.erb b/app/components/onboarding_signal_link/onboarding_signal_link.html.erb deleted file mode 100644 index 175074bda..000000000 --- a/app/components/onboarding_signal_link/onboarding_signal_link.html.erb +++ /dev/null @@ -1,39 +0,0 @@ -<%= c 'stack', **attrs do %> - - <%= c 'stack', class: 'text-serif text-center', space: :xsmall do %> - <%= c 'heading', tag: :h1 do %> - <%= t('components.onboarding_signal_link.heading') %> - <% end %> - -

- <%= t('components.onboarding_signal_link.text').html_safe %> -

- -

- <%= t('components.onboarding_signal_link.action', - number: Setting.signal_server_phone_number.phony_formatted(spaces: ' ') - ).html_safe %> -

- <% end %> - - <%= c 'stack', space: :small do %> - <%= c 'button', - styles: [:block, :primary], - link: "https://signal.me/#p/#{Setting.signal_server_phone_number}", - label: t('components.onboarding_signal_link.link'), - target: '_blank', - rel: 'noopener noreferrer' - %> - - <%= c 'copy_button', - styles: [:block, :secondary], - label: t('components.onboarding_signal_link.copy'), - copy: Setting.signal_server_phone_number - %> - -

- <%= t('components.onboarding_signal_link.help') %> -

- <% end %> - -<% end %> diff --git a/app/components/onboarding_signal_link/onboarding_signal_link.rb b/app/components/onboarding_signal_link/onboarding_signal_link.rb deleted file mode 100644 index 66fc795d0..000000000 --- a/app/components/onboarding_signal_link/onboarding_signal_link.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module OnboardingSignalLink - class OnboardingSignalLink < ApplicationComponent; end -end diff --git a/app/controllers/onboarding/signal_controller.rb b/app/controllers/onboarding/signal_controller.rb index ef8a6ba94..3020d30da 100644 --- a/app/controllers/onboarding/signal_controller.rb +++ b/app/controllers/onboarding/signal_controller.rb @@ -11,9 +11,5 @@ def link; end def attr_name :signal_phone_number end - - def complete_onboarding(contributor) - SignalAdapter::CreateContactJob.perform_later(contributor) - end end end diff --git a/app/controllers/onboarding_controller.rb b/app/controllers/onboarding_controller.rb index 942ef6e6f..e742c3a68 100644 --- a/app/controllers/onboarding_controller.rb +++ b/app/controllers/onboarding_controller.rb @@ -28,7 +28,6 @@ def create @contributor.tag_list = tag_list_from_jwt if @contributor.save - complete_onboarding(@contributor) @contributor.send_welcome_message! redirect_to_success else @@ -87,8 +86,6 @@ def verify_jwt raise ActionController::BadRequest unless resume_telegram_onboarding? end - def complete_onboarding(contributor); end - def resume_telegram_onboarding return unless resume_telegram_onboarding? diff --git a/app/jobs/signal_adapter/attach_contributors_avatar_job.rb b/app/jobs/signal_adapter/attach_contributors_avatar_job.rb index b4c8a1da9..bbad4ca96 100644 --- a/app/jobs/signal_adapter/attach_contributors_avatar_job.rb +++ b/app/jobs/signal_adapter/attach_contributors_avatar_job.rb @@ -4,8 +4,11 @@ module SignalAdapter class AttachContributorsAvatarJob < ApplicationJob queue_as :attach_signal_avatar - def perform(contributor) - avatar = "/app/signal-cli-config/avatars/profile-#{contributor.signal_phone_number}" + def perform(contributor_id:) + contributor = Contributor.find_by(id: contributor_id) + return unless contributor + + avatar = "/app/signal-cli-config/avatars/profile-#{contributor.signal_uuid}" return unless File.file?(avatar) contributor.avatar.attach(io: File.open(avatar), filename: contributor.id) diff --git a/app/jobs/signal_adapter/create_contact_job.rb b/app/jobs/signal_adapter/create_contact_job.rb index 497bcef47..109c1ceb6 100644 --- a/app/jobs/signal_adapter/create_contact_job.rb +++ b/app/jobs/signal_adapter/create_contact_job.rb @@ -4,14 +4,17 @@ module SignalAdapter class CreateContactJob < ApplicationJob - def perform(contributor) + def perform(contributor_id:) + contributor = Contributor.find_by(id: contributor_id) + return unless contributor + url = URI.parse("#{Setting.signal_cli_rest_api_endpoint}/v1/contacts/#{Setting.signal_server_phone_number}") header = { Accept: 'application/json', 'Content-Type': 'application/json' } data = { - recipient: contributor.signal_phone_number, + recipient: contributor.signal_uuid, name: contributor.name } req = Net::HTTP::Put.new(url.to_s, header) diff --git a/app/jobs/signal_adapter/receive_polling_job.rb b/app/jobs/signal_adapter/receive_polling_job.rb index 13caa6e78..f4142ef81 100644 --- a/app/jobs/signal_adapter/receive_polling_job.rb +++ b/app/jobs/signal_adapter/receive_polling_job.rb @@ -5,24 +5,46 @@ module SignalAdapter class ReceivePollingJob < ApplicationJob queue_as :poll_signal_messages + attr_reader :adapter before_enqueue do throw(:abort) unless queue_empty? end - # rubocop:disable Metrics/MethodLength def perform(*_args) return if Setting.signal_server_phone_number.blank? signal_messages = request_new_messages - adapter = SignalAdapter::Inbound.new + @adapter = SignalAdapter::Inbound.new + handle_callbacks + + signal_messages.each do |raw_message| + adapter.consume(raw_message) { |m| m.contributor.reply(adapter) } + rescue StandardError => e + ErrorNotifier.report(e) + end + + ping_monitoring_service && return + end + + private + + def request_new_messages + url = URI.parse("#{Setting.signal_cli_rest_api_endpoint}/v1/receive/#{Setting.signal_server_phone_number}") + res = Net::HTTP.get_response(url) + raise SignalAdapter::ServerError if res.instance_of?(Net::HTTPBadRequest) + + JSON.parse(res.body) + end + + def handle_callbacks adapter.on(SignalAdapter::CONNECT) do |contributor| handle_connect(contributor) end - adapter.on(SignalAdapter::UNKNOWN_CONTRIBUTOR) do |signal_phone_number| - exception = SignalAdapter::UnknownContributorError.new(signal_phone_number: signal_phone_number) + adapter.on(SignalAdapter::UNKNOWN_CONTRIBUTOR) do |signal_uuid| + exception = SignalAdapter::UnknownContributorError.new(signal_uuid: signal_uuid) ErrorNotifier.report(exception) end @@ -41,25 +63,6 @@ def perform(*_args) adapter.on(SignalAdapter::HANDLE_DELIVERY_RECEIPT) do |delivery_receipt, contributor| handle_delivery_receipt(delivery_receipt, contributor) end - - signal_messages.each do |raw_message| - adapter.consume(raw_message) { |m| m.contributor.reply(adapter) } - rescue StandardError => e - ErrorNotifier.report(e) - end - - ping_monitoring_service && return - end - # rubocop:enable Metrics/MethodLength - - private - - def request_new_messages - url = URI.parse("#{Setting.signal_cli_rest_api_endpoint}/v1/receive/#{Setting.signal_server_phone_number}") - res = Net::HTTP.get_response(url) - raise SignalAdapter::ServerError if res.instance_of?(Net::HTTPBadRequest) - - JSON.parse(res.body) end def ping_monitoring_service @@ -73,11 +76,17 @@ def queue_empty? Delayed::Job.where(queue: queue_name, failed_at: nil).none? end - def handle_connect(contributor) - SignalAdapter::AttachContributorsAvatarJob.perform_later(contributor) + def handle_connect(contributor, signal_uuid) + contributor.update!(signal_uuid) + SignalAdapter::CreateContactJob.perform_later(contributor_id: contributor.id) + SignalAdapter::AttachContributorsAvatarJob.perform_later(contributor_id: contributor.id) end - def handle_delivery_receipt(delivery_receipt, contributor) + def handle_delivery_receipt(signal_message, contributor) + delivery_receipt = signal_message.dig(:envelope, :receiptMessage) + signal_uuid = signal_message.dig(:envelope, :sourceUuid) + handle_connect(contributor, signal_uuid) if contributor.signal_uuid.blank? + datetime = Time.zone.at(delivery_receipt[:when] / 1000).to_datetime latest_received_message = contributor.received_messages.first return unless latest_received_message diff --git a/app/models/contributor.rb b/app/models/contributor.rb index 43a625832..95c919a72 100644 --- a/app/models/contributor.rb +++ b/app/models/contributor.rb @@ -54,7 +54,7 @@ class Contributor < ApplicationRecord scope :with_email, -> { where.not(email: nil) } scope :with_threema, -> { where.not(threema_id: nil) } scope :with_telegram, -> { where.not(telegram_id: nil) } - scope :with_signal, -> { where.not(signal_phone_number: nil, signal_onboarding_completed_at: nil) } + scope :with_signal, -> { where.not(signal_phone_number: nil) } scope :with_whats_app, -> { where.not(whats_app_phone_number: nil) } before_validation do diff --git a/app/views/onboarding/signal/link.html.erb b/app/views/onboarding/signal/link.html.erb deleted file mode 100644 index 941538323..000000000 --- a/app/views/onboarding/signal/link.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -
- <%= c 'interstitial' do %> - <%= c 'onboarding_signal_link' %> - <% end %> -
diff --git a/config/locales/de.yml b/config/locales/de.yml index 44e276493..5adae4afb 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -82,13 +82,6 @@ de: other: Du hast {count} neue Antworten. onboarding_header: byline: eine Dialog-Recherche von tactile.news - onboarding_signal_link: - heading: Fast geschafft. - text: Damit wir Ihnen zukünftig auf Signal schreiben können, müssen Sie uns zuerst kontaktieren. - action: Senden Sie uns hierzu eine Nachricht auf Signal. Ein kurzes „Hallo” reicht aus! Unsere Nummer ist %{number}. - link: Zur Signal-App - copy: Handynummer kopieren - help: Über den Button gelangen Sie direkt zu unserem Kontakt in der Signal-App. Alternativ können Sie unsere Nummer kopieren. onboarding_signal_form: heading: Mit Signal teilnehmen text: Geben Sie bitte unten die Handynummer ein, mit der Sie bei Signal registriert sind. diff --git a/db/migrate/20240618054327_update_signal_attrs_on_contributors.rb b/db/migrate/20240618054327_update_signal_attrs_on_contributors.rb new file mode 100644 index 000000000..a6d070882 --- /dev/null +++ b/db/migrate/20240618054327_update_signal_attrs_on_contributors.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class UpdateSignalAttrsOnContributors < ActiveRecord::Migration[6.1] + def change + change_table :contributors, bulk: true do |t| + t.remove :signal_onboarding_completed_at, type: :datetime + t.column :signal_uuid, :string, default: nil + t.column :signal_username, :string, default: nil + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 7e89ac1d2..4718d07d2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_03_11_085038) do +ActiveRecord::Schema.define(version: 2024_06_18_054327) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -106,7 +106,6 @@ t.datetime "data_processing_consented_at" t.string "telegram_onboarding_token" t.string "signal_phone_number" - t.datetime "signal_onboarding_completed_at" t.string "additional_email" t.datetime "additional_consent_given_at" t.bigint "organization_id" @@ -116,6 +115,8 @@ t.boolean "deactivated_by_admin", default: false t.datetime "whats_app_message_template_sent_at" t.datetime "unsubscribed_at" + t.string "signal_uuid" + t.string "signal_username" t.index ["email"], name: "index_contributors_on_email", unique: true t.index ["organization_id"], name: "index_contributors_on_organization_id" t.index ["signal_phone_number"], name: "index_contributors_on_signal_phone_number", unique: true diff --git a/docker-compose.yml b/docker-compose.yml index 118821c07..9af9a1b12 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: depends_on: [ signal ] signal: - image: bbernhard/signal-cli-rest-api:0.81 + image: tactilenews/signal-cli-rest-api:latest environment: - MODE=native #- AUTO_RECEIVE_SCHEDULE=0 22 * * * #enable this parameter on demand (see description below) diff --git a/spec/adapters/signal_adapter/inbound_spec.rb b/spec/adapters/signal_adapter/inbound_spec.rb index 7b57276b1..a027125b1 100644 --- a/spec/adapters/signal_adapter/inbound_spec.rb +++ b/spec/adapters/signal_adapter/inbound_spec.rb @@ -178,8 +178,7 @@ create( :contributor, id: 4711, - signal_phone_number: phone_number, - signal_onboarding_completed_at: onboarding_completed_at + signal_phone_number: phone_number ) end @@ -195,18 +194,11 @@ it { should be_a(Message) } context 'from an unknown contributor' do - let(:onboarding_completed_at) { nil } let!(:phone_number) { '+495555555' } it { should be(nil) } end - context 'from a contributor with incomplete onboarding' do - let(:onboarding_completed_at) { nil } - - it { should be(nil) } - end - context 'given a receipt message' do before { create(:message, recipient_id: contributor.id) } let(:signal_message) { signal_receipt_message } @@ -378,12 +370,12 @@ it { should_not have_received(:call) } end - context 'if the sender is a contributor with incomplete onboarding' do - let(:onboarding_completed_at) { nil } + context 'if the sender is a contributor with no replies' do it { should have_received(:call).with(contributor) } end - context 'if the sender is a contributor who has completed onboarding' do + context 'if the sender is a contributor with replies' do + before { create(:message, sender: contributor) } it { should_not have_received(:call) } end end diff --git a/spec/adapters/signal_adapter/outbound_spec.rb b/spec/adapters/signal_adapter/outbound_spec.rb index 0a9712bbc..cae080e2f 100644 --- a/spec/adapters/signal_adapter/outbound_spec.rb +++ b/spec/adapters/signal_adapter/outbound_spec.rb @@ -18,22 +18,15 @@ it { should_not enqueue_job(described_class::Text) } context 'contributor has a phone number' do - let(:onboarding_completed_at) { nil } let(:contributor) do create( :contributor, signal_phone_number: '+491511234567', - signal_onboarding_completed_at: onboarding_completed_at, email: nil ) end - it { should_not enqueue_job(described_class::Text) } - - context 'and has completed onboarding' do - let(:onboarding_completed_at) { Time.zone.now } - it { should enqueue_job(described_class::Text).with(expected_job_args) } - end + it { should enqueue_job(described_class::Text) } end end @@ -43,23 +36,15 @@ it { should_not enqueue_job(described_class::Text) } describe 'contributor has a phone number' do - let(:onboarding_completed_at) { nil } - let(:contributor) do create( :contributor, email: nil, - signal_phone_number: '+491511234567', - signal_onboarding_completed_at: onboarding_completed_at + signal_phone_number: '+491511234567' ) end - it { should_not enqueue_job(described_class::Text) } - - context 'and has completed onboarding' do - let(:onboarding_completed_at) { Time.zone.now } - it { should enqueue_job(described_class::Text) } - end + it { should enqueue_job(described_class::Text) } end end end diff --git a/spec/components/contributor_signal_settings_spec.rb b/spec/components/contributor_signal_settings_spec.rb index 0d2a51e13..c25693579 100644 --- a/spec/components/contributor_signal_settings_spec.rb +++ b/spec/components/contributor_signal_settings_spec.rb @@ -12,21 +12,9 @@ first_name: 'Max', last_name: 'Mustermann', signal_phone_number: '+4915112345678', - signal_onboarding_completed_at: onboarding_completed_at, created_at: '2021-01-01') end - let(:onboarding_completed_at) { Time.zone.now } - it { should have_css('h2', text: 'Signal') } it { should have_css('p', text: 'Max Mustermann hat sich mit der Handynummer 0151 1234 5678 angemeldet.') } - - context 'given a contributor with incomplete onboarding' do - let(:onboarding_completed_at) { nil } - - it { should have_css('p', text: 'Max Mustermann hat sich am 01.01.2021 via Signal') } - it { should have_css('p', text: 'mit der Handynummer 0151 1234 5678 angemeldet, die Anmeldung aber noch nicht abgeschlossen.') } - it { should have_css('p', text: 'Sende Max einen Link mit Hinweisen zum Abschließen der Anmeldung.') } - it { should have_css('button[data-copy-button-copy-value$="http://test.host/onboarding/signal/link"]') } - end end diff --git a/spec/components/onboarding_signal_link_spec.rb b/spec/components/onboarding_signal_link_spec.rb deleted file mode 100644 index 7022e43b1..000000000 --- a/spec/components/onboarding_signal_link_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe OnboardingSignalLink::OnboardingSignalLink, type: :component do - subject { render_inline(described_class.new(**params)) } - let(:params) { {} } - before(:each) { allow(Setting).to receive(:signal_server_phone_number).and_return('+4915112345678') } - - it { should have_css('.OnboardingSignalLink') } - it { should have_css('strong', text: '015112345678'.phony_formatted(normalize: :DE, spaces: ' ')) } -end diff --git a/spec/factories/contributors.rb b/spec/factories/contributors.rb index 8119cfbe1..755f81977 100644 --- a/spec/factories/contributors.rb +++ b/spec/factories/contributors.rb @@ -39,7 +39,6 @@ after(:build) do |contributor| contributor.email = nil contributor.signal_phone_number = Faker::PhoneNumber.cell_phone_in_e164 - contributor.signal_onboarding_completed_at = Time.current end end diff --git a/spec/jobs/resubscribe_contributor_job_spec.rb b/spec/jobs/resubscribe_contributor_job_spec.rb index a5059403a..001010b10 100644 --- a/spec/jobs/resubscribe_contributor_job_spec.rb +++ b/spec/jobs/resubscribe_contributor_job_spec.rb @@ -13,7 +13,7 @@ it_behaves_like 'a Contributor resubscribes', SignalAdapter::Outbound::Text do let(:contributor) do - create(:contributor, signal_phone_number: '+491234567', signal_onboarding_completed_at: Time.current, unsubscribed_at: 1.day.ago) + create(:contributor, signal_phone_number: '+491234567', unsubscribed_at: 1.day.ago) end end @@ -22,7 +22,6 @@ let(:contributor) do create(:contributor, signal_phone_number: '+491234567', - signal_onboarding_completed_at: Time.current, unsubscribed_at: 1.day.ago, deactivated_at: Time.current, deactivated_by_user: create(:user)) @@ -35,7 +34,6 @@ let(:contributor) do create(:contributor, signal_phone_number: '+491234567', - signal_onboarding_completed_at: Time.current, unsubscribed_at: 1.day.ago, deactivated_at: Time.current, deactivated_by_admin: true) diff --git a/spec/jobs/signal_adapter/receive_polling_job_spec.rb b/spec/jobs/signal_adapter/receive_polling_job_spec.rb index 64879bdae..9432c710a 100644 --- a/spec/jobs/signal_adapter/receive_polling_job_spec.rb +++ b/spec/jobs/signal_adapter/receive_polling_job_spec.rb @@ -73,42 +73,16 @@ end end - describe 'given a message from a contributor with incomplete onboarding' do + describe 'given a message from a contributor for the first time' do let!(:contributor) { create(:contributor, signal_phone_number: '+4915112345789') } - before do - allow(Setting).to receive(:onboarding_success_heading).and_return('Welcome!') - allow(Setting).to receive(:onboarding_success_text).and_return('') - end - - it { should_not(change { Message.count }) } - - it 'sends welcome message' do - should have_enqueued_job(SignalAdapter::Outbound::Text).with do |text, recipient| - expect(text).to eq("Welcome!\n") - expect(recipient.id).to eq(contributor.id) - end - end - - it 'sets signal_onboarding_completed_at' do - subject.call - expect(contributor.reload.signal_onboarding_completed_at).to be_present + it 'creates a message' do + expect { subject.call }.to change(Message, :count).by(1) end it 'enqueues a job to attach contributors avatar' do expect { subject.call }.to have_enqueued_job(SignalAdapter::AttachContributorsAvatarJob).with(contributor) end - end - - describe 'given a message from a contributor with completed onboarding' do - before do - create(:contributor, signal_phone_number: '+4915112345789', signal_onboarding_completed_at: Time.zone.now) - create(:contributor, signal_phone_number: '+4915155555555', signal_onboarding_completed_at: Time.zone.now) - end - - it 'is expected to create a message' do - should(change { Message.count }.from(0).to(1)) - end it 'is expected to assign the correct contributor' do subject.call @@ -121,7 +95,7 @@ describe 'given multiple messages from known and unknown contributors', vcr: { cassette_name: :receive_multiple_signal_messages } do before do allow(Sentry).to receive(:capture_exception).with(an_instance_of(SignalAdapter::UnknownContributorError)) - create(:contributor, signal_phone_number: '+4915112345789', signal_onboarding_completed_at: Time.zone.now) + create(:contributor, signal_phone_number: '+4915112345789') end it 'creates a message for the known contributor' do @@ -135,7 +109,7 @@ end describe 'given a message with attachments' do - let!(:contributor) { create(:contributor, signal_phone_number: '+4915112345678', signal_onboarding_completed_at: Time.zone.now) } + let!(:contributor) { create(:contributor, signal_phone_number: '+4915112345678') } before do allow(File).to receive(:open).and_call_original @@ -174,7 +148,7 @@ allow(Setting).to receive(:signal_cli_rest_api_endpoint).and_return('http://signal:8080') end - let!(:contributor) { create(:contributor, signal_phone_number: '+4915112345789', signal_onboarding_completed_at: Time.zone.now) } + let!(:contributor) { create(:contributor, signal_phone_number: '+4915112345789') } it { is_expected.to have_enqueued_job(UnsubscribeContributorJob).with(contributor.id, SignalAdapter::Outbound) } end @@ -184,8 +158,7 @@ allow(Setting).to receive(:signal_cli_rest_api_endpoint).and_return('http://signal:8080') end let!(:contributor) do - create(:contributor, signal_phone_number: '+4915112345789', signal_onboarding_completed_at: Time.zone.now, - unsubscribed_at: 1.week.ago) + create(:contributor, signal_phone_number: '+4915112345789', unsubscribed_at: 1.week.ago) end it { is_expected.to have_enqueued_job(ResubscribeContributorJob).with(contributor.id, SignalAdapter::Outbound) } diff --git a/spec/jobs/unsubscribe_contributor_job_spec.rb b/spec/jobs/unsubscribe_contributor_job_spec.rb index 81b9756fd..5e6548a46 100644 --- a/spec/jobs/unsubscribe_contributor_job_spec.rb +++ b/spec/jobs/unsubscribe_contributor_job_spec.rb @@ -17,7 +17,7 @@ let(:adapter) { SignalAdapter::Outbound } it_behaves_like 'a Contributor unsubscribes', SignalAdapter::Outbound::Text do - let(:contributor) { create(:contributor, signal_phone_number: '+491234567', signal_onboarding_completed_at: Time.current) } + let(:contributor) { create(:contributor, signal_phone_number: '+491234567') } end end diff --git a/spec/models/contributor_spec.rb b/spec/models/contributor_spec.rb index c0fffa246..e8f34b4f3 100644 --- a/spec/models/contributor_spec.rb +++ b/spec/models/contributor_spec.rb @@ -537,14 +537,9 @@ end end - let(:onboarding_completed_at) { Time.zone.now } let(:phone_number) { '+4912345789' } let!(:contributor) do - create( - :contributor, - signal_phone_number: phone_number, - signal_onboarding_completed_at: onboarding_completed_at - ) + create(:contributor, signal_phone_number: phone_number) end it { should_not raise_error } diff --git a/spec/requests/onboarding/signal_spec.rb b/spec/requests/onboarding/signal_spec.rb index e699740a9..ac3d911de 100644 --- a/spec/requests/onboarding/signal_spec.rb +++ b/spec/requests/onboarding/signal_spec.rb @@ -75,6 +75,8 @@ end describe 'but when a signal server phone number is configured and onboarding has not been disallowed' do + let(:welcome_message) { [Setting.onboarding_success_heading, Setting.onboarding_success_text].join("\n") } + let(:signal_adapter_outbound_spy) { spy(SignalAdapter::Outbound) } before do allow(Setting).to receive(:signal_server_phone_number).and_return('+4491234567890') allow(Setting).to receive(:signal_onboarding_allowed?).and_return(true) @@ -96,13 +98,19 @@ ) end - it 'does not send welcome message' do - should_not enqueue_job(SignalAdapter::Outbound).with(message: Message.new(text: anything), recipient: anything) + it 'sends welcome message' do + subject.call + contributor = Contributor.find_by(signal_phone_number: '+4915112345678') + + expect(SignalAdapter::Outbound::Text).to have_been_enqueued.with( + contributor_id: contributor.id, + text: welcome_message + ) end - it 'redirects to onboarding signal link page' do + it 'redirects to success page' do subject.call - expect(response).to redirect_to onboarding_signal_link_path + expect(response).to redirect_to onboarding_success_path(jwt: nil) end it 'invalidates the jwt' do @@ -159,7 +167,7 @@ it 'redirects to success page so that an attacker cannot make a phone number listing' do subject.call - expect(response).to redirect_to onboarding_signal_link_path + expect(response).to redirect_to onboarding_success_path(jwt: nil) end it 'invalidates the jwt' do diff --git a/spec/system/requests/sending_images_spec.rb b/spec/system/requests/sending_images_spec.rb index 4936e3ce8..a5ecad454 100644 --- a/spec/system/requests/sending_images_spec.rb +++ b/spec/system/requests/sending_images_spec.rb @@ -11,7 +11,7 @@ allow(Request).to receive(:broadcast!).and_call_original create(:contributor, email: 'adam@example.org') - create(:contributor, signal_phone_number: '+4912345678', signal_onboarding_completed_at: Time.current) + create(:contributor, signal_phone_number: '+4912345678') create(:contributor, telegram_id: 125_689) create(:contributor, :skip_validations, threema_id: '12345678') end From bb644773b1b6c8d1392c3a05102f635f49d376ee Mon Sep 17 00:00:00 2001 From: mattwr18 Date: Mon, 24 Jun 2024 12:08:59 +0200 Subject: [PATCH 03/10] Favor signal_onboarding_token to identify new Signal contributors --- app/adapters/signal_adapter/inbound.rb | 52 ++++++++++++------- app/adapters/signal_adapter/outbound.rb | 2 +- app/adapters/signal_adapter/outbound/file.rb | 2 +- app/adapters/signal_adapter/outbound/text.rb | 2 +- .../contributor_signal_settings.html.erb | 33 +++++++++--- .../onboarding_signal_form.html.erb | 8 +-- .../onboarding_signal_link.html.erb | 14 +++++ .../onboarding_signal_link.rb | 20 +++++++ .../onboarding/signal_controller.rb | 20 ++++++- app/controllers/onboarding_controller.rb | 15 +++++- .../signal_adapter/receive_polling_job.rb | 10 ++-- app/views/onboarding/signal/link.html.erb | 5 ++ config/locales/de.yml | 7 ++- ...327_update_signal_attrs_on_contributors.rb | 3 +- db/schema.rb | 5 +- docker-compose.yml | 2 +- 16 files changed, 155 insertions(+), 45 deletions(-) create mode 100644 app/components/onboarding_signal_link/onboarding_signal_link.html.erb create mode 100644 app/components/onboarding_signal_link/onboarding_signal_link.rb create mode 100644 app/views/onboarding/signal/link.html.erb diff --git a/app/adapters/signal_adapter/inbound.rb b/app/adapters/signal_adapter/inbound.rb index 9755a3579..0ada7458d 100644 --- a/app/adapters/signal_adapter/inbound.rb +++ b/app/adapters/signal_adapter/inbound.rb @@ -12,7 +12,7 @@ class Inbound UNKNOWN_CONTENT_KEYS = %w[mentions contacts sticker].freeze SUPPORTED_ATTACHMENT_TYPES = %w[image/jpg image/jpeg image/png image/gif audio/oog audio/aac audio/mp4 audio/mpeg video/mp4].freeze - attr_reader :sender, :text, :message + attr_reader :sender, :message def initialize @callbacks = {} @@ -25,12 +25,12 @@ def on(callback, &block) def consume(signal_message) signal_message = signal_message.with_indifferent_access - delivery_receipt = initialize_delivery_receipt(signal_message) - return if delivery_receipt - @sender = initialize_sender(signal_message) return unless @sender + delivery_receipt = initialize_delivery_receipt(signal_message) + return if delivery_receipt + remove_emoji = signal_message.dig(:envelope, :dataMessage, :reaction, :isRemove) return if remove_emoji @@ -53,31 +53,43 @@ def trigger(event, *args) @callbacks[event].call(*args) end - def initialize_delivery_receipt(signal_message) - delivery_receipt = signal_message.dig(:envelope, :receiptMessage) - return nil unless delivery_receipt - + def initialize_sender(signal_message) signal_phone_number = signal_message.dig(:envelope, :sourceNumber) - sender = Contributor.find_by(signal_phone_number: signal_phone_number) - Rails.logger.debug sender, signal_phone_number - return unless sender + signal_uuid = signal_message.dig(:envelope, :sourceUuid) + sender = if signal_phone_number + Contributor.find_by(signal_phone_number: signal_phone_number) + else + Contributor.find_by(signal_uuid: signal_uuid) + end + return sender if sender - trigger(HANDLE_DELIVERY_RECEIPT, signal_message, sender) - delivery_receipt - end + signal_onboarding_token = signal_message.dig(:envelope, :dataMessage, :message) + return nil unless signal_onboarding_token - def initialize_sender(signal_message) - signal_uuid = signal_message.dig(:envelope, :sourceUuid) - sender = Contributor.find_by(signal_uuid: signal_uuid) + sender = Contributor.find_by(signal_onboarding_token: signal_onboarding_token.strip) unless sender - trigger(UNKNOWN_CONTRIBUTOR, signal_uuid) + trigger(UNKNOWN_CONTRIBUTOR, signal_message.dig(:envelope, :source)) + return nil + end + + if sender.signal_onboarding_completed_at.blank? + trigger(CONNECT, sender, signal_uuid) return nil end sender end + def initialize_delivery_receipt(signal_message) + return nil unless is_delivery_receipt?(signal_message) + + delivery_receipt = signal_message.dig(:envelope, :receiptMessage) + + trigger(HANDLE_DELIVERY_RECEIPT, delivery_receipt, sender) + delivery_receipt + end + def initialize_message(signal_message) is_data_message = signal_message.dig(:envelope, :dataMessage) return nil unless is_data_message @@ -148,5 +160,9 @@ def create_message? text = message.text has_non_text_content || (text.present? && !unsubscribe_text?(text) && !resubscribe_text?(text)) end + + def delivery_receipt?(signal_message) + signal_message.dig(:envelope, :receiptMessage) + end end end diff --git a/app/adapters/signal_adapter/outbound.rb b/app/adapters/signal_adapter/outbound.rb index e130a9aa6..36004ab5f 100644 --- a/app/adapters/signal_adapter/outbound.rb +++ b/app/adapters/signal_adapter/outbound.rb @@ -42,7 +42,7 @@ def send_resubscribe_error_message!(contributor) end def contributor_can_receive_messages?(recipient) - recipient&.signal_phone_number.present? + (recipient&.signal_phone_number.present? || recipient&.signal_uuid.present?) && recipient&.signal_onboarding_completed_at.present? end end end diff --git a/app/adapters/signal_adapter/outbound/file.rb b/app/adapters/signal_adapter/outbound/file.rb index 1313719ea..fa5597b2f 100644 --- a/app/adapters/signal_adapter/outbound/file.rb +++ b/app/adapters/signal_adapter/outbound/file.rb @@ -28,7 +28,7 @@ def data end { number: Setting.signal_server_phone_number, - recipients: [message.recipient.signal_phone_number], + recipients: [message.recipient.signal_phone_number || message.recipient.signal_uuid], message: message.text, base64_attachments: base64_files } diff --git a/app/adapters/signal_adapter/outbound/text.rb b/app/adapters/signal_adapter/outbound/text.rb index 97e222e98..d95f55b72 100644 --- a/app/adapters/signal_adapter/outbound/text.rb +++ b/app/adapters/signal_adapter/outbound/text.rb @@ -28,7 +28,7 @@ def perform(contributor_id:, text:) def data { number: Setting.signal_server_phone_number, - recipients: [recipient.signal_phone_number], + recipients: [recipient.signal_phone_number || recipient.signal_uuid], message: text } end diff --git a/app/components/contributor_signal_settings/contributor_signal_settings.html.erb b/app/components/contributor_signal_settings/contributor_signal_settings.html.erb index 91f6e639b..914175fbc 100644 --- a/app/components/contributor_signal_settings/contributor_signal_settings.html.erb +++ b/app/components/contributor_signal_settings/contributor_signal_settings.html.erb @@ -3,11 +3,32 @@ <%= t('.heading') %> <% end %> -

- <%= t('.complete.text', - name: contributor.name, - phone_number: contributor.signal_phone_number.phony_formatted - ) %> -

+ <% if contributor.signal_onboarding_completed_at.present? %> +

+ <%= t('.complete.text', + name: contributor.name, + phone_number: contributor.signal_phone_number&.phony_formatted || contributor.signal_uuid + ) %> +

+ <% else %> + <%= c 'callout', style: :warning do %> + <%= c 'stack', space: :small do %> +

+ <%= t('.incomplete.text', + name: contributor.name, + first_name: contributor.first_name, + date: l(contributor.created_at.to_date), + phone_number: contributor.signal_phone_number&.phony_formatted || contributor.signal_uuid + ) %> +

+ + <%= c 'copy_button', + style: :secondary, + copy: onboarding_signal_link_url(signal_onboarding_token: contributor.signal_onboarding_token), + label: t('.incomplete.action') + %> + <% end %> + <% end %> + <% end %> <% end %> diff --git a/app/components/onboarding_signal_form/onboarding_signal_form.html.erb b/app/components/onboarding_signal_form/onboarding_signal_form.html.erb index 1930319b0..e53c96e50 100644 --- a/app/components/onboarding_signal_form/onboarding_signal_form.html.erb +++ b/app/components/onboarding_signal_form/onboarding_signal_form.html.erb @@ -18,9 +18,11 @@ <%= c 'input', field.input_defaults.merge(required: true) %> <% end %> - <%= c 'field', object: contributor, attr: :signal_username do |field| %> - <%= c 'input', field.input_defaults.merge(placeholder: '', required: true) %> - <% end %> + <%= c 'input', + id: 'contributor[signal_onboarding_token]', + type: 'hidden', + value: contributor.signal_onboarding_token + %> <%= c 'onboarding_consent', contributor: contributor %> diff --git a/app/components/onboarding_signal_link/onboarding_signal_link.html.erb b/app/components/onboarding_signal_link/onboarding_signal_link.html.erb new file mode 100644 index 000000000..cce61a32c --- /dev/null +++ b/app/components/onboarding_signal_link/onboarding_signal_link.html.erb @@ -0,0 +1,14 @@ +<%= c 'stack', **attrs do %> + <%= c 'stack', class: 'text-serif text-center', space: :xsmall do %> + <%= c 'heading', tag: :h1 do %> + <%= t 'components.onboarding_signal_link.heading' %> + <% end %> + +

<%= t 'components.onboarding_signal_link.text' %>

+ <% end %> + + <%= c 'steps_list', + class: 'text-serif', + steps: fallback_steps.map(&:html_safe) + %> +<% end %> diff --git a/app/components/onboarding_signal_link/onboarding_signal_link.rb b/app/components/onboarding_signal_link/onboarding_signal_link.rb new file mode 100644 index 000000000..4c00f317c --- /dev/null +++ b/app/components/onboarding_signal_link/onboarding_signal_link.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module OnboardingSignalLink + class OnboardingSignalLink < ApplicationComponent + def initialize(signal_onboarding_token:, **) + super + + @signal_onboarding_token = signal_onboarding_token + end + + private + + attr_reader :signal_onboarding_token + + def fallback_steps + data = { signal_server_phone_number: Setting.signal_server_phone_number, token: signal_onboarding_token } + I18n.t('components.onboarding_signal_link.steps', **data) + end + end +end diff --git a/app/controllers/onboarding/signal_controller.rb b/app/controllers/onboarding/signal_controller.rb index 3020d30da..0ff9a51f8 100644 --- a/app/controllers/onboarding/signal_controller.rb +++ b/app/controllers/onboarding/signal_controller.rb @@ -4,12 +4,28 @@ module Onboarding class SignalController < OnboardingController skip_before_action :verify_jwt, only: :link - def link; end + def show + super + @contributor.signal_onboarding_token = SecureRandom.alphanumeric(8).upcase + end + + def link + @signal_onboarding_token = signal_onboarding_token + end private + def redirect_to_success + signal_onboarding_token = @contributor.signal_onboarding_token + redirect_to onboarding_signal_link_path(signal_onboarding_token: signal_onboarding_token) + end + def attr_name - :signal_phone_number + :signal_onboarding_token + end + + def signal_onboarding_token + params.require(:signal_onboarding_token) end end end diff --git a/app/controllers/onboarding_controller.rb b/app/controllers/onboarding_controller.rb index e742c3a68..b038f2810 100644 --- a/app/controllers/onboarding_controller.rb +++ b/app/controllers/onboarding_controller.rb @@ -4,6 +4,7 @@ class OnboardingController < ApplicationController skip_before_action :require_login before_action :verify_jwt, except: :success before_action :resume_telegram_onboarding, only: %i[index show] + before_action :resume_signal_onboarding, only: %i[index show] before_action :redirect_if_contributor_exists, only: :create rescue_from ActionController::BadRequest, with: :render_unauthorized @@ -83,7 +84,7 @@ def render_unauthorized def verify_jwt return unless jwt - raise ActionController::BadRequest unless resume_telegram_onboarding? + raise ActionController::BadRequest unless resume_telegram_onboarding? || resume_signal_onboarding? end def resume_telegram_onboarding @@ -98,6 +99,18 @@ def resume_telegram_onboarding? contributor&.telegram_id.blank? && contributor&.telegram_onboarding_token.present? end + def resume_signal_onboarding + return unless resume_signal_onboarding? + + token = jwt.contributor.signal_onboarding_token + redirect_to onboarding_signal_link_path(signal_onboarding_token: token) + end + + def resume_signal_onboarding? + contributor = jwt&.contributor + contributor&.signal_uuid.blank? && contributor&.signal_onboarding_token.present? + end + def jwt decoded_token = JsonWebToken.decode(jwt_param) action = decoded_token.first['data']['action'] diff --git a/app/jobs/signal_adapter/receive_polling_job.rb b/app/jobs/signal_adapter/receive_polling_job.rb index f4142ef81..586232282 100644 --- a/app/jobs/signal_adapter/receive_polling_job.rb +++ b/app/jobs/signal_adapter/receive_polling_job.rb @@ -39,8 +39,8 @@ def request_new_messages end def handle_callbacks - adapter.on(SignalAdapter::CONNECT) do |contributor| - handle_connect(contributor) + adapter.on(SignalAdapter::CONNECT) do |contributor, signal_uuid| + handle_connect(contributor, signal_uuid) end adapter.on(SignalAdapter::UNKNOWN_CONTRIBUTOR) do |signal_uuid| @@ -77,16 +77,14 @@ def queue_empty? end def handle_connect(contributor, signal_uuid) - contributor.update!(signal_uuid) + contributor.update!(signal_uuid: signal_uuid, signal_onboarding_completed_at: Time.current) SignalAdapter::CreateContactJob.perform_later(contributor_id: contributor.id) SignalAdapter::AttachContributorsAvatarJob.perform_later(contributor_id: contributor.id) + SignalAdapter::Outbound.send_welcome_message!(contributor) end def handle_delivery_receipt(signal_message, contributor) delivery_receipt = signal_message.dig(:envelope, :receiptMessage) - signal_uuid = signal_message.dig(:envelope, :sourceUuid) - handle_connect(contributor, signal_uuid) if contributor.signal_uuid.blank? - datetime = Time.zone.at(delivery_receipt[:when] / 1000).to_datetime latest_received_message = contributor.received_messages.first return unless latest_received_message diff --git a/app/views/onboarding/signal/link.html.erb b/app/views/onboarding/signal/link.html.erb new file mode 100644 index 000000000..c9d08f1f3 --- /dev/null +++ b/app/views/onboarding/signal/link.html.erb @@ -0,0 +1,5 @@ +
+ <%= c 'interstitial' do %> + <%= c 'onboarding_signal_link', signal_onboarding_token: @signal_onboarding_token %> + <% end %> +
diff --git a/config/locales/de.yml b/config/locales/de.yml index 5adae4afb..4e2aba208 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -82,11 +82,16 @@ de: other: Du hast {count} neue Antworten. onboarding_header: byline: eine Dialog-Recherche von tactile.news + onboarding_signal_link: + heading: Fast geschafft. + text: Verbinden Sie Ihr Signal-Konto in der Signal-App, um die Anmeldung abzuschließen. + steps: + - "Finden Sie uns auf Signal. Suchen Sie dazu nach %{signal_server_phone_number}." + - "Wenn Sie unser Signal-Konto gefunden haben, geben Sie diesen Code ein, sobald sie danach gefragt werden: %{token}" onboarding_signal_form: heading: Mit Signal teilnehmen text: Geben Sie bitte unten die Handynummer ein, mit der Sie bei Signal registriert sind. submit: Anmeldung abschließen - onboarding_whats_app_form: heading: Mit WhatsApp teilnehmen text: Geben Sie bitte unten die Handynummer ein, mit der Sie bei WhatsApp registriert sind. diff --git a/db/migrate/20240618054327_update_signal_attrs_on_contributors.rb b/db/migrate/20240618054327_update_signal_attrs_on_contributors.rb index a6d070882..a858803c5 100644 --- a/db/migrate/20240618054327_update_signal_attrs_on_contributors.rb +++ b/db/migrate/20240618054327_update_signal_attrs_on_contributors.rb @@ -3,9 +3,8 @@ class UpdateSignalAttrsOnContributors < ActiveRecord::Migration[6.1] def change change_table :contributors, bulk: true do |t| - t.remove :signal_onboarding_completed_at, type: :datetime t.column :signal_uuid, :string, default: nil - t.column :signal_username, :string, default: nil + t.column :signal_onboarding_token, :string, unique: true end end end diff --git a/db/schema.rb b/db/schema.rb index 4718d07d2..4731f1de2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_06_18_054327) do +ActiveRecord::Schema.define(version: 2024_06_21_100548) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -106,6 +106,7 @@ t.datetime "data_processing_consented_at" t.string "telegram_onboarding_token" t.string "signal_phone_number" + t.datetime "signal_onboarding_completed_at" t.string "additional_email" t.datetime "additional_consent_given_at" t.bigint "organization_id" @@ -116,7 +117,7 @@ t.datetime "whats_app_message_template_sent_at" t.datetime "unsubscribed_at" t.string "signal_uuid" - t.string "signal_username" + t.string "signal_onboarding_token" t.index ["email"], name: "index_contributors_on_email", unique: true t.index ["organization_id"], name: "index_contributors_on_organization_id" t.index ["signal_phone_number"], name: "index_contributors_on_signal_phone_number", unique: true diff --git a/docker-compose.yml b/docker-compose.yml index 9af9a1b12..899ca3450 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: depends_on: [ signal ] signal: - image: tactilenews/signal-cli-rest-api:latest + image: bbernhard/signal-cli-rest-api:0.162-dev environment: - MODE=native #- AUTO_RECEIVE_SCHEDULE=0 22 * * * #enable this parameter on demand (see description below) From 06ce4371bb4431cf02c76bad2d7559ad16ed46fc Mon Sep 17 00:00:00 2001 From: Matthew Rider Date: Tue, 25 Jun 2024 21:47:47 +0200 Subject: [PATCH 04/10] Start fixing tests --- app/adapters/signal_adapter/inbound.rb | 51 +++++----- .../contributor_signal_settings.html.erb | 16 +-- .../contributor_signal_settings.rb | 20 ++++ app/models/contributor.rb | 2 +- config/locales/de.yml | 9 +- spec/adapters/signal_adapter/inbound_spec.rb | 97 ++++++++++++++----- spec/adapters/signal_adapter/outbound_spec.rb | 88 ++++++++++++++++- .../contributor_signal_settings_spec.rb | 45 +++++++++ 8 files changed, 260 insertions(+), 68 deletions(-) diff --git a/app/adapters/signal_adapter/inbound.rb b/app/adapters/signal_adapter/inbound.rb index 0ada7458d..2ff9d7fe3 100644 --- a/app/adapters/signal_adapter/inbound.rb +++ b/app/adapters/signal_adapter/inbound.rb @@ -25,12 +25,16 @@ def on(callback, &block) def consume(signal_message) signal_message = signal_message.with_indifferent_access - @sender = initialize_sender(signal_message) - return unless @sender + @sender = initialize_contributing_sender(signal_message) delivery_receipt = initialize_delivery_receipt(signal_message) return if delivery_receipt + unless @sender + initialize_onboarding_sender(signal_message) + return + end + remove_emoji = signal_message.dig(:envelope, :dataMessage, :reaction, :isRemove) return if remove_emoji @@ -53,18 +57,29 @@ def trigger(event, *args) @callbacks[event].call(*args) end - def initialize_sender(signal_message) + def initialize_contributing_sender(signal_message) signal_phone_number = signal_message.dig(:envelope, :sourceNumber) signal_uuid = signal_message.dig(:envelope, :sourceUuid) - sender = if signal_phone_number - Contributor.find_by(signal_phone_number: signal_phone_number) - else - Contributor.find_by(signal_uuid: signal_uuid) - end - return sender if sender + if signal_phone_number + Contributor.find_by(signal_phone_number: signal_phone_number) + else + Contributor.find_by(signal_uuid: signal_uuid) + end + end + + def initialize_delivery_receipt(signal_message) + return nil unless delivery_receipt?(signal_message) && sender + + delivery_receipt = signal_message.dig(:envelope, :receiptMessage) + + trigger(HANDLE_DELIVERY_RECEIPT, delivery_receipt, sender) + delivery_receipt + end + def initialize_onboarding_sender(signal_message) + signal_uuid = signal_message.dig(:envelope, :sourceUuid) signal_onboarding_token = signal_message.dig(:envelope, :dataMessage, :message) - return nil unless signal_onboarding_token + return nil unless signal_onboarding_token && signal_uuid sender = Contributor.find_by(signal_onboarding_token: signal_onboarding_token.strip) @@ -73,21 +88,7 @@ def initialize_sender(signal_message) return nil end - if sender.signal_onboarding_completed_at.blank? - trigger(CONNECT, sender, signal_uuid) - return nil - end - - sender - end - - def initialize_delivery_receipt(signal_message) - return nil unless is_delivery_receipt?(signal_message) - - delivery_receipt = signal_message.dig(:envelope, :receiptMessage) - - trigger(HANDLE_DELIVERY_RECEIPT, delivery_receipt, sender) - delivery_receipt + trigger(CONNECT, sender, signal_uuid) end def initialize_message(signal_message) diff --git a/app/components/contributor_signal_settings/contributor_signal_settings.html.erb b/app/components/contributor_signal_settings/contributor_signal_settings.html.erb index 914175fbc..50508c2b5 100644 --- a/app/components/contributor_signal_settings/contributor_signal_settings.html.erb +++ b/app/components/contributor_signal_settings/contributor_signal_settings.html.erb @@ -4,23 +4,11 @@ <% end %> <% if contributor.signal_onboarding_completed_at.present? %> -

- <%= t('.complete.text', - name: contributor.name, - phone_number: contributor.signal_phone_number&.phony_formatted || contributor.signal_uuid - ) %> -

+

<%= completed_onboarding_text %>

<% else %> <%= c 'callout', style: :warning do %> <%= c 'stack', space: :small do %> -

- <%= t('.incomplete.text', - name: contributor.name, - first_name: contributor.first_name, - date: l(contributor.created_at.to_date), - phone_number: contributor.signal_phone_number&.phony_formatted || contributor.signal_uuid - ) %> -

+

<%= incomplete_onboarding_text %>

<%= c 'copy_button', style: :secondary, diff --git a/app/components/contributor_signal_settings/contributor_signal_settings.rb b/app/components/contributor_signal_settings/contributor_signal_settings.rb index caa06f93b..cdba4fd7e 100644 --- a/app/components/contributor_signal_settings/contributor_signal_settings.rb +++ b/app/components/contributor_signal_settings/contributor_signal_settings.rb @@ -11,5 +11,25 @@ def initialize(contributor:, **) private attr_reader :contributor + + def completed_onboarding_text + if contributor.signal_phone_number.present? + t('.complete.text.phone_number', + name: contributor.name, + phone_number: contributor.signal_phone_number&.phony_formatted) + else + + t('.complete.text.uuid', + name: contributor.name, + uuid: contributor.signal_uuid) + end + end + + def incomplete_onboarding_text + t('.incomplete.text', + name: contributor.name, + first_name: contributor.first_name, + date: l(contributor.created_at.to_date)) + end end end diff --git a/app/models/contributor.rb b/app/models/contributor.rb index 95c919a72..649663643 100644 --- a/app/models/contributor.rb +++ b/app/models/contributor.rb @@ -54,7 +54,7 @@ class Contributor < ApplicationRecord scope :with_email, -> { where.not(email: nil) } scope :with_threema, -> { where.not(threema_id: nil) } scope :with_telegram, -> { where.not(telegram_id: nil) } - scope :with_signal, -> { where.not(signal_phone_number: nil) } + scope :with_signal, -> { where.not(signal_phone_number: nil).and(where.not(signal_uuid: nil)) } scope :with_whats_app, -> { where.not(whats_app_phone_number: nil) } before_validation do diff --git a/config/locales/de.yml b/config/locales/de.yml index 4e2aba208..c95785f0c 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -242,11 +242,14 @@ de: contributor_signal_settings: heading: "Ausgewählter Kanal: Signal" complete: - text: | - %{name} hat sich mit der Handynummer %{phone_number} angemeldet. + text: + phone_number: | + %{name} hat sich mit der Handynummer %{phone_number} angemeldet. + uuid: | + %{name} hat sich mit der UUID %{uuid} angemeldet. incomplete: text: | - %{name} hat sich am %{date} via Signal mit der Handynummer %{phone_number} angemeldet, die Anmeldung aber noch nicht abgeschlossen. + %{name} hat sich am %{date} via Signal angemeldet, die Anmeldung aber noch nicht abgeschlossen. Sende %{first_name} einen Link mit Hinweisen zum Abschließen der Anmeldung. action: Link kopieren contributor_whats_app_settings: diff --git a/spec/adapters/signal_adapter/inbound_spec.rb b/spec/adapters/signal_adapter/inbound_spec.rb index a027125b1..ad94877a8 100644 --- a/spec/adapters/signal_adapter/inbound_spec.rb +++ b/spec/adapters/signal_adapter/inbound_spec.rb @@ -8,6 +8,8 @@ { envelope: { source: '+4912345789', + sourceNumber: '+4912345789', + sourceUuid: 'valid_uuid', sourceDevice: 2, timestamp: 1_626_708_555_697, dataMessage: { @@ -20,6 +22,24 @@ } end + let(:signal_message_with_uuid) do + { + envelope: { + source: 'valid_uuid', + sourceNumber: nil, + sourceUuid: 'valid_uuid', + sourceDevice: 2, + timestamp: 1_626_708_555_697, + dataMessage: { + timestamp: 1_626_708_555_697, + message: signal_onboarding_token, + expiresInSeconds: 0, + viewOnce: false + } + } + } + end + let(:signal_receipt_message) do { envelope: { @@ -178,7 +198,8 @@ create( :contributor, id: 4711, - signal_phone_number: phone_number + signal_phone_number: phone_number, + signal_onboarding_completed_at: onboarding_completed_at ) end @@ -194,6 +215,7 @@ it { should be_a(Message) } context 'from an unknown contributor' do + let(:onboarding_completed_at) { nil } let!(:phone_number) { '+495555555' } it { should be(nil) } @@ -240,6 +262,37 @@ expect(message.files.first.attachment).to be_attached end end + + describe 'given a message to complete onboarding' do + let(:signal_message) { signal_message_with_uuid } + let(:signal_uuid) { nil } + let(:onboarding_completed_at) { nil } + + let!(:contributor) do + create( + :contributor, + signal_uuid: signal_uuid, + signal_onboarding_completed_at: onboarding_completed_at, + signal_onboarding_token: 'NQ272QQK' + ) + end + + context 'unknown contributor' do + let(:signal_onboarding_token) { 'some other message' } + + it 'does not create a message' do + expect(subject).to be(nil) + end + end + + context 'known contributor' do + let(:signal_onboarding_token) { 'NQ272QQK' } + + it 'does not create a message' do + expect(subject).to be(nil) + end + end + end end describe '|message|text' do @@ -353,10 +406,14 @@ describe '#on' do describe 'CONNECT' do let(:connect_callback) { spy('connect_callback') } + let(:signal_message) { signal_message_with_uuid } + let(:signal_uuid) { signal_message.dig(:envelope, :sourceUuid) } + + let!(:contributor) { create(:contributor, signal_onboarding_token: 'NQ272QQK') } before do - adapter.on(SignalAdapter::CONNECT) do |contributor| - connect_callback.call(contributor) + adapter.on(SignalAdapter::CONNECT) do |contributor, signal_uuid| + connect_callback.call(contributor, signal_uuid) end end @@ -366,26 +423,26 @@ end context 'if the sender is unknown' do - let(:phone_number) { nil } + let(:signal_onboarding_token) { 'whatever message' } it { should_not have_received(:call) } end - context 'if the sender is a contributor with no replies' do - it { should have_received(:call).with(contributor) } - end - - context 'if the sender is a contributor with replies' do - before { create(:message, sender: contributor) } - it { should_not have_received(:call) } + context 'if the sender is a contributor with incomplete onboarding' do + let(:signal_onboarding_token) { 'NQ272QQK' } + it { should have_received(:call).with(contributor, signal_uuid) } end end describe 'UNKNOWN_CONTRIBUTOR' do let(:unknown_contributor_callback) { spy('unknown_contributor_callback') } + let(:signal_message) { signal_message_with_uuid } + let(:signal_uuid) { signal_message.dig(:envelope, :sourceUuid) } + let(:source) { signal_message.dig(:envelope, :source) } + let!(:contributor) { create(:contributor, signal_onboarding_token: 'NQ272QQK') } before do - adapter.on(SignalAdapter::UNKNOWN_CONTRIBUTOR) do |signal_phone_number| - unknown_contributor_callback.call(signal_phone_number) + adapter.on(SignalAdapter::UNKNOWN_CONTRIBUTOR) do |source| + unknown_contributor_callback.call(source) end end @@ -394,18 +451,14 @@ unknown_contributor_callback end - describe 'if the sender is a contributor ' do + context 'if the sender is a contributor with incomplete onboarding' do + let(:signal_onboarding_token) { 'NQ272QQK' } it { should_not have_received(:call) } end - describe 'if the sender has not completed onboarding' do - let(:onboarding_completed_at) { nil } - it { should_not have_received(:call) } - end - - describe 'if the sender is unknown' do - before { signal_message[:envelope][:source] = '+4955443322' } - it { should have_received(:call).with('+4955443322') } + context 'if the sender is unknown' do + let(:signal_onboarding_token) { 'whatever message' } + it { should have_received(:call).with(source) } end end diff --git a/spec/adapters/signal_adapter/outbound_spec.rb b/spec/adapters/signal_adapter/outbound_spec.rb index cae080e2f..781e58276 100644 --- a/spec/adapters/signal_adapter/outbound_spec.rb +++ b/spec/adapters/signal_adapter/outbound_spec.rb @@ -10,6 +10,7 @@ let(:expected_job_args) do { contributor_id: contributor.id, text: [Setting.onboarding_success_heading, Setting.onboarding_success_text].join("\n") } end + let(:onboarding_completed_at) { nil } describe '::send_welcome_message!' do subject { -> { described_class.send_welcome_message!(contributor) } } @@ -22,17 +23,60 @@ create( :contributor, signal_phone_number: '+491511234567', + signal_onboarding_completed_at: onboarding_completed_at, email: nil ) end - it { should enqueue_job(described_class::Text) } + context 'but has not completed onboarding' do + it 'does not enqueue the job to send the welcome message' do + expect { subject.call }.not_to enqueue_job(described_class::Text) + end + end + + context 'has completed onboarding' do + let(:onboarding_completed_at) { 1.minute.ago } + + it 'enqueus the job to send out the welcome message' do + expect { subject.call }.to enqueue_job(described_class::Text).with(expected_job_args) + end + end + end + + context 'contributor has a uuid' do + let(:contributor) do + create( + :contributor, + signal_uuid: 'valid_uuid', + signal_onboarding_completed_at: onboarding_completed_at, + email: nil + ) + end + + context 'but has not completed onboarding' do + it 'does not enqueue the job to send the welcome message' do + expect { subject.call }.not_to enqueue_job(described_class::Text) + end + end + + context 'has completed onboarding' do + let(:onboarding_completed_at) { 1.minute.ago } + + it 'enqueus the job to send out the welcome message' do + expect { subject.call }.to enqueue_job(described_class::Text).with(expected_job_args) + end + end end end describe '::send!' do subject { -> { described_class.send!(message) } } + + let(:expected_job_args) do + { contributor_id: contributor.id, text: message.text } + end before { message } # we don't count the extra ::send here + it { should_not enqueue_job(described_class::Text) } describe 'contributor has a phone number' do @@ -40,11 +84,49 @@ create( :contributor, email: nil, - signal_phone_number: '+491511234567' + signal_phone_number: '+491511234567', + signal_onboarding_completed_at: onboarding_completed_at ) end - it { should enqueue_job(described_class::Text) } + context 'but has not completed onboarding' do + it 'does not enqueue the job to send the welcome message' do + expect { subject.call }.not_to enqueue_job(described_class::Text) + end + end + + context 'has completed onboarding' do + let(:onboarding_completed_at) { 1.minute.ago } + + it 'enqueus the job to send out the welcome message' do + expect { subject.call }.to enqueue_job(described_class::Text).with(expected_job_args) + end + end + end + + describe 'contributor has a uuid' do + let(:contributor) do + create( + :contributor, + email: nil, + signal_uuid: 'valid_uuid', + signal_onboarding_completed_at: onboarding_completed_at + ) + end + + context 'but has not completed onboarding' do + it 'does not enqueue the job to send the welcome message' do + expect { subject.call }.not_to enqueue_job(described_class::Text) + end + end + + context 'has completed onboarding' do + let(:onboarding_completed_at) { 1.minute.ago } + + it 'enqueus the job to send out the welcome message' do + expect { subject.call }.to enqueue_job(described_class::Text).with(expected_job_args) + end + end end end end diff --git a/spec/components/contributor_signal_settings_spec.rb b/spec/components/contributor_signal_settings_spec.rb index c25693579..59517d502 100644 --- a/spec/components/contributor_signal_settings_spec.rb +++ b/spec/components/contributor_signal_settings_spec.rb @@ -6,15 +6,60 @@ subject { render_inline(described_class.new(**params)) } let(:params) { { contributor: contributor } } + let(:complete_onboarding_link) { onboarding_signal_link_path(signal_onboarding_token: contributor.signal_onboarding_token) } let(:contributor) do create(:contributor, first_name: 'Max', last_name: 'Mustermann', signal_phone_number: '+4915112345678', + signal_onboarding_completed_at: onboarding_completed_at, created_at: '2021-01-01') end + let(:onboarding_completed_at) { Time.zone.now } + it { should have_css('h2', text: 'Signal') } it { should have_css('p', text: 'Max Mustermann hat sich mit der Handynummer 0151 1234 5678 angemeldet.') } + + context 'given a contributor with incomplete onboarding' do + let(:onboarding_completed_at) { nil } + + it { + should have_css('p', + text: 'Max Mustermann hat sich am 01.01.2021 via Signal angemeldet, die Anmeldung aber noch nicht abgeschlossen.') + } + it { should have_css('p', text: 'Sende Max einen Link mit Hinweisen zum Abschließen der Anmeldung.') } + it { should have_css("button[data-copy-button-copy-value$='#{complete_onboarding_link}']") } + end + + describe 'given a contributor with signal_uuid' do + let(:contributor) do + create(:contributor, + first_name: 'Max', + last_name: 'Mustermann', + signal_phone_number: nil, + signal_uuid: signal_uuid, + signal_onboarding_completed_at: onboarding_completed_at, + created_at: '2021-01-01') + end + + let(:onboarding_completed_at) { Time.current } + let(:signal_uuid) { 'valid_uuid' } + + it { should have_css('h2', text: 'Signal') } + it { should have_css('p', text: 'Max Mustermann hat sich mit der UUID valid_uuid angemeldet.') } + + context 'given a contributor with incomplete onboarding' do + let(:onboarding_completed_at) { nil } + let(:signal_uuid) { nil } + + it { + should have_css('p', + text: 'Max Mustermann hat sich am 01.01.2021 via Signal angemeldet, die Anmeldung aber noch nicht abgeschlossen.') + } + it { should have_css('p', text: 'Sende Max einen Link mit Hinweisen zum Abschließen der Anmeldung.') } + it { should have_css("button[data-copy-button-copy-value$='#{complete_onboarding_link}']") } + end + end end From a9f9f9c53fa8489decc62cd3a6fddf7f8a90f0b8 Mon Sep 17 00:00:00 2001 From: Matthew Rider Date: Wed, 26 Jun 2024 07:57:45 +0200 Subject: [PATCH 05/10] Fixing more specs --- .../onboarding/signal_controller.rb | 2 +- package-lock.json | 4456 ++++++++++++++++- ...ofile-+491212343434 => profile-valid_uuid} | Bin spec/jobs/resubscribe_contributor_job_spec.rb | 4 +- .../attach_contributors_avatar_job_spec.rb | 10 +- spec/jobs/unsubscribe_contributor_job_spec.rb | 2 +- spec/requests/onboarding/signal_spec.rb | 54 +- 7 files changed, 4451 insertions(+), 77 deletions(-) rename spec/fixtures/files/{profile-+491212343434 => profile-valid_uuid} (100%) diff --git a/app/controllers/onboarding/signal_controller.rb b/app/controllers/onboarding/signal_controller.rb index 0ff9a51f8..df8cca3fc 100644 --- a/app/controllers/onboarding/signal_controller.rb +++ b/app/controllers/onboarding/signal_controller.rb @@ -17,7 +17,7 @@ def link def redirect_to_success signal_onboarding_token = @contributor.signal_onboarding_token - redirect_to onboarding_signal_link_path(signal_onboarding_token: signal_onboarding_token) + redirect_to onboarding_signal_link_path(signal_onboarding_token: signal_onboarding_token, jwt: nil) end def attr_name diff --git a/package-lock.json b/package-lock.json index d73f84009..ff3bd507b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,4406 @@ { + "name": "app", + "lockfileVersion": 2, "requires": true, - "lockfileVersion": 1, + "packages": { + "": { + "dependencies": { + "@hotwired/stimulus": "^3.2.1", + "@hotwired/turbo": "^7.2.2", + "@rails/ujs": "^6.1.5", + "@yaireo/tagify": "^3.25.0", + "apexcharts": "^3.36.3", + "clipboard-polyfill": "^3.0.3", + "dialog-polyfill": "^0.5.6" + }, + "devDependencies": { + "@arkweid/lefthook": "^0.7.7", + "cssnano": "^5.1.13", + "esbuild": "^0.17.10", + "esbuild-plugin-import-glob": "^0.1.1", + "npm-run-all": "^4.1.5", + "postcss": "^8.4.38", + "postcss-cli": "^9.1.0", + "postcss-easy-import": "^3.0.0", + "postcss-preset-env": "^7.8.2", + "prettier": "^2.7.1" + } + }, + "node_modules/@arkweid/lefthook": { + "version": "0.7.7", + "resolved": "https://registry.yarnpkg.com/@arkweid/lefthook/-/lefthook-0.7.7.tgz", + "integrity": "sha1-EpUbCblV2AVIhf/pKaoHpJ85Anw= sha512-Eq30OXKmjxIAIsTtbX2fcF3SNZIXS8yry1u8yty7PQFYRctx04rVlhOJCEB2UmfTh8T2vrOMC9IHHUvvo5zbaQ==", + "cpu": [ + "x64", + "arm64", + "ia32" + ], + "deprecated": "@arkweid/lefthook has been renamed to @evilmartians/lefthook, please upgrade to it. You also can use @evilmartians/lefthook-installer", + "dev": true, + "hasInstallScript": true, + "os": [ + "darwin", + "linux", + "win32" + ], + "bin": { + "lefthook": "bin/index.js" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", + "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", + "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", + "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2", + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.10.tgz", + "integrity": "sha512-7YEBfZ5lSem9Tqpsz+tjbdsEshlO9j/REJrfv4DXgKTt1+/MHqGwbtlyxQuaSlMeUZLxUKBaX8wdzlTfHkmnLw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.10.tgz", + "integrity": "sha512-ht1P9CmvrPF5yKDtyC+z43RczVs4rrHpRqrmIuoSvSdn44Fs1n6DGlpZKdK6rM83pFLbVaSUwle8IN+TPmkv7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.10.tgz", + "integrity": "sha512-CYzrm+hTiY5QICji64aJ/xKdN70IK8XZ6iiyq0tZkd3tfnwwSWTYH1t3m6zyaaBxkuj40kxgMyj1km/NqdjQZA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.10.tgz", + "integrity": "sha512-3HaGIowI+nMZlopqyW6+jxYr01KvNaLB5znXfbyyjuo4lE0VZfvFGcguIJapQeQMS4cX/NEispwOekJt3gr5Dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.10.tgz", + "integrity": "sha512-J4MJzGchuCRG5n+B4EHpAMoJmBeAE1L3wGYDIN5oWNqX0tEr7VKOzw0ymSwpoeSpdCa030lagGUfnfhS7OvzrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.10.tgz", + "integrity": "sha512-ZkX40Z7qCbugeK4U5/gbzna/UQkM9d9LNV+Fro8r7HA7sRof5Rwxc46SsqeMvB5ZaR0b1/ITQ/8Y1NmV2F0fXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.10.tgz", + "integrity": "sha512-0m0YX1IWSLG9hWh7tZa3kdAugFbZFFx9XrvfpaCMMvrswSTvUZypp0NFKriUurHpBA3xsHVE9Qb/0u2Bbi/otg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.10.tgz", + "integrity": "sha512-whRdrrl0X+9D6o5f0sTZtDM9s86Xt4wk1bf7ltx6iQqrIIOH+sre1yjpcCdrVXntQPCNw/G+XqsD4HuxeS+2QA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.10.tgz", + "integrity": "sha512-g1EZJR1/c+MmCgVwpdZdKi4QAJ8DCLP5uTgLWSAVd9wlqk9GMscaNMEViG3aE1wS+cNMzXXgdWiW/VX4J+5nTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.10.tgz", + "integrity": "sha512-1vKYCjfv/bEwxngHERp7huYfJ4jJzldfxyfaF7hc3216xiDA62xbXJfRlradiMhGZbdNLj2WA1YwYFzs9IWNPw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.10.tgz", + "integrity": "sha512-mvwAr75q3Fgc/qz3K6sya3gBmJIYZCgcJ0s7XshpoqIAIBszzfXsqhpRrRdVFAyV1G9VUjj7VopL2HnAS8aHFA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.10.tgz", + "integrity": "sha512-XilKPgM2u1zR1YuvCsFQWl9Fc35BqSqktooumOY2zj7CSn5czJn279j9TE1JEqSqz88izJo7yE4x3LSf7oxHzg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.10.tgz", + "integrity": "sha512-kM4Rmh9l670SwjlGkIe7pYWezk8uxKHX4Lnn5jBZYBNlWpKMBCVfpAgAJqp5doLobhzF3l64VZVrmGeZ8+uKmQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.10.tgz", + "integrity": "sha512-r1m9ZMNJBtOvYYGQVXKy+WvWd0BPvSxMsVq8Hp4GzdMBQvfZRvRr5TtX/1RdN6Va8JMVQGpxqde3O+e8+khNJQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.10.tgz", + "integrity": "sha512-LsY7QvOLPw9WRJ+fU5pNB3qrSfA00u32ND5JVDrn/xG5hIQo3kvTxSlWFRP0NJ0+n6HmhPGG0Q4jtQsb6PFoyg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.10.tgz", + "integrity": "sha512-zJUfJLebCYzBdIz/Z9vqwFjIA7iSlLCFvVi7glMgnu2MK7XYigwsonXshy9wP9S7szF+nmwrelNaP3WGanstEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.10.tgz", + "integrity": "sha512-lOMkailn4Ok9Vbp/q7uJfgicpDTbZFlXlnKT2DqC8uBijmm5oGtXAJy2ZZVo5hX7IOVXikV9LpCMj2U8cTguWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.10.tgz", + "integrity": "sha512-/VE0Kx6y7eekqZ+ZLU4AjMlB80ov9tEz4H067Y0STwnGOYL8CsNg4J+cCmBznk1tMpxMoUOf0AbWlb1d2Pkbig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.10.tgz", + "integrity": "sha512-ERNO0838OUm8HfUjjsEs71cLjLMu/xt6bhOlxcJ0/1MG3hNqCmbWaS+w/8nFLa0DDjbwZQuGKVtCUJliLmbVgg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.10.tgz", + "integrity": "sha512-fXv+L+Bw2AeK+XJHwDAQ9m3NRlNemG6Z6ijLwJAAVdu4cyoFbBWbEtyZzDeL+rpG2lWI51cXeMt70HA8g2MqIg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.10.tgz", + "integrity": "sha512-3s+HADrOdCdGOi5lnh5DMQEzgbsFsd4w57L/eLKKjMnN0CN4AIEP0DCP3F3N14xnxh3ruNc32A0Na9zYe1Z/AQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.10.tgz", + "integrity": "sha512-oP+zFUjYNaMNmjTwlFtWep85hvwUu19cZklB3QsBOcZSs6y7hmH4LNCJ7075bsqzYaNvZFXJlAVaQ2ApITDXtw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@hotwired/stimulus": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.2.1.tgz", + "integrity": "sha512-HGlzDcf9vv/EQrMJ5ZG6VWNs8Z/xMN+1o2OhV1gKiSG6CqZt5MCBB1gRg5ILiN3U0jEAxuDTNPRfBcnZBDmupQ==" + }, + "node_modules/@hotwired/turbo": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@hotwired/turbo/-/turbo-7.2.2.tgz", + "integrity": "sha512-YfTHwnur3tDFS/D5JYstU+09Z3bvCF6euqqXajHAks2lTSIDbLc8gMp8yLomD+cAC337h1wnv0oWduK6+6pUDw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha1-dhnC6yGyVIP20WdUi0z9WnSIw9U= sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha1-W9Jir5Tp0lvR5xsF3u1Eh2oiLos= sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha1-6Vc36LtnRt3t9pxVaVNJTxlv5po= sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rails/ujs": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@rails/ujs/-/ujs-6.1.5.tgz", + "integrity": "sha512-Ir4yd2fdDldWIghavPr874copVKf6OOoecKHZiFRlPtm38tFvhyxr+ywzNieXGwolF9JQe3D5GrM8ejkzUgh5Q==" + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@yaireo/tagify": { + "version": "3.25.0", + "resolved": "https://registry.yarnpkg.com/@yaireo/tagify/-/tagify-3.25.0.tgz", + "integrity": "sha1-Sgcoz2jHE64Wwl1SOTqRDi37WHU= sha512-aBRkGDkWdv6ZnRUCcA0mBIK5f3YegnxlFO9vj9zN5zSAUO90m2v5TsOPYN60sk6RNU/y556oc8jAmeSY7FUvaQ==", + "peerDependencies": { + "prop-types": "^15.7.2" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0= sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/apexcharts": { + "version": "3.36.3", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.36.3.tgz", + "integrity": "sha512-8/FXEs0ohXMff07Gv28XjhPwEJphIUdq2/wii/pcvi54Tw6z1mjrV8ydN8rlWi/ve8BAPBefJkLmRWv7UOBsLw==", + "dependencies": { + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.12", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.12.tgz", + "integrity": "sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001407", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/autoprefixer/node_modules/browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/autoprefixer/node_modules/caniuse-lite": { + "version": "1.0.30001414", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz", + "integrity": "sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/autoprefixer/node_modules/electron-to-chromium": { + "version": "1.4.270", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.270.tgz", + "integrity": "sha512-KNhIzgLiJmDDC444dj9vEOpZEgsV96ult9Iff98Vanumn+ShJHd5se8aX6KeVxdc0YQeqdrezBZv89rleDbvSg==", + "dev": true + }, + "node_modules/autoprefixer/node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha1-6D46fj8wCzTLnYf2FfoMvzV2kO4= sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0= sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz", + "integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc= sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserslist/node_modules/update-browserslist-db": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", + "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha1-sdTonmiBGcPJqQOtMKuy9qkZvjw= sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001414", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz", + "integrity": "sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ= sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clipboard-polyfill": { + "version": "3.0.3", + "resolved": "https://registry.yarnpkg.com/clipboard-polyfill/-/clipboard-polyfill-3.0.3.tgz", + "integrity": "sha1-FZ6gdo4g7cf/2kBL0TxUxz3k/0A= sha512-hts0o01ZkwjA1qHA5gFePzAj/780W7v+eyN3GdaCRyDnapzcPsKRV5aodv77gcr40NDIcyNjNmc+HvfKV+jD0g==" + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg= sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-convert/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha1-Sl7Hxk364iw6FBJNus3uhG2Ay8Q= sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-blank-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", + "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-has-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "dev": true, + "bin": { + "css-prefers-color-scheme": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssdb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.0.1.tgz", + "integrity": "sha512-pT3nzyGM78poCKLAEy2zWIVX2hikq6dIrjuZzLV98MumBg+xMTNYfHx7paUlfiRTgg91O/vR889CIf+qiv79Rw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.13.tgz", + "integrity": "sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ==", + "dev": true, + "dependencies": { + "cssnano-preset-default": "^5.2.12", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz", + "integrity": "sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==", + "dev": true, + "dependencies": { + "css-declaration-sorter": "^6.3.0", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.0", + "postcss-convert-values": "^5.1.2", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.6", + "postcss-merge-rules": "^5.1.2", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.3", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.0", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.0", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE= sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/dialog-polyfill": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/dialog-polyfill/-/dialog-polyfill-0.5.6.tgz", + "integrity": "sha512-ZbVDJI9uvxPAKze6z146rmfUZjBqNEwcnFTVamQzXH+svluiV7swmVIGr7miwADgfgt1G2JQIytypM9fbyhX4w==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.270", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.270.tgz", + "integrity": "sha512-KNhIzgLiJmDDC444dj9vEOpZEgsV96ult9Iff98Vanumn+ShJHd5se8aX6KeVxdc0YQeqdrezBZv89rleDbvSg==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha1-tKxAZIEH/c3PriQvQovqihTU8b8= sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.18.6", + "resolved": "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.6.tgz", + "integrity": "sha1-LETj6npiVQORZNJlWXd6bZeMtFY= sha512-kAeIT4cku5eNLNuUKhlmtuk1/TRZvQoYccn6TO0cSVdf1kzB0T7+dYuVK9MWM7l+/53W2Q8M7N2c6MQvhXFcUQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-string": "^1.0.7", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha1-5VzUyc3BiLzvsDs2bHNjI/xciYo= sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.10.tgz", + "integrity": "sha512-n7V3v29IuZy5qgxx25TKJrEm0FHghAlS6QweUcyIgh/U0zYmQcvogWROitrTyZId1mHSkuhhuyEXtI9OXioq7A==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.10", + "@esbuild/android-arm64": "0.17.10", + "@esbuild/android-x64": "0.17.10", + "@esbuild/darwin-arm64": "0.17.10", + "@esbuild/darwin-x64": "0.17.10", + "@esbuild/freebsd-arm64": "0.17.10", + "@esbuild/freebsd-x64": "0.17.10", + "@esbuild/linux-arm": "0.17.10", + "@esbuild/linux-arm64": "0.17.10", + "@esbuild/linux-ia32": "0.17.10", + "@esbuild/linux-loong64": "0.17.10", + "@esbuild/linux-mips64el": "0.17.10", + "@esbuild/linux-ppc64": "0.17.10", + "@esbuild/linux-riscv64": "0.17.10", + "@esbuild/linux-s390x": "0.17.10", + "@esbuild/linux-x64": "0.17.10", + "@esbuild/netbsd-x64": "0.17.10", + "@esbuild/openbsd-x64": "0.17.10", + "@esbuild/sunos-x64": "0.17.10", + "@esbuild/win32-arm64": "0.17.10", + "@esbuild/win32-ia32": "0.17.10", + "@esbuild/win32-x64": "0.17.10" + } + }, + "node_modules/esbuild-plugin-import-glob": { + "version": "0.1.1", + "resolved": "https://registry.yarnpkg.com/esbuild-plugin-import-glob/-/esbuild-plugin-import-glob-0.1.1.tgz", + "integrity": "sha1-arWtmSqme6mxqoDAdZ1WF5YM1cM= sha512-yAFH+9AoIcsQkODSx0KUPRv1FeJUN6Tef8vkPQMcuVkc2vXYneYKsHhOiFS/yIsg5bQ70HHtAlXVA1uTjgoJXg==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.5" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha1-/Wy3otfpqnp4RhEehaGW1rL3ZqE= sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha1-YWdg+Ip1Jr38WWt8q4wYk4w2uYw= sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA= sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0= sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha1-FfWfN2+FXERpY5SPDSTNNje0q8Y= sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha1-f9uByQAQH71WTdXxowr1qtweWNY= sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz", + "integrity": "sha1-Oxk+kjPwHULQs/eClLvutBj5SpA= sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha1-hpgyxYA0/mikCTwX3BXoNA2EAcQ= sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", + "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "dev": true, + "dependencies": { + "array-union": "^3.0.1", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.7", + "ignore": "^5.1.9", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha1-5BK40z9eAGWTy9PO5t+fLOu+gCo= sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz", + "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y= sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha1-ZP5qywIGc+O3jbA1pa9pqp0HsRM= sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha1-Fl0wcMADCXUqEjakeTMeOsVvFCM= sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha1-fhM4GKfTlHNPlB5zw9P5KR5liyU= sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha1-3/wL+aIcAiCQkPKqaUKeFBTa8/k= sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w= sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha1-c0fjB97uovqsKsYgXUvH00ln9Zw= sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha1-CBR6GHW8KzIAXUHM2Ckd/8ZpHfM= sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha1-XG3CACRt2TIa5LiFoRS7H3X2Nxk= sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha1-RzAdWN0CWUB4ZVR4U99tYf5HGUU= sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.7.0", + "resolved": "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.7.0.tgz", + "integrity": "sha1-PA730xtKz8V0+AxYQJ1WioNoSOM= sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha1-CEHVU25yTCVZe/bqYuG9OCmN8x8= sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.2", + "resolved": "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.2.tgz", + "integrity": "sha1-hZ/C5zHljJAvmfyrzLdafdB9Kdg= sha512-ZZTOjRcDjuAAAv2cTBQP/lL59ZTArx77+7UzHdWW/XB1mrfp7DEaVpKmZ0XIzx+M7AxfhKcqV+nMetUQmFifwg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha1-PedGwY3aIxkkGlNnWQjY92bxHCQ= sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss= sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha1-anqvg4x/BoalC0VT9+VKlklOifA= sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha1-7vVmPNWfpMCuM5UFMj32hUuxWVg= sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha1-DdEr8gBvJVu1j2lREO/3SR7rwP0= sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha1-ptrJO2NbBjymhyI23oiRClevE5w= sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk= sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lilconfig": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", + "integrity": "sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha1-Z5WRxWTDv/quhFTPCz3zcMPWkRw= sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha1-Q2iJL4hekHRVpv19xVwMnUBJkK4= sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha1-iW1Rnf6dsl/OlM63pQCRm/iB6/k= sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha1-ozeKdpbOfSI+iPybdkvX7xCJ42Y= sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha1-5m2xg4sgDB38IzIl0SyzZSDiNKg= sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha1-BEdiAqFe4OLiFAgIYb/xKlHZj7o= sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha1-nc6xRs7dQUig2eUauI00z1CZIrE= sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha1-HEfyct8nfzsdrwYWd9nILiMixg4= sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha1-DtVKNC7Os3s4/3brgxoOeIy2OUA= sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha1-+8EUtgykKzDZ2vWFjkvWi77bZzU= sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428= sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha1-8fBh3o9qS/AiiS4tEoI0+5gwKXI= sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha1-7wmsLMBTPfHzJQzPLE02aw0SEUo= sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-cli": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-9.1.0.tgz", + "integrity": "sha512-zvDN2ADbWfza42sAnj+O2uUWyL0eRL1V+6giM2vi4SqTR3gTYy8XzcpfwccayF2szcUif0HMmXiEaDv9iEhcpw==", + "dev": true, + "dependencies": { + "chokidar": "^3.3.0", + "dependency-graph": "^0.11.0", + "fs-extra": "^10.0.0", + "get-stdin": "^9.0.0", + "globby": "^12.0.0", + "picocolors": "^1.0.0", + "postcss-load-config": "^3.0.0", + "postcss-reporter": "^7.0.0", + "pretty-hrtime": "^1.0.3", + "read-cache": "^1.0.0", + "slash": "^4.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "postcss": "index.js" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", + "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz", + "integrity": "sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==", + "dev": true, + "dependencies": { + "browserslist": "^4.20.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-custom-properties": { + "version": "12.1.9", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.9.tgz", + "integrity": "sha512-/E7PRvK8DAVljBbeWrcEQJPG72jaImxF3vvCNFwv9cC8CzigVoNIpeyfnJzphnN3Fd8/auBf5wvkw6W9MfmTyg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-easy-import": { + "version": "3.0.0", + "resolved": "https://registry.yarnpkg.com/postcss-easy-import/-/postcss-easy-import-3.0.0.tgz", + "integrity": "sha1-jqr1rllWYIPQyumHNd/YA+OrGU0= sha512-cfNsear/v8xlkl9v5Wm8y4Do/puiDQTFF+WX2Fo++h7oKt1fKWVVW/5Ca8hslYDQWnjndrg813cA23Pt1jsYdg==", + "dev": true, + "dependencies": { + "globby": "^6.1.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.4", + "object-assign": "^4.0.1", + "pify": "^3.0.0", + "postcss": "^6.0.11", + "postcss-import": "^10.0.0", + "resolve": "^1.1.7" + } + }, + "node_modules/postcss-easy-import/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss-easy-import/node_modules/globby/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss-easy-import/node_modules/postcss": { + "version": "6.0.23", + "resolved": "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha1-YcgswyisYOZ3ZF+XkFTrmLwOMyQ= sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "dev": true, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-import": { + "version": "10.0.0", + "resolved": "https://registry.yarnpkg.com/postcss-import/-/postcss-import-10.0.0.tgz", + "integrity": "sha1-TIXJewmRNsxeoCQNwd/b/eTi674=", + "dev": true, + "dependencies": { + "object-assign": "^4.0.1", + "postcss": "^6.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "node_modules/postcss-import/node_modules/postcss": { + "version": "6.0.23", + "resolved": "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha1-YcgswyisYOZ3ZF+XkFTrmLwOMyQ= sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss-import/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha1-n/giVH4okyE88cMO+lGsX9G6goE= sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "dev": true, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz", + "integrity": "sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz", + "integrity": "sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dev": true, + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz", + "integrity": "sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz", + "integrity": "sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dev": true, + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz", + "integrity": "sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==", + "dev": true, + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "engines": { + "node": "^12 || ^14 || >=16" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dev": true, + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.2.tgz", + "integrity": "sha512-rSMUEaOCnovKnwc5LvBDHUDzpGP+nrUeWZGWt9M72fBvckCi45JmnJigUr4QG4zZeOHmOCNCZnd2LKDvP++ZuQ==", + "dev": true, + "dependencies": { + "@csstools/postcss-cascade-layers": "^1.1.0", + "@csstools/postcss-color-function": "^1.1.1", + "@csstools/postcss-font-format-keywords": "^1.0.1", + "@csstools/postcss-hwb-function": "^1.0.2", + "@csstools/postcss-ic-unit": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^2.0.7", + "@csstools/postcss-nested-calc": "^1.0.0", + "@csstools/postcss-normalize-display-values": "^1.0.1", + "@csstools/postcss-oklab-function": "^1.1.1", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.1", + "@csstools/postcss-text-decoration-shorthand": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.2", + "@csstools/postcss-unset-value": "^1.0.2", + "autoprefixer": "^10.4.11", + "browserslist": "^4.21.3", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^7.0.1", + "postcss-attribute-case-insensitive": "^5.0.2", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.4", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.1", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.9", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-double-position-gradients": "^3.1.2", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.5", + "postcss-image-set-function": "^4.0.7", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.1", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.2.0", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.4", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.5", + "postcss-pseudo-class-any-link": "^7.1.6", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env/node_modules/browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/postcss-preset-env/node_modules/caniuse-lite": { + "version": "1.0.30001414", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz", + "integrity": "sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/postcss-preset-env/node_modules/electron-to-chromium": { + "version": "1.4.270", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.270.tgz", + "integrity": "sha512-KNhIzgLiJmDDC444dj9vEOpZEgsV96ult9Iff98Vanumn+ShJHd5se8aX6KeVxdc0YQeqdrezBZv89rleDbvSg==", + "dev": true + }, + "node_modules/postcss-preset-env/node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz", + "integrity": "sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-reporter": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.0.5.tgz", + "integrity": "sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "thenby": "^1.3.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha1-SSkii7xyTfrEPg77BYyve2z7YkM= sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha1-YpoBP7P3B1XW8LeTXMHCxTeLGXU= sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha1-kNo4Kx4SbvwCFG6QhFqI2xKSXXY= sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha1-ZtE2jae9+SHrnZW9GpIp5/IaQ+4= sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha1-785cj9wQTudRslxY1CkAEfpeos8= sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM= sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha1-3s6BrJweZxPl99G28X1Gj6U9iak= sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha1-PyjOGnegA3JoPq3kpDMYNSeiFj0= sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha1-z3D1BILu/cmOPOCmgz5KU87rpnk= sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.10", + "resolved": "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", + "integrity": "sha1-DZvszN5wA9bGWNSH3UijLwvzAUs= sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", + "dev": true + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.2", + "resolved": "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz", + "integrity": "sha1-aFjKTzXFJo69XoYV4TJ9VfWe4xE= sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha1-51rpDClCxjUEaGwYsoe0oLGkX4A= sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha1-s2OZr0qymZtMnGSL16P7K7Jv7u0= sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylehacks": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", + "integrity": "sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8= sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "dependencies": { + "svg.js": "^2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "dependencies": { + "svg.js": ">=2.3.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "node_modules/svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "dependencies": { + "svg.js": "^2.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "dependencies": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js/node_modules/svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "dependencies": { + "svg.js": "^2.6.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/thenby": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", + "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ= sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha1-CF4hViXsMWJXTciFmr7nilmxRHE= sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", + "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha1-/JH2uce6FchX9MssXe/uw51PQQo= sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz", + "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo= sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha1-E3V7yJsgmwSf5dhkMOIc9AqJqOY= sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true, + "engines": { + "node": ">=12" + } + } + }, "dependencies": { "@arkweid/lefthook": { "version": "0.7.7", @@ -134,13 +4534,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", - "dev": true + "dev": true, + "requires": {} }, "@csstools/selector-specificity": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", - "dev": true + "dev": true, + "requires": {} }, "@esbuild/android-arm": { "version": "0.17.10", @@ -346,7 +4748,8 @@ "@yaireo/tagify": { "version": "3.25.0", "resolved": "https://registry.yarnpkg.com/@yaireo/tagify/-/tagify-3.25.0.tgz", - "integrity": "sha1-Sgcoz2jHE64Wwl1SOTqRDi37WHU= sha512-aBRkGDkWdv6ZnRUCcA0mBIK5f3YegnxlFO9vj9zN5zSAUO90m2v5TsOPYN60sk6RNU/y556oc8jAmeSY7FUvaQ==" + "integrity": "sha1-Sgcoz2jHE64Wwl1SOTqRDi37WHU= sha512-aBRkGDkWdv6ZnRUCcA0mBIK5f3YegnxlFO9vj9zN5zSAUO90m2v5TsOPYN60sk6RNU/y556oc8jAmeSY7FUvaQ==", + "requires": {} }, "ansi-regex": { "version": "5.0.1", @@ -646,7 +5049,8 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", - "dev": true + "dev": true, + "requires": {} }, "css-has-pseudo": { "version": "3.0.4", @@ -661,7 +5065,8 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", - "dev": true + "dev": true, + "requires": {} }, "css-select": { "version": "4.3.0", @@ -756,7 +5161,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true + "dev": true, + "requires": {} }, "csso": { "version": "4.2.0", @@ -1742,25 +6148,29 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-double-position-gradients": { "version": "3.1.2", @@ -1853,13 +6263,15 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "dev": true + "dev": true, + "requires": {} }, "postcss-gap-properties": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", - "dev": true + "dev": true, + "requires": {} }, "postcss-image-set-function": { "version": "4.0.7", @@ -1906,7 +6318,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-lab-function": { "version": "4.2.1", @@ -1932,13 +6345,15 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", - "dev": true + "dev": true, + "requires": {} }, "postcss-media-minmax": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-merge-longhand": { "version": "5.1.6", @@ -2016,7 +6431,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true + "dev": true, + "requires": {} }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -2121,7 +6537,8 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-place": { "version": "7.0.5", @@ -2253,7 +6670,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-reporter": { "version": "7.0.5", diff --git a/spec/fixtures/files/profile-+491212343434 b/spec/fixtures/files/profile-valid_uuid similarity index 100% rename from spec/fixtures/files/profile-+491212343434 rename to spec/fixtures/files/profile-valid_uuid diff --git a/spec/jobs/resubscribe_contributor_job_spec.rb b/spec/jobs/resubscribe_contributor_job_spec.rb index 001010b10..a5059403a 100644 --- a/spec/jobs/resubscribe_contributor_job_spec.rb +++ b/spec/jobs/resubscribe_contributor_job_spec.rb @@ -13,7 +13,7 @@ it_behaves_like 'a Contributor resubscribes', SignalAdapter::Outbound::Text do let(:contributor) do - create(:contributor, signal_phone_number: '+491234567', unsubscribed_at: 1.day.ago) + create(:contributor, signal_phone_number: '+491234567', signal_onboarding_completed_at: Time.current, unsubscribed_at: 1.day.ago) end end @@ -22,6 +22,7 @@ let(:contributor) do create(:contributor, signal_phone_number: '+491234567', + signal_onboarding_completed_at: Time.current, unsubscribed_at: 1.day.ago, deactivated_at: Time.current, deactivated_by_user: create(:user)) @@ -34,6 +35,7 @@ let(:contributor) do create(:contributor, signal_phone_number: '+491234567', + signal_onboarding_completed_at: Time.current, unsubscribed_at: 1.day.ago, deactivated_at: Time.current, deactivated_by_admin: true) diff --git a/spec/jobs/signal_adapter/attach_contributors_avatar_job_spec.rb b/spec/jobs/signal_adapter/attach_contributors_avatar_job_spec.rb index 75883fb92..036d15dca 100644 --- a/spec/jobs/signal_adapter/attach_contributors_avatar_job_spec.rb +++ b/spec/jobs/signal_adapter/attach_contributors_avatar_job_spec.rb @@ -4,16 +4,16 @@ RSpec.describe SignalAdapter::AttachContributorsAvatarJob do describe '#perform_later(contributor)' do - subject { -> { described_class.new.perform(contributor) } } - let(:contributor) { create(:contributor, signal_phone_number: '+491212343434') } + subject { -> { described_class.new.perform(contributor_id: contributor.id) } } + let(:contributor) { create(:contributor, signal_uuid: 'valid_uuid') } context 'with avatar on file system' do - let(:avatar) { file_fixture("profile-#{contributor.signal_phone_number}") } + let(:avatar) { file_fixture("profile-#{contributor.signal_uuid}") } before do - allow(File).to receive(:file?).with("/app/signal-cli-config/avatars/profile-#{contributor.signal_phone_number}").and_return(true) + allow(File).to receive(:file?).with("/app/signal-cli-config/avatars/profile-#{contributor.signal_uuid}").and_return(true) allow(File).to receive(:open).and_call_original allow(File).to receive(:open) - .with("/app/signal-cli-config/avatars/profile-#{contributor.signal_phone_number}") + .with("/app/signal-cli-config/avatars/profile-#{contributor.signal_uuid}") .and_return(avatar.open) end diff --git a/spec/jobs/unsubscribe_contributor_job_spec.rb b/spec/jobs/unsubscribe_contributor_job_spec.rb index 5e6548a46..81b9756fd 100644 --- a/spec/jobs/unsubscribe_contributor_job_spec.rb +++ b/spec/jobs/unsubscribe_contributor_job_spec.rb @@ -17,7 +17,7 @@ let(:adapter) { SignalAdapter::Outbound } it_behaves_like 'a Contributor unsubscribes', SignalAdapter::Outbound::Text do - let(:contributor) { create(:contributor, signal_phone_number: '+491234567') } + let(:contributor) { create(:contributor, signal_phone_number: '+491234567', signal_onboarding_completed_at: Time.current) } end end diff --git a/spec/requests/onboarding/signal_spec.rb b/spec/requests/onboarding/signal_spec.rb index ac3d911de..53b47222a 100644 --- a/spec/requests/onboarding/signal_spec.rb +++ b/spec/requests/onboarding/signal_spec.rb @@ -46,13 +46,14 @@ { first_name: 'Zora', last_name: 'Zimmermann', - signal_phone_number: signal_phone_number, + signal_onboarding_token: signal_onboarding_token, data_processing_consent: data_processing_consent, additional_consent: additional_consent } end let(:params) { { jwt: jwt, contributor: attrs, context: :contributor_signup } } + let(:signal_onboarding_token) { SecureRandom.alphanumeric(8).upcase } subject { -> { post onboarding_signal_path, params: params } } @@ -89,7 +90,7 @@ expect(contributor).to have_attributes( first_name: 'Zora', last_name: 'Zimmermann', - signal_phone_number: '+4915112345678', + signal_onboarding_token: signal_onboarding_token, data_processing_consent: data_processing_consent, additional_consent: additional_consent ) @@ -98,19 +99,9 @@ ) end - it 'sends welcome message' do - subject.call - contributor = Contributor.find_by(signal_phone_number: '+4915112345678') - - expect(SignalAdapter::Outbound::Text).to have_been_enqueued.with( - contributor_id: contributor.id, - text: welcome_message - ) - end - it 'redirects to success page' do subject.call - expect(response).to redirect_to onboarding_success_path(jwt: nil) + expect(response).to redirect_to onboarding_signal_link_path(jwt: nil, signal_onboarding_token: signal_onboarding_token) end it 'invalidates the jwt' do @@ -124,23 +115,6 @@ it_behaves_like 'an ActivityNotification', 'OnboardingCompleted' end - context 'given invalid phone number' do - let(:signal_phone_number) { 'invalid-phone-number' } - - it 'displays validation errors' do - subject.call - parsed = Capybara::Node::Simple.new(response.body) - fields = parsed.all('.Field') - signal_phone_number_field = fields.find { |f| f.has_text? 'Handynummer' } - expect(signal_phone_number_field).to have_text('ist keine gültige Nummer') - end - - it 'has 422 status code' do - subject.call - expect(response).to have_http_status(:unprocessable_entity) - end - end - context 'without data processing consent' do let(:data_processing_consent) { false } @@ -162,26 +136,6 @@ end end - describe 'if a contributor exists with the same phone number' do - let!(:contributor) { create(:contributor, **attrs.merge(json_web_token: create(:json_web_token, invalidated_jwt: :jwt))) } - - it 'redirects to success page so that an attacker cannot make a phone number listing' do - subject.call - expect(response).to redirect_to onboarding_success_path(jwt: nil) - end - - it 'invalidates the jwt' do - expect { subject.call }.to change(JsonWebToken, :count).by(1) - - json_web_token = JsonWebToken.where(invalidated_jwt: jwt) - expect(json_web_token).to exist - end - - it 'does not create new contributor' do - expect { subject.call }.not_to change(Contributor, :count) - end - end - context 'without additional consent' do let(:additional_consent) { false } From e341de517335d4b80a391b105b7bbb100d030573 Mon Sep 17 00:00:00 2001 From: Matthew Rider Date: Wed, 26 Jun 2024 12:54:51 +0200 Subject: [PATCH 06/10] Fix remaining broken tests --- app/adapters/signal_adapter/inbound.rb | 3 +- app/models/contributor.rb | 2 +- spec/factories/contributors.rb | 7 ++++ .../receive_polling_job_spec.rb | 39 +++++++++++++------ spec/requests/onboarding/signal_spec.rb | 1 - spec/system/admin/stats_spec.rb | 3 +- .../receive_multiple_signal_messages.yml | 2 + ..._signal_message_to_complete_onboarding.yml | 34 ++++++++++++++++ vcr_cassettes/receive_signal_messages.yml | 2 +- ...ages_containing_unsupported_attachment.yml | 1 + 10 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 vcr_cassettes/receive_signal_message_to_complete_onboarding.yml diff --git a/app/adapters/signal_adapter/inbound.rb b/app/adapters/signal_adapter/inbound.rb index 2ff9d7fe3..a32d32a23 100644 --- a/app/adapters/signal_adapter/inbound.rb +++ b/app/adapters/signal_adapter/inbound.rb @@ -79,7 +79,7 @@ def initialize_delivery_receipt(signal_message) def initialize_onboarding_sender(signal_message) signal_uuid = signal_message.dig(:envelope, :sourceUuid) signal_onboarding_token = signal_message.dig(:envelope, :dataMessage, :message) - return nil unless signal_onboarding_token && signal_uuid + return nil unless signal_onboarding_token sender = Contributor.find_by(signal_onboarding_token: signal_onboarding_token.strip) @@ -87,6 +87,7 @@ def initialize_onboarding_sender(signal_message) trigger(UNKNOWN_CONTRIBUTOR, signal_message.dig(:envelope, :source)) return nil end + return unless signal_uuid trigger(CONNECT, sender, signal_uuid) end diff --git a/app/models/contributor.rb b/app/models/contributor.rb index 649663643..4a811a81b 100644 --- a/app/models/contributor.rb +++ b/app/models/contributor.rb @@ -54,7 +54,7 @@ class Contributor < ApplicationRecord scope :with_email, -> { where.not(email: nil) } scope :with_threema, -> { where.not(threema_id: nil) } scope :with_telegram, -> { where.not(telegram_id: nil) } - scope :with_signal, -> { where.not(signal_phone_number: nil).and(where.not(signal_uuid: nil)) } + scope :with_signal, -> { where.not(signal_phone_number: nil, signal_uuid: nil) } scope :with_whats_app, -> { where.not(whats_app_phone_number: nil) } before_validation do diff --git a/spec/factories/contributors.rb b/spec/factories/contributors.rb index 755f81977..89281c56e 100644 --- a/spec/factories/contributors.rb +++ b/spec/factories/contributors.rb @@ -42,6 +42,13 @@ end end + trait :signal_contributor_uuid do + after(:build) do |contributor| + contributor.email = nil + contributor.signal_uuid = Faker::Internet.uuid + end + end + trait :whats_app_contributor do after(:build) do |contributor| contributor.email = nil diff --git a/spec/jobs/signal_adapter/receive_polling_job_spec.rb b/spec/jobs/signal_adapter/receive_polling_job_spec.rb index 9432c710a..084e72aed 100644 --- a/spec/jobs/signal_adapter/receive_polling_job_spec.rb +++ b/spec/jobs/signal_adapter/receive_polling_job_spec.rb @@ -73,23 +73,37 @@ end end - describe 'given a message from a contributor for the first time' do - let!(:contributor) { create(:contributor, signal_phone_number: '+4915112345789') } + describe 'given a message from a contributor for the first time', + vcr: { cassette_name: :receive_signal_message_to_complete_onboarding } do + let!(:contributor) { create(:contributor, signal_onboarding_token: signal_onboarding_token) } + let(:signal_uuid) { 'valid_uuid' } + let(:signal_onboarding_token) { 'CM1TOEC7' } + + it 'does not create a message' do + expect { subject.call }.not_to change(Message, :count) + end - it 'creates a message' do - expect { subject.call }.to change(Message, :count).by(1) + it 'enqueues a job to create the contact' do + expect { subject.call }.to have_enqueued_job(SignalAdapter::CreateContactJob).with(contributor_id: contributor.id) end it 'enqueues a job to attach contributors avatar' do - expect { subject.call }.to have_enqueued_job(SignalAdapter::AttachContributorsAvatarJob).with(contributor) + expect { subject.call }.to have_enqueued_job(SignalAdapter::AttachContributorsAvatarJob).with(contributor_id: contributor.id) end - it 'is expected to assign the correct contributor' do - subject.call - expect(Message.first.contributor.signal_phone_number).to eq('+4915112345789') + it 'is expected to complete the onboarding' do + expect { subject.call }.to change { contributor.reload.signal_uuid }.from(nil).to(signal_uuid) + .and change { + contributor.reload.signal_onboarding_completed_at + }.from(nil).to(kind_of(ActiveSupport::TimeWithZone)) end - it_behaves_like 'an ActivityNotification', 'MessageReceived' + it 'sends the welcome message' do + expect { subject.call }.to have_enqueued_job(SignalAdapter::Outbound::Text).with( + contributor_id: contributor.id, + text: [Setting.onboarding_success_heading, Setting.onboarding_success_text].join("\n") + ) + end end describe 'given multiple messages from known and unknown contributors', vcr: { cassette_name: :receive_multiple_signal_messages } do @@ -109,7 +123,7 @@ end describe 'given a message with attachments' do - let!(:contributor) { create(:contributor, signal_phone_number: '+4915112345678') } + let!(:contributor) { create(:contributor, signal_phone_number: '+4915112345678', signal_onboarding_completed_at: 2.weeks.ago) } before do allow(File).to receive(:open).and_call_original @@ -148,7 +162,7 @@ allow(Setting).to receive(:signal_cli_rest_api_endpoint).and_return('http://signal:8080') end - let!(:contributor) { create(:contributor, signal_phone_number: '+4915112345789') } + let!(:contributor) { create(:contributor, signal_phone_number: '+4915112345789', signal_onboarding_completed_at: 2.weeks.ago) } it { is_expected.to have_enqueued_job(UnsubscribeContributorJob).with(contributor.id, SignalAdapter::Outbound) } end @@ -158,7 +172,8 @@ allow(Setting).to receive(:signal_cli_rest_api_endpoint).and_return('http://signal:8080') end let!(:contributor) do - create(:contributor, signal_phone_number: '+4915112345789', unsubscribed_at: 1.week.ago) + create(:contributor, signal_phone_number: '+4915112345789', unsubscribed_at: 1.week.ago, + signal_onboarding_completed_at: 2.weeks.ago) end it { is_expected.to have_enqueued_job(ResubscribeContributorJob).with(contributor.id, SignalAdapter::Outbound) } diff --git a/spec/requests/onboarding/signal_spec.rb b/spec/requests/onboarding/signal_spec.rb index 53b47222a..7b3f6f3c5 100644 --- a/spec/requests/onboarding/signal_spec.rb +++ b/spec/requests/onboarding/signal_spec.rb @@ -146,7 +146,6 @@ expect(contributor).to have_attributes( first_name: 'Zora', last_name: 'Zimmermann', - signal_phone_number: '+4915112345678', data_processing_consent: data_processing_consent, additional_consent: additional_consent ) diff --git a/spec/system/admin/stats_spec.rb b/spec/system/admin/stats_spec.rb index b860c3c07..4825c0bc9 100644 --- a/spec/system/admin/stats_spec.rb +++ b/spec/system/admin/stats_spec.rb @@ -11,7 +11,8 @@ create_list(:contributor, 3) create_list(:contributor, 2, :threema_contributor, :skip_validations) create_list(:contributor, 4, :telegram_contributor) - create_list(:contributor, 6, :signal_contributor) + create_list(:contributor, 2, :signal_contributor) + create_list(:contributor, 4, :signal_contributor_uuid) create_list(:contributor, 12, :whats_app_contributor) end diff --git a/vcr_cassettes/receive_multiple_signal_messages.yml b/vcr_cassettes/receive_multiple_signal_messages.yml index e1456fa67..129c589e4 100644 --- a/vcr_cassettes/receive_multiple_signal_messages.yml +++ b/vcr_cassettes/receive_multiple_signal_messages.yml @@ -29,6 +29,7 @@ http_interactions: string: '[{ "envelope": { "source": "+4915100000000", + "sourceNumber": "+4915100000000", "sourceDevice": 1, "timestamp": 1630589332148, "dataMessage": { @@ -42,6 +43,7 @@ http_interactions: { "envelope": { "source": "+4915112345789", + "sourceNumber": "+4915112345789", "sourceDevice": 1, "timestamp": 1630589338630, "dataMessage": { diff --git a/vcr_cassettes/receive_signal_message_to_complete_onboarding.yml b/vcr_cassettes/receive_signal_message_to_complete_onboarding.yml new file mode 100644 index 000000000..cd331781d --- /dev/null +++ b/vcr_cassettes/receive_signal_message_to_complete_onboarding.yml @@ -0,0 +1,34 @@ +--- +http_interactions: +- request: + method: get + uri: http://localhost:8080/v1/receive/SIGNAL_SERVER_PHONE_NUMBER + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + Host: + - signal:8080 + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - text/plain; charset=utf-8 + Date: + - Wed, 26 Jun 2024 10:27:17 GMT + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: '[{"envelope":{"source":"valid_uuid","sourceNumber":null,"sourceUuid":"valid_uuid","sourceName":"Matthew + Rider","sourceDevice":1,"timestamp":1719396464361,"dataMessage":{"timestamp":1719396464361,"message":"CM1TOEC7","expiresInSeconds":0,"viewOnce":false}},"account":"+4915888627934"}]' + recorded_at: Wed, 26 Jun 2024 10:27:17 GMT +recorded_with: VCR 6.1.0 diff --git a/vcr_cassettes/receive_signal_messages.yml b/vcr_cassettes/receive_signal_messages.yml index 547231b85..c86650749 100644 --- a/vcr_cassettes/receive_signal_messages.yml +++ b/vcr_cassettes/receive_signal_messages.yml @@ -26,7 +26,7 @@ http_interactions: - '235' body: encoding: UTF-8 - string: '[{"envelope":{"source":"+4915112345789","sourceDevice":2,"timestamp":1626884435591,"dataMessage":{"timestamp":1626884435591,"message":"Hello + string: '[{"envelope":{"source":"+4915112345789","sourceNumber":"+4915112345789","sourceUuid":"valid_uuid","sourceDevice":2,"timestamp":1626884435591,"dataMessage":{"timestamp":1626884435591,"message":"Hello World!","expiresInSeconds":0,"viewOnce":false}}}]' recorded_at: Wed, 21 Jul 2021 16:20:49 GMT recorded_with: VCR 6.0.0 diff --git a/vcr_cassettes/receive_signal_messages_containing_unsupported_attachment.yml b/vcr_cassettes/receive_signal_messages_containing_unsupported_attachment.yml index d1d584a8a..9cde794b4 100644 --- a/vcr_cassettes/receive_signal_messages_containing_unsupported_attachment.yml +++ b/vcr_cassettes/receive_signal_messages_containing_unsupported_attachment.yml @@ -29,6 +29,7 @@ http_interactions: string: '[{ "envelope": { "source": "+4915112345678", + "sourceNumber": "+4915112345678", "sourceDevice": 2, "timestamp": 1630790514242, "dataMessage": { From 0a33d4e0233cdad2375abbc3d52ab11f676630a6 Mon Sep 17 00:00:00 2001 From: Matthew Rider Date: Wed, 26 Jun 2024 13:16:45 +0200 Subject: [PATCH 07/10] Update signal? method and add tests --- app/models/contributor.rb | 2 +- spec/models/contributor_spec.rb | 46 +++++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/app/models/contributor.rb b/app/models/contributor.rb index 4a811a81b..7dfd1b818 100644 --- a/app/models/contributor.rb +++ b/app/models/contributor.rb @@ -145,7 +145,7 @@ def threema? end def signal? - signal_phone_number.present? + signal_phone_number.present? || signal_uuid.present? end def whats_app? diff --git a/spec/models/contributor_spec.rb b/spec/models/contributor_spec.rb index e8f34b4f3..3995a90b7 100644 --- a/spec/models/contributor_spec.rb +++ b/spec/models/contributor_spec.rb @@ -255,6 +255,16 @@ let(:contributor) { create(:contributor, telegram_id: '123', email: 'contributor@example.org') } it { should contain_exactly(:telegram, :email) } end + + describe 'given a contributor with signal_phone_number' do + let(:contributor) { create(:contributor, :signal_contributor) } + it { should contain_exactly(:signal) } + end + + describe 'given a contributor with signal_uuid' do + let(:contributor) { create(:contributor, :signal_contributor_uuid) } + it { should contain_exactly(:signal) } + end end describe '#telegram?' do @@ -514,22 +524,24 @@ end describe 'given a SignalAdapter::Inbound' do + let(:signal_message) do + { + envelope: { + source: '+4912345789', + sourceNumber: '+4912345789', + sourceDevice: 2, + timestamp: 1_626_708_555_697, + dataMessage: { + timestamp: 1_626_708_555_697, + message: 'Hello 100eyes', + expiresInSeconds: 0, + viewOnce: false + } + } + } + end subject do lambda do - signal_message = - { - envelope: { - source: '+4912345789', - sourceDevice: 2, - timestamp: 1_626_708_555_697, - dataMessage: { - timestamp: 1_626_708_555_697, - message: 'Hello 100eyes', - expiresInSeconds: 0, - viewOnce: false - } - } - } message_inbound_adapter = SignalAdapter::Inbound.new message_inbound_adapter.consume(signal_message) do |message| message.contributor.reply(message_inbound_adapter) @@ -555,6 +567,12 @@ it_behaves_like 'an ActivityNotification', 'MessageReceived' end end + + describe 'givena a contributor with signal_uuid' do + let!(:contributor) { create(:contributor, signal_uuid: 'valid_uuid') } + + before { signal_message[:envelope][:sourceUuid] = 'valid_uuid' } + end end end From 3b7ba07ea0741b8c11ba513e0472d7b04933b718 Mon Sep 17 00:00:00 2001 From: Matthew Rider Date: Wed, 26 Jun 2024 13:20:00 +0200 Subject: [PATCH 08/10] Add method to return signal_attr --- app/adapters/signal_adapter/outbound/file.rb | 2 +- app/adapters/signal_adapter/outbound/text.rb | 2 +- .../contributor_signal_settings.rb | 2 +- app/models/contributor.rb | 4 ++++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/adapters/signal_adapter/outbound/file.rb b/app/adapters/signal_adapter/outbound/file.rb index fa5597b2f..3169c100d 100644 --- a/app/adapters/signal_adapter/outbound/file.rb +++ b/app/adapters/signal_adapter/outbound/file.rb @@ -28,7 +28,7 @@ def data end { number: Setting.signal_server_phone_number, - recipients: [message.recipient.signal_phone_number || message.recipient.signal_uuid], + recipients: [message.recipient.signal_attr], message: message.text, base64_attachments: base64_files } diff --git a/app/adapters/signal_adapter/outbound/text.rb b/app/adapters/signal_adapter/outbound/text.rb index d95f55b72..c9902b8a3 100644 --- a/app/adapters/signal_adapter/outbound/text.rb +++ b/app/adapters/signal_adapter/outbound/text.rb @@ -28,7 +28,7 @@ def perform(contributor_id:, text:) def data { number: Setting.signal_server_phone_number, - recipients: [recipient.signal_phone_number || recipient.signal_uuid], + recipients: [recipient.signal_attr], message: text } end diff --git a/app/components/contributor_signal_settings/contributor_signal_settings.rb b/app/components/contributor_signal_settings/contributor_signal_settings.rb index cdba4fd7e..b72f73bbc 100644 --- a/app/components/contributor_signal_settings/contributor_signal_settings.rb +++ b/app/components/contributor_signal_settings/contributor_signal_settings.rb @@ -16,7 +16,7 @@ def completed_onboarding_text if contributor.signal_phone_number.present? t('.complete.text.phone_number', name: contributor.name, - phone_number: contributor.signal_phone_number&.phony_formatted) + phone_number: contributor.signal_phone_number.phony_formatted) else t('.complete.text.uuid', diff --git a/app/models/contributor.rb b/app/models/contributor.rb index 7dfd1b818..2dbd22ca6 100644 --- a/app/models/contributor.rb +++ b/app/models/contributor.rb @@ -152,6 +152,10 @@ def whats_app? whats_app_phone_number.present? end + def signal_attr + signal_phone_number || signal_uuid + end + def avatar? avatar.attached? end From 3bad22a0967e9a0744d23e6eb285d9cd6164e3b4 Mon Sep 17 00:00:00 2001 From: Matthew Rider Date: Wed, 26 Jun 2024 13:21:48 +0200 Subject: [PATCH 09/10] Improve semantics for param name --- app/adapters/signal_adapter/unknown_contributor_error.rb | 4 ++-- app/jobs/signal_adapter/receive_polling_job.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/adapters/signal_adapter/unknown_contributor_error.rb b/app/adapters/signal_adapter/unknown_contributor_error.rb index c61df1793..4da9a638d 100644 --- a/app/adapters/signal_adapter/unknown_contributor_error.rb +++ b/app/adapters/signal_adapter/unknown_contributor_error.rb @@ -2,8 +2,8 @@ module SignalAdapter class UnknownContributorError < StandardError - def initialize(signal_uuid:) - super("Received a message on signal from an unknown sender: #{signal_uuid}") + def initialize(signal_attr:) + super("Received a message on signal from an unknown sender: #{signal_attr}") end end end diff --git a/app/jobs/signal_adapter/receive_polling_job.rb b/app/jobs/signal_adapter/receive_polling_job.rb index 586232282..6e69a3363 100644 --- a/app/jobs/signal_adapter/receive_polling_job.rb +++ b/app/jobs/signal_adapter/receive_polling_job.rb @@ -43,8 +43,8 @@ def handle_callbacks handle_connect(contributor, signal_uuid) end - adapter.on(SignalAdapter::UNKNOWN_CONTRIBUTOR) do |signal_uuid| - exception = SignalAdapter::UnknownContributorError.new(signal_uuid: signal_uuid) + adapter.on(SignalAdapter::UNKNOWN_CONTRIBUTOR) do |signal_attr| + exception = SignalAdapter::UnknownContributorError.new(signal_attr: signal_attr) ErrorNotifier.report(exception) end From 50c40dc6f3ce8fb6772dea0fe93a37e2738c7eae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 26 Jun 2024 17:31:15 +0200 Subject: [PATCH 10/10] refactor(translations): clearer instructions ..in German --- config/locales/de.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index c95785f0c..1d84bdded 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -87,7 +87,7 @@ de: text: Verbinden Sie Ihr Signal-Konto in der Signal-App, um die Anmeldung abzuschließen. steps: - "Finden Sie uns auf Signal. Suchen Sie dazu nach %{signal_server_phone_number}." - - "Wenn Sie unser Signal-Konto gefunden haben, geben Sie diesen Code ein, sobald sie danach gefragt werden: %{token}" + - "Wenn Sie unser Signal-Konto gefunden haben, geben Sie diesen Code ein: %{token}" onboarding_signal_form: heading: Mit Signal teilnehmen text: Geben Sie bitte unten die Handynummer ein, mit der Sie bei Signal registriert sind.