aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.codeclimate.yml2
-rw-r--r--.github/autolabeler.yml4
-rw-r--r--.rubocop.yml14
-rw-r--r--.travis.yml80
-rw-r--r--Gemfile13
-rw-r--r--Gemfile.lock163
-rw-r--r--MIT-LICENSE2
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.md6
-rw-r--r--RELEASING_RAILS.md15
-rw-r--r--actioncable/CHANGELOG.md25
-rw-r--r--actioncable/MIT-LICENSE2
-rw-r--r--actioncable/README.md545
-rw-r--r--actioncable/app/assets/javascripts/action_cable.js25
-rw-r--r--actioncable/app/javascript/action_cable/adapters.js4
-rw-r--r--actioncable/app/javascript/action_cable/connection.js8
-rw-r--r--actioncable/app/javascript/action_cable/connection_monitor.js4
-rw-r--r--actioncable/app/javascript/action_cable/index.js10
-rw-r--r--actioncable/lib/action_cable.rb2
-rw-r--r--actioncable/lib/action_cable/channel/broadcasting.rb26
-rw-r--r--actioncable/lib/action_cable/channel/streams.rb2
-rw-r--r--actioncable/lib/action_cable/channel/test_case.rb59
-rw-r--r--actioncable/lib/action_cable/connection.rb1
-rw-r--r--actioncable/lib/action_cable/connection/test_case.rb234
-rw-r--r--actioncable/lib/action_cable/gem_version.rb2
-rw-r--r--actioncable/lib/action_cable/test_helper.rb2
-rw-r--r--actioncable/lib/rails/generators/channel/USAGE3
-rw-r--r--actioncable/lib/rails/generators/channel/channel_generator.rb2
-rw-r--r--actioncable/lib/rails/generators/channel/templates/javascript/consumer.js.tt2
-rw-r--r--actioncable/lib/rails/generators/test_unit/channel_generator.rb20
-rw-r--r--actioncable/lib/rails/generators/test_unit/templates/channel_test.rb.tt8
-rw-r--r--actioncable/package.json4
-rw-r--r--actioncable/test/channel/broadcasting_test.rb15
-rw-r--r--actioncable/test/channel/test_case_test.rb32
-rw-r--r--actioncable/test/client_test.rb2
-rw-r--r--actioncable/test/connection/test_case_test.rb197
-rw-r--r--actioncable/test/javascript/src/unit/action_cable_test.js8
-rw-r--r--actioncable/test/test_helper_test.rb2
-rw-r--r--actionmailbox/.gitignore5
-rw-r--r--actionmailbox/CHANGELOG.md5
-rw-r--r--actionmailbox/MIT-LICENSE21
-rw-r--r--actionmailbox/README.md13
-rw-r--r--actionmailbox/Rakefile15
-rw-r--r--actionmailbox/actionmailbox.gemspec38
-rw-r--r--actionmailbox/app/controllers/action_mailbox/base_controller.rb38
-rw-r--r--actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb54
-rw-r--r--actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb103
-rw-r--r--actionmailbox/app/controllers/action_mailbox/ingresses/mandrill/inbound_emails_controller.rb82
-rw-r--r--actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb62
-rw-r--r--actionmailbox/app/controllers/action_mailbox/ingresses/relay/inbound_emails_controller.rb65
-rw-r--r--actionmailbox/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb54
-rw-r--r--actionmailbox/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb34
-rw-r--r--actionmailbox/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb19
-rw-r--r--actionmailbox/app/controllers/rails/conductor/base_controller.rb14
-rw-r--r--actionmailbox/app/jobs/action_mailbox/incineration_job.rb22
-rw-r--r--actionmailbox/app/jobs/action_mailbox/routing_job.rb13
-rw-r--r--actionmailbox/app/models/action_mailbox/inbound_email.rb49
-rw-r--r--actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb20
-rw-r--r--actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb26
-rw-r--r--actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb38
-rw-r--r--actionmailbox/app/models/action_mailbox/inbound_email/routable.rb24
-rw-r--r--actionmailbox/app/views/layouts/rails/conductor.html.erb8
-rw-r--r--actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/index.html.erb15
-rw-r--r--actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb47
-rw-r--r--actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/show.html.erb15
-rwxr-xr-xactionmailbox/bin/test5
-rw-r--r--actionmailbox/config/routes.rb20
-rw-r--r--actionmailbox/db/migrate/20180917164000_create_action_mailbox_tables.rb17
-rw-r--r--actionmailbox/lib/action_mailbox.rb16
-rw-r--r--actionmailbox/lib/action_mailbox/base.rb118
-rw-r--r--actionmailbox/lib/action_mailbox/callbacks.rb34
-rw-r--r--actionmailbox/lib/action_mailbox/engine.rb42
-rw-r--r--actionmailbox/lib/action_mailbox/gem_version.rb17
-rw-r--r--actionmailbox/lib/action_mailbox/mail_ext.rb6
-rw-r--r--actionmailbox/lib/action_mailbox/mail_ext/address_equality.rb9
-rw-r--r--actionmailbox/lib/action_mailbox/mail_ext/address_wrapping.rb9
-rw-r--r--actionmailbox/lib/action_mailbox/mail_ext/addresses.rb29
-rw-r--r--actionmailbox/lib/action_mailbox/mail_ext/from_source.rb7
-rw-r--r--actionmailbox/lib/action_mailbox/mail_ext/recipients.rb9
-rw-r--r--actionmailbox/lib/action_mailbox/relayer.rb75
-rw-r--r--actionmailbox/lib/action_mailbox/router.rb42
-rw-r--r--actionmailbox/lib/action_mailbox/router/route.rb42
-rw-r--r--actionmailbox/lib/action_mailbox/routing.rb22
-rw-r--r--actionmailbox/lib/action_mailbox/test_case.rb12
-rw-r--r--actionmailbox/lib/action_mailbox/test_helper.rb44
-rw-r--r--actionmailbox/lib/action_mailbox/version.rb10
-rw-r--r--actionmailbox/lib/rails/generators/installer.rb10
-rw-r--r--actionmailbox/lib/rails/generators/mailbox/USAGE12
-rw-r--r--actionmailbox/lib/rails/generators/mailbox/mailbox_generator.rb32
-rw-r--r--actionmailbox/lib/rails/generators/mailbox/templates/application_mailbox.rb.tt3
-rw-r--r--actionmailbox/lib/rails/generators/mailbox/templates/mailbox.rb.tt4
-rw-r--r--actionmailbox/lib/rails/generators/test_unit/mailbox_generator.rb20
-rw-r--r--actionmailbox/lib/rails/generators/test_unit/templates/mailbox_test.rb.tt13
-rw-r--r--actionmailbox/lib/tasks/ingress.rake72
-rw-r--r--actionmailbox/lib/tasks/install.rake20
-rw-r--r--actionmailbox/test/controllers/ingresses/amazon/inbound_emails_controller_test.rb22
-rw-r--r--actionmailbox/test/controllers/ingresses/mailgun/inbound_emails_controller_test.rb91
-rw-r--r--actionmailbox/test/controllers/ingresses/mandrill/inbound_emails_controller_test.rb60
-rw-r--r--actionmailbox/test/controllers/ingresses/postmark/inbound_emails_controller_test.rb55
-rw-r--r--actionmailbox/test/controllers/ingresses/relay/inbound_emails_controller_test.rb56
-rw-r--r--actionmailbox/test/controllers/ingresses/sendgrid/inbound_emails_controller_test.rb46
-rw-r--r--actionmailbox/test/controllers/rails/action_mailbox/inbound_emails_controller_test.rb57
-rw-r--r--actionmailbox/test/dummy/.babelrc18
-rw-r--r--actionmailbox/test/dummy/.postcssrc.yml3
-rw-r--r--actionmailbox/test/dummy/Rakefile6
-rw-r--r--actionmailbox/test/dummy/app/assets/config/manifest.js3
-rw-r--r--actionmailbox/test/dummy/app/assets/images/.keep0
-rw-r--r--actionmailbox/test/dummy/app/assets/stylesheets/application.css15
-rw-r--r--actionmailbox/test/dummy/app/assets/stylesheets/scaffold.css80
-rw-r--r--actionmailbox/test/dummy/app/channels/application_cable/channel.rb4
-rw-r--r--actionmailbox/test/dummy/app/channels/application_cable/connection.rb4
-rw-r--r--actionmailbox/test/dummy/app/controllers/application_controller.rb2
-rw-r--r--actionmailbox/test/dummy/app/controllers/concerns/.keep0
-rw-r--r--actionmailbox/test/dummy/app/helpers/application_helper.rb2
-rw-r--r--actionmailbox/test/dummy/app/javascript/packs/application.js0
-rw-r--r--actionmailbox/test/dummy/app/jobs/application_job.rb2
-rw-r--r--actionmailbox/test/dummy/app/mailboxes/application_mailbox.rb5
-rw-r--r--actionmailbox/test/dummy/app/mailboxes/messages_mailbox.rb4
-rw-r--r--actionmailbox/test/dummy/app/mailers/application_mailer.rb4
-rw-r--r--actionmailbox/test/dummy/app/models/application_record.rb3
-rw-r--r--actionmailbox/test/dummy/app/models/concerns/.keep0
-rw-r--r--actionmailbox/test/dummy/app/views/layouts/application.html.erb14
-rw-r--r--actionmailbox/test/dummy/app/views/layouts/mailer.html.erb13
-rw-r--r--actionmailbox/test/dummy/app/views/layouts/mailer.text.erb1
-rwxr-xr-xactionmailbox/test/dummy/bin/bundle3
-rwxr-xr-xactionmailbox/test/dummy/bin/rails4
-rwxr-xr-xactionmailbox/test/dummy/bin/rake4
-rwxr-xr-xactionmailbox/test/dummy/bin/setup36
-rwxr-xr-xactionmailbox/test/dummy/bin/update31
-rwxr-xr-xactionmailbox/test/dummy/bin/yarn11
-rw-r--r--actionmailbox/test/dummy/config.ru5
-rw-r--r--actionmailbox/test/dummy/config/application.rb17
-rw-r--r--actionmailbox/test/dummy/config/boot.rb5
-rw-r--r--actionmailbox/test/dummy/config/cable.yml10
-rw-r--r--actionmailbox/test/dummy/config/database.yml25
-rw-r--r--actionmailbox/test/dummy/config/environment.rb5
-rw-r--r--actionmailbox/test/dummy/config/environments/development.rb63
-rw-r--r--actionmailbox/test/dummy/config/environments/production.rb99
-rw-r--r--actionmailbox/test/dummy/config/environments/test.rb46
-rw-r--r--actionmailbox/test/dummy/config/initializers/application_controller_renderer.rb8
-rw-r--r--actionmailbox/test/dummy/config/initializers/assets.rb14
-rw-r--r--actionmailbox/test/dummy/config/initializers/backtrace_silencers.rb7
-rw-r--r--actionmailbox/test/dummy/config/initializers/content_security_policy.rb22
-rw-r--r--actionmailbox/test/dummy/config/initializers/cookies_serializer.rb5
-rw-r--r--actionmailbox/test/dummy/config/initializers/filter_parameter_logging.rb4
-rw-r--r--actionmailbox/test/dummy/config/initializers/inflections.rb16
-rw-r--r--actionmailbox/test/dummy/config/initializers/mime_types.rb4
-rw-r--r--actionmailbox/test/dummy/config/initializers/wrap_parameters.rb14
-rw-r--r--actionmailbox/test/dummy/config/locales/en.yml33
-rw-r--r--actionmailbox/test/dummy/config/puma.rb34
-rw-r--r--actionmailbox/test/dummy/config/routes.rb4
-rw-r--r--actionmailbox/test/dummy/config/spring.rb6
-rw-r--r--actionmailbox/test/dummy/config/storage.yml35
-rw-r--r--actionmailbox/test/dummy/config/webpack/development.js3
-rw-r--r--actionmailbox/test/dummy/config/webpack/environment.js3
-rw-r--r--actionmailbox/test/dummy/config/webpack/production.js3
-rw-r--r--actionmailbox/test/dummy/config/webpack/test.js3
-rw-r--r--actionmailbox/test/dummy/config/webpacker.yml65
-rw-r--r--actionmailbox/test/dummy/db/migrate/20180208205311_create_action_mailbox_tables.rb17
-rw-r--r--actionmailbox/test/dummy/db/migrate/20180212164506_create_active_storage_tables.active_storage.rb27
-rw-r--r--actionmailbox/test/dummy/db/schema.rb45
-rw-r--r--actionmailbox/test/dummy/lib/assets/.keep0
-rw-r--r--actionmailbox/test/dummy/log/.keep0
-rw-r--r--actionmailbox/test/dummy/package.json11
-rw-r--r--actionmailbox/test/dummy/public/404.html67
-rw-r--r--actionmailbox/test/dummy/public/422.html67
-rw-r--r--actionmailbox/test/dummy/public/500.html66
-rw-r--r--actionmailbox/test/dummy/public/apple-touch-icon-precomposed.png0
-rw-r--r--actionmailbox/test/dummy/public/apple-touch-icon.png0
-rw-r--r--actionmailbox/test/dummy/public/favicon.ico0
-rw-r--r--actionmailbox/test/dummy/storage/.keep0
-rw-r--r--actionmailbox/test/dummy/yarn.lock6071
-rw-r--r--actionmailbox/test/fixtures/files/avatar1.jpegbin0 -> 20535 bytes
-rw-r--r--actionmailbox/test/fixtures/files/avatar2.jpegbin0 -> 12686 bytes
-rw-r--r--actionmailbox/test/fixtures/files/welcome.eml631
-rw-r--r--actionmailbox/test/generators/mailbox_generator_test.rb85
-rw-r--r--actionmailbox/test/jobs/incineration_job_test.rb17
-rw-r--r--actionmailbox/test/test_helper.rb56
-rw-r--r--actionmailbox/test/unit/inbound_email/incineration_test.rb47
-rw-r--r--actionmailbox/test/unit/inbound_email/message_id_test.rb15
-rw-r--r--actionmailbox/test/unit/inbound_email_test.rb37
-rw-r--r--actionmailbox/test/unit/mail_ext/address_equality_test.rb11
-rw-r--r--actionmailbox/test/unit/mail_ext/address_wrapping_test.rb13
-rw-r--r--actionmailbox/test/unit/mail_ext/recipients_test.rb35
-rw-r--r--actionmailbox/test/unit/mailbox/bouncing_test.rb31
-rw-r--r--actionmailbox/test/unit/mailbox/callbacks_test.rb77
-rw-r--r--actionmailbox/test/unit/mailbox/routing_test.rb31
-rw-r--r--actionmailbox/test/unit/mailbox/state_test.rb51
-rw-r--r--actionmailbox/test/unit/relayer_test.rb100
-rw-r--r--actionmailbox/test/unit/router_test.rb139
-rw-r--r--actionmailer/CHANGELOG.md6
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/README.rdoc36
-rw-r--r--actionmailer/actionmailer.gemspec4
-rw-r--r--actionmailer/lib/action_mailer.rb2
-rw-r--r--actionmailer/lib/action_mailer/base.rb7
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb2
-rw-r--r--actionmailer/lib/action_mailer/parameterized.rb10
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb4
-rw-r--r--actionmailer/test/abstract_unit.rb2
-rw-r--r--actionmailer/test/legacy_delivery_job_test.rb7
-rw-r--r--actionmailer/test/log_subscriber_test.rb4
-rw-r--r--actionpack/CHANGELOG.md21
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/lib/abstract_controller/caching/fragments.rb19
-rw-r--r--actionpack/lib/action_controller/metal/basic_implicit_render.rb2
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb8
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb23
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb4
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb8
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb33
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb10
-rw-r--r--actionpack/lib/action_controller/test_case.rb5
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb35
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb4
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb1
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb27
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_pack/gem_version.rb2
-rw-r--r--actionpack/test/controller/base_test.rb2
-rw-r--r--actionpack/test/controller/caching_test.rb8
-rw-r--r--actionpack/test/controller/filters_test.rb2
-rw-r--r--actionpack/test/controller/flash_test.rb8
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb5
-rw-r--r--actionpack/test/controller/new_base/render_context_test.rb55
-rw-r--r--actionpack/test/controller/render_test.rb10
-rw-r--r--actionpack/test/controller/url_for_test.rb8
-rw-r--r--actionpack/test/controller/webservice_test.rb6
-rw-r--r--actionpack/test/dispatch/cookies_test.rb185
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb44
-rw-r--r--actionpack/test/dispatch/live_response_test.rb2
-rw-r--r--actionpack/test/dispatch/request_test.rb1
-rw-r--r--actionpack/test/dispatch/routing_test.rb10
-rw-r--r--actionpack/test/dispatch/test_response_test.rb7
-rw-r--r--actionpack/test/journey/router_test.rb6
-rw-r--r--actiontext/.gitignore5
-rw-r--r--actiontext/CHANGELOG.md5
-rw-r--r--actiontext/MIT-LICENSE21
-rw-r--r--actiontext/README.md9
-rw-r--r--actiontext/Rakefile15
-rw-r--r--actiontext/actiontext.gemspec37
-rw-r--r--actiontext/app/helpers/action_text/content_helper.rb30
-rw-r--r--actiontext/app/helpers/action_text/tag_helper.rb75
-rw-r--r--actiontext/app/javascript/actiontext/attachment_upload.js45
-rw-r--r--actiontext/app/javascript/actiontext/index.js10
-rw-r--r--actiontext/app/models/action_text/rich_text.rb29
-rw-r--r--actiontext/app/views/action_text/attachables/_missing_attachable.html.erb1
-rw-r--r--actiontext/app/views/action_text/attachables/_remote_image.html.erb8
-rw-r--r--actiontext/app/views/action_text/attachment_galleries/_attachment_gallery.html.erb3
-rw-r--r--actiontext/app/views/action_text/content/_layout.html.erb3
-rw-r--r--actiontext/app/views/active_storage/blobs/_blob.html.erb14
-rwxr-xr-xactiontext/bin/test5
-rwxr-xr-xactiontext/bin/webpack29
-rwxr-xr-xactiontext/bin/webpack-dev-server29
-rw-r--r--actiontext/db/migrate/201805281641_create_action_text_tables.rb14
-rw-r--r--actiontext/lib/action_text.rb37
-rw-r--r--actiontext/lib/action_text/attachable.rb82
-rw-r--r--actiontext/lib/action_text/attachables/content_attachment.rb38
-rw-r--r--actiontext/lib/action_text/attachables/missing_attachable.rb13
-rw-r--r--actiontext/lib/action_text/attachables/remote_image.rb46
-rw-r--r--actiontext/lib/action_text/attachment.rb103
-rw-r--r--actiontext/lib/action_text/attachment_gallery.rb65
-rw-r--r--actiontext/lib/action_text/attachments/caching.rb16
-rw-r--r--actiontext/lib/action_text/attachments/minification.rb17
-rw-r--r--actiontext/lib/action_text/attachments/trix_conversion.rb34
-rw-r--r--actiontext/lib/action_text/attribute.rb48
-rw-r--r--actiontext/lib/action_text/content.rb132
-rw-r--r--actiontext/lib/action_text/engine.rb50
-rw-r--r--actiontext/lib/action_text/fragment.rb57
-rw-r--r--actiontext/lib/action_text/gem_version.rb17
-rw-r--r--actiontext/lib/action_text/html_conversion.rb24
-rw-r--r--actiontext/lib/action_text/plain_text_conversion.rb81
-rw-r--r--actiontext/lib/action_text/serialization.rb34
-rw-r--r--actiontext/lib/action_text/trix_attachment.rb92
-rw-r--r--actiontext/lib/action_text/version.rb10
-rw-r--r--actiontext/lib/tasks/actiontext.rake20
-rw-r--r--actiontext/lib/templates/actiontext.scss36
-rw-r--r--actiontext/lib/templates/fixtures.yml4
-rw-r--r--actiontext/lib/templates/installer.rb32
-rw-r--r--actiontext/package.json29
-rw-r--r--actiontext/test/dummy/.babelrc18
-rw-r--r--actiontext/test/dummy/.postcssrc.yml3
-rw-r--r--actiontext/test/dummy/Rakefile6
-rw-r--r--actiontext/test/dummy/app/assets/config/manifest.js3
-rw-r--r--actiontext/test/dummy/app/assets/images/.keep0
-rw-r--r--actiontext/test/dummy/app/assets/stylesheets/application.css16
-rw-r--r--actiontext/test/dummy/app/assets/stylesheets/messages.css4
-rw-r--r--actiontext/test/dummy/app/assets/stylesheets/scaffold.css80
-rw-r--r--actiontext/test/dummy/app/channels/application_cable/channel.rb4
-rw-r--r--actiontext/test/dummy/app/channels/application_cable/connection.rb4
-rw-r--r--actiontext/test/dummy/app/controllers/application_controller.rb2
-rw-r--r--actiontext/test/dummy/app/controllers/concerns/.keep0
-rw-r--r--actiontext/test/dummy/app/controllers/messages_controller.rb58
-rw-r--r--actiontext/test/dummy/app/helpers/application_helper.rb2
-rw-r--r--actiontext/test/dummy/app/helpers/messages_helper.rb2
-rw-r--r--actiontext/test/dummy/app/javascript/packs/application.js1
-rw-r--r--actiontext/test/dummy/app/jobs/application_job.rb2
-rw-r--r--actiontext/test/dummy/app/mailers/application_mailer.rb4
-rw-r--r--actiontext/test/dummy/app/models/application_record.rb3
-rw-r--r--actiontext/test/dummy/app/models/concerns/.keep0
-rw-r--r--actiontext/test/dummy/app/models/message.rb4
-rw-r--r--actiontext/test/dummy/app/models/person.rb7
-rw-r--r--actiontext/test/dummy/app/views/layouts/application.html.erb14
-rw-r--r--actiontext/test/dummy/app/views/layouts/mailer.html.erb13
-rw-r--r--actiontext/test/dummy/app/views/layouts/mailer.text.erb1
-rw-r--r--actiontext/test/dummy/app/views/messages/_form.html.erb27
-rw-r--r--actiontext/test/dummy/app/views/messages/edit.html.erb6
-rw-r--r--actiontext/test/dummy/app/views/messages/index.html.erb29
-rw-r--r--actiontext/test/dummy/app/views/messages/new.html.erb5
-rw-r--r--actiontext/test/dummy/app/views/messages/show.html.erb13
-rw-r--r--actiontext/test/dummy/app/views/people/_trix_content_attachment.html.erb3
-rwxr-xr-xactiontext/test/dummy/bin/bundle3
-rwxr-xr-xactiontext/test/dummy/bin/rails4
-rwxr-xr-xactiontext/test/dummy/bin/rake4
-rwxr-xr-xactiontext/test/dummy/bin/setup36
-rwxr-xr-xactiontext/test/dummy/bin/update31
-rwxr-xr-xactiontext/test/dummy/bin/yarn11
-rw-r--r--actiontext/test/dummy/config.ru5
-rw-r--r--actiontext/test/dummy/config/application.rb19
-rw-r--r--actiontext/test/dummy/config/boot.rb5
-rw-r--r--actiontext/test/dummy/config/cable.yml10
-rw-r--r--actiontext/test/dummy/config/database.yml25
-rw-r--r--actiontext/test/dummy/config/environment.rb5
-rw-r--r--actiontext/test/dummy/config/environments/development.rb63
-rw-r--r--actiontext/test/dummy/config/environments/production.rb96
-rw-r--r--actiontext/test/dummy/config/environments/test.rb46
-rw-r--r--actiontext/test/dummy/config/initializers/application_controller_renderer.rb8
-rw-r--r--actiontext/test/dummy/config/initializers/assets.rb14
-rw-r--r--actiontext/test/dummy/config/initializers/backtrace_silencers.rb7
-rw-r--r--actiontext/test/dummy/config/initializers/content_security_policy.rb22
-rw-r--r--actiontext/test/dummy/config/initializers/cookies_serializer.rb5
-rw-r--r--actiontext/test/dummy/config/initializers/filter_parameter_logging.rb4
-rw-r--r--actiontext/test/dummy/config/initializers/inflections.rb16
-rw-r--r--actiontext/test/dummy/config/initializers/mime_types.rb4
-rw-r--r--actiontext/test/dummy/config/initializers/wrap_parameters.rb14
-rw-r--r--actiontext/test/dummy/config/locales/en.yml33
-rw-r--r--actiontext/test/dummy/config/puma.rb34
-rw-r--r--actiontext/test/dummy/config/routes.rb4
-rw-r--r--actiontext/test/dummy/config/spring.rb6
-rw-r--r--actiontext/test/dummy/config/storage.yml35
-rw-r--r--actiontext/test/dummy/config/webpack/development.js3
-rw-r--r--actiontext/test/dummy/config/webpack/environment.js3
-rw-r--r--actiontext/test/dummy/config/webpack/production.js3
-rw-r--r--actiontext/test/dummy/config/webpack/test.js3
-rw-r--r--actiontext/test/dummy/config/webpacker.yml65
-rw-r--r--actiontext/test/dummy/db/migrate/20180208205311_create_messages.rb8
-rw-r--r--actiontext/test/dummy/db/migrate/20180212164506_create_active_storage_tables.rb27
-rw-r--r--actiontext/test/dummy/db/migrate/2018052816_create_action_text_tables.rb14
-rw-r--r--actiontext/test/dummy/db/migrate/20181003185713_create_people.rb9
-rw-r--r--actiontext/test/dummy/db/schema.rb58
-rw-r--r--actiontext/test/dummy/lib/assets/.keep0
-rw-r--r--actiontext/test/dummy/log/.keep0
-rw-r--r--actiontext/test/dummy/package.json11
-rw-r--r--actiontext/test/dummy/public/404.html67
-rw-r--r--actiontext/test/dummy/public/422.html67
-rw-r--r--actiontext/test/dummy/public/500.html66
-rw-r--r--actiontext/test/dummy/public/apple-touch-icon-precomposed.png0
-rw-r--r--actiontext/test/dummy/public/apple-touch-icon.png0
-rw-r--r--actiontext/test/dummy/public/favicon.ico0
-rw-r--r--actiontext/test/dummy/storage/.keep0
-rw-r--r--actiontext/test/dummy/tmp/.keep0
-rw-r--r--actiontext/test/dummy/tmp/storage/.keep0
-rw-r--r--actiontext/test/dummy/yarn.lock6071
-rw-r--r--actiontext/test/fixtures/files/racecar.jpgbin0 -> 1124062 bytes
-rw-r--r--actiontext/test/template/form_helper_test.rb67
-rw-r--r--actiontext/test/test_helper.rb30
-rw-r--r--actiontext/test/unit/attachment_test.rb60
-rw-r--r--actiontext/test/unit/content_test.rb116
-rw-r--r--actiontext/test/unit/model_test.rb47
-rw-r--r--actiontext/test/unit/plain_text_conversion_test.rb94
-rw-r--r--actiontext/test/unit/trix_attachment_test.rb83
-rw-r--r--actionview/CHANGELOG.md21
-rw-r--r--actionview/MIT-LICENSE2
-rw-r--r--actionview/app/assets/javascripts/MIT-LICENSE2
-rw-r--r--actionview/app/assets/javascripts/README.md11
-rw-r--r--actionview/lib/action_view.rb2
-rw-r--r--actionview/lib/action_view/base.rb18
-rw-r--r--actionview/lib/action_view/context.rb5
-rw-r--r--actionview/lib/action_view/gem_version.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb33
-rw-r--r--actionview/lib/action_view/helpers/csrf_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb221
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb2
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb2
-rw-r--r--actionview/lib/action_view/renderer/streaming_template_renderer.rb4
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb12
-rw-r--r--actionview/lib/action_view/rendering.rb4
-rw-r--r--actionview/lib/action_view/template.rb14
-rw-r--r--actionview/lib/action_view/template/handlers/erb/erubi.rb10
-rw-r--r--actionview/package.json4
-rw-r--r--actionview/test/abstract_unit.rb2
-rw-r--r--actionview/test/actionpack/controller/render_test.rb10
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb20
-rw-r--r--actionview/test/template/csrf_helper_test.rb46
-rw-r--r--actionview/test/template/template_test.rb2
-rw-r--r--actionview/test/template/url_helper_test.rb10
-rw-r--r--activejob/CHANGELOG.md2
-rw-r--r--activejob/MIT-LICENSE2
-rw-r--r--activejob/lib/active_job.rb2
-rw-r--r--activejob/lib/active_job/core.rb2
-rw-r--r--activejob/lib/active_job/exceptions.rb15
-rw-r--r--activejob/lib/active_job/gem_version.rb2
-rw-r--r--activejob/test/cases/exceptions_test.rb192
-rw-r--r--activejob/test/jobs/retry_job.rb22
-rw-r--r--activemodel/CHANGELOG.md34
-rw-r--r--activemodel/MIT-LICENSE2
-rw-r--r--activemodel/lib/active_model.rb2
-rw-r--r--activemodel/lib/active_model/callbacks.rb1
-rw-r--r--activemodel/lib/active_model/errors.rb39
-rw-r--r--activemodel/lib/active_model/gem_version.rb2
-rw-r--r--activemodel/lib/active_model/type/time.rb2
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb11
-rw-r--r--activemodel/test/cases/errors_test.rb80
-rw-r--r--activemodel/test/cases/type/time_test.rb1
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb13
-rw-r--r--activerecord/CHANGELOG.md72
-rw-r--r--activerecord/MIT-LICENSE2
-rw-r--r--activerecord/lib/active_record.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb6
-rw-r--r--activerecord/lib/active_record/associations/association.rb14
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb4
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb16
-rw-r--r--activerecord/lib/active_record/associations/foreign_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb33
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb34
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb38
-rw-r--r--activerecord/lib/active_record/core.rb17
-rw-r--r--activerecord/lib/active_record/gem_version.rb2
-rw-r--r--activerecord/lib/active_record/integration.rb15
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb25
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb8
-rw-r--r--activerecord/lib/active_record/railtie.rb55
-rw-r--r--activerecord/lib/active_record/railties/databases.rake4
-rw-r--r--activerecord/lib/active_record/relation.rb5
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb24
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb9
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb12
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb23
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/range_handler.rb23
-rw-r--r--activerecord/lib/active_record/relation/query_attribute.rb21
-rw-r--r--activerecord/lib/active_record/sanitization.rb37
-rw-r--r--activerecord/lib/active_record/schema.rb11
-rw-r--r--activerecord/lib/active_record/schema_migration.rb4
-rw-r--r--activerecord/lib/active_record/statement_cache.rb2
-rw-r--r--activerecord/lib/active_record/test_fixtures.rb23
-rw-r--r--activerecord/lib/active_record/timestamp.rb9
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb38
-rw-r--r--activerecord/lib/arel.rb1
-rw-r--r--activerecord/lib/arel/compatibility/wheres.rb35
-rw-r--r--activerecord/lib/arel/nodes/and.rb2
-rw-r--r--activerecord/lib/arel/nodes/bind_param.rb8
-rw-r--r--activerecord/lib/arel/nodes/case.rb2
-rw-r--r--activerecord/lib/arel/nodes/casted.rb4
-rw-r--r--activerecord/lib/arel/predications.rb32
-rw-r--r--activerecord/lib/arel/visitors/informix.rb3
-rw-r--r--activerecord/lib/arel/visitors/oracle12.rb2
-rw-r--r--activerecord/lib/arel/visitors/to_sql.rb16
-rw-r--r--activerecord/test/cases/adapter_test.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb13
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb17
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb14
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb4
-rw-r--r--activerecord/test/cases/arel/attributes/attribute_test.rb123
-rw-r--r--activerecord/test/cases/arel/nodes/and_test.rb9
-rw-r--r--activerecord/test/cases/arel/nodes/case_test.rb10
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb22
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb18
-rw-r--r--activerecord/test/cases/batches_test.rb2
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb4
-rw-r--r--activerecord/test/cases/calculations_test.rb26
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb16
-rw-r--r--activerecord/test/cases/counter_cache_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb46
-rw-r--r--activerecord/test/cases/fixtures_test.rb48
-rw-r--r--activerecord/test/cases/inheritance_test.rb4
-rw-r--r--activerecord/test/cases/integration_test.rb15
-rw-r--r--activerecord/test/cases/migration_test.rb10
-rw-r--r--activerecord/test/cases/persistence_test.rb14
-rw-r--r--activerecord/test/cases/reaper_test.rb2
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb16
-rw-r--r--activerecord/test/cases/relation/or_test.rb5
-rw-r--r--activerecord/test/cases/relation/where_test.rb10
-rw-r--r--activerecord/test/cases/sanitize_test.rb6
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb7
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb13
-rw-r--r--activerecord/test/cases/statement_cache_test.rb6
-rw-r--r--activerecord/test/cases/transactions_test.rb11
-rw-r--r--activerecord/test/cases/type/time_test.rb22
-rw-r--r--activerecord/test/models/drink_designer.rb6
-rw-r--r--activerecord/test/models/person.rb5
-rw-r--r--activerecord/test/models/reference.rb1
-rw-r--r--activerecord/test/support/stubs/strong_parameters.rb21
-rw-r--r--activestorage/CHANGELOG.md28
-rw-r--r--activestorage/MIT-LICENSE2
-rw-r--r--activestorage/README.md5
-rw-r--r--activestorage/app/assets/javascripts/activestorage.js5
-rw-r--r--activestorage/app/javascript/activestorage/blob_record.js7
-rw-r--r--activestorage/app/jobs/active_storage/analyze_job.rb2
-rw-r--r--activestorage/app/jobs/active_storage/base_job.rb1
-rw-r--r--activestorage/app/jobs/active_storage/purge_job.rb2
-rw-r--r--activestorage/app/models/active_storage/blob.rb16
-rw-r--r--activestorage/app/models/active_storage/blob/representable.rb10
-rw-r--r--activestorage/app/models/active_storage/preview.rb2
-rw-r--r--activestorage/app/models/active_storage/variant.rb8
-rw-r--r--activestorage/app/models/active_storage/variation.rb2
-rw-r--r--activestorage/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb7
-rw-r--r--activestorage/lib/active_storage.rb4
-rw-r--r--activestorage/lib/active_storage/engine.rb18
-rw-r--r--activestorage/lib/active_storage/gem_version.rb2
-rw-r--r--activestorage/lib/tasks/activestorage.rake7
-rw-r--r--activestorage/package.json4
-rw-r--r--activestorage/test/controllers/blobs_controller_test.rb25
-rw-r--r--activestorage/test/controllers/representations_controller_test.rb30
-rw-r--r--activestorage/test/dummy/config/application.rb2
-rw-r--r--activestorage/test/dummy/config/environments/development.rb1
-rw-r--r--activestorage/test/fixtures/files/racecar.tifbin0 -> 33705838 bytes
-rw-r--r--activestorage/test/models/blob_test.rb4
-rw-r--r--activestorage/test/models/variant_test.rb13
-rw-r--r--activesupport/CHANGELOG.md20
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/lib/active_support.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/reachable.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb26
-rw-r--r--activesupport/lib/active_support/duration.rb5
-rw-r--r--activesupport/lib/active_support/gem_version.rb2
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb3
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb2
-rw-r--r--activesupport/lib/active_support/key_generator.rb32
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb39
-rw-r--r--activesupport/lib/active_support/notifications.rb7
-rw-r--r--activesupport/test/autoloading_fixtures/raises_load_error.rb4
-rw-r--r--activesupport/test/cache/behaviors/cache_store_behavior.rb10
-rw-r--r--activesupport/test/constantize_test_cases.rb10
-rw-r--r--activesupport/test/core_ext/module/reachable_test.rb51
-rw-r--r--activesupport/test/core_ext/secure_random_test.rb20
-rw-r--r--activesupport/test/inflector_test.rb6
-rw-r--r--activesupport/test/test_case_test.rb2
-rwxr-xr-xci/travis.rb32
-rw-r--r--guides/CHANGELOG.md10
-rw-r--r--guides/rails_guides/generator.rb6
-rw-r--r--guides/rails_guides/markdown.rb8
-rw-r--r--guides/source/6_0_release_notes.md49
-rw-r--r--guides/source/action_cable_overview.md307
-rw-r--r--guides/source/action_mailbox_basics.md398
-rw-r--r--guides/source/action_mailer_basics.md52
-rw-r--r--guides/source/action_text_overview.md99
-rw-r--r--guides/source/active_record_basics.md6
-rw-r--r--guides/source/active_storage_overview.md9
-rw-r--r--guides/source/active_support_instrumentation.md48
-rw-r--r--guides/source/asset_pipeline.md4
-rw-r--r--guides/source/association_basics.md4
-rw-r--r--guides/source/caching_with_rails.md4
-rw-r--r--guides/source/configuring.md96
-rw-r--r--guides/source/development_dependencies_install.md1
-rw-r--r--guides/source/documents.yaml12
-rw-r--r--guides/source/engines.md27
-rw-r--r--guides/source/i18n.md8
-rw-r--r--guides/source/index.html.erb5
-rw-r--r--guides/source/layout.html.erb9
-rw-r--r--guides/source/rails_on_rack.md2
-rw-r--r--guides/source/routing.md2
-rw-r--r--guides/source/security.md99
-rw-r--r--guides/source/testing.md139
-rw-r--r--guides/source/upgrading_ruby_on_rails.md36
-rw-r--r--package.json1
-rw-r--r--rails.gemspec2
-rw-r--r--railties/CHANGELOG.md87
-rw-r--r--railties/MIT-LICENSE2
-rw-r--r--railties/RDOC_MAIN.rdoc6
-rw-r--r--railties/lib/rails/all.rb2
-rw-r--r--railties/lib/rails/api/task.rb16
-rw-r--r--railties/lib/rails/application.rb22
-rw-r--r--railties/lib/rails/application/configuration.rb34
-rw-r--r--railties/lib/rails/code_statistics.rb4
-rw-r--r--railties/lib/rails/command/base.rb14
-rw-r--r--railties/lib/rails/command/environment_argument.rb12
-rw-r--r--railties/lib/rails/commands/credentials/USAGE21
-rw-r--r--railties/lib/rails/commands/credentials/credentials_command.rb56
-rw-r--r--railties/lib/rails/commands/db/system/change/change_command.rb20
-rw-r--r--railties/lib/rails/commands/server/server_command.rb16
-rw-r--r--railties/lib/rails/engine.rb14
-rw-r--r--railties/lib/rails/gem_version.rb2
-rw-r--r--railties/lib/rails/generators.rb7
-rw-r--r--railties/lib/rails/generators/actions.rb20
-rw-r--r--railties/lib/rails/generators/app_base.rb78
-rw-r--r--railties/lib/rails/generators/app_name.rb50
-rw-r--r--railties/lib/rails/generators/database.rb57
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb74
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_6_0.rb.tt19
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/package.json.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt11
-rw-r--r--railties/lib/rails/generators/rails/db/system/change/change_generator.rb55
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb16
-rw-r--r--railties/lib/rails/info.rb4
-rw-r--r--railties/lib/rails/info_controller.rb12
-rw-r--r--railties/lib/rails/tasks/framework.rake6
-rw-r--r--railties/lib/rails/tasks/statistics.rake3
-rw-r--r--railties/lib/rails/tasks/yarn.rake3
-rw-r--r--railties/lib/rails/test_unit/testing.rake2
-rw-r--r--railties/test/application/configuration_test.rb307
-rw-r--r--railties/test/application/console_test.rb2
-rw-r--r--railties/test/application/credentials_test.rb56
-rw-r--r--railties/test/application/middleware/remote_ip_test.rb4
-rw-r--r--railties/test/application/middleware/session_test.rb128
-rw-r--r--railties/test/application/multiple_applications_test.rb4
-rw-r--r--railties/test/application/rake/routes_test.rb30
-rw-r--r--railties/test/application/server_test.rb14
-rw-r--r--railties/test/application/test_runner_test.rb27
-rw-r--r--railties/test/command/base_test.rb2
-rw-r--r--railties/test/commands/console_test.rb27
-rw-r--r--railties/test/commands/db_system_change_test.rb62
-rw-r--r--railties/test/commands/dbconsole_test.rb20
-rw-r--r--railties/test/commands/routes_test.rb328
-rw-r--r--railties/test/commands/server_test.rb6
-rw-r--r--railties/test/credentials_test.rb65
-rw-r--r--railties/test/generators/actions_test.rb9
-rw-r--r--railties/test/generators/app_generator_test.rb51
-rw-r--r--railties/test/generators/channel_generator_test.rb14
-rw-r--r--railties/test/generators/db_system_change_generator_test.rb78
-rw-r--r--railties/test/generators/generators_test_helper.rb36
-rw-r--r--railties/test/generators/plugin_generator_test.rb32
-rw-r--r--railties/test/generators/shared_generator_tests.rb15
-rw-r--r--railties/test/generators_test.rb1
-rw-r--r--railties/test/isolation/abstract_unit.rb4
-rw-r--r--railties/test/path_generation_test.rb2
-rw-r--r--railties/test/rails_info_controller_test.rb5
-rw-r--r--railties/test/rails_info_test.rb12
-rw-r--r--railties/test/railties/engine_test.rb20
-rw-r--r--tasks/release.rb73
-rw-r--r--version.rb2
-rw-r--r--yarn.lock5
679 files changed, 25388 insertions, 3464 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 139ee8013b..908a988e69 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -23,7 +23,7 @@ checks:
engines:
rubocop:
enabled: true
- channel: rubocop-0-60
+ channel: rubocop-0-63
ratings:
paths:
diff --git a/.github/autolabeler.yml b/.github/autolabeler.yml
index d73b2e3362..6eb6f354e5 100644
--- a/.github/autolabeler.yml
+++ b/.github/autolabeler.yml
@@ -1,9 +1,13 @@
actioncable:
- "actioncable/**/*"
+actionmailbox:
+ - "actionmailbox/**/*"
actionmailer:
- "actionmailer/**/*"
actionpack:
- "actionpack/**/*"
+actiontext:
+ - "actiontext/**/*"
actionview:
- "actionview/**/*"
activejob:
diff --git a/.rubocop.yml b/.rubocop.yml
index 96e2ad76d0..dce1a30d9f 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -8,6 +8,8 @@ AllCops:
- '**/vendor/**/*'
- 'actionpack/lib/action_dispatch/journey/parser.rb'
- 'railties/test/fixtures/tmp/**/*'
+ - 'actionmailbox/test/dummy/**/*'
+ - 'actiontext/test/dummy/**/*'
- 'node_modules/**/*'
Performance:
@@ -133,6 +135,9 @@ Style/FrozenStringLiteralComment:
- 'actionpack/test/**/*.builder'
- 'actionpack/test/**/*.ruby'
- 'activestorage/db/migrate/**/*.rb'
+ - 'activestorage/db/update_migrate/**/*.rb'
+ - 'actionmailbox/db/migrate/**/*.rb'
+ - 'actiontext/db/migrate/**/*.rb'
Style/RedundantFreeze:
Enabled: true
@@ -178,12 +183,21 @@ Style/UnneededPercentQ:
Lint/RequireParentheses:
Enabled: true
+Lint/ShadowingOuterLocalVariable:
+ Enabled: true
+
Lint/StringConversionInInterpolation:
Enabled: true
Lint/UriEscapeUnescape:
Enabled: true
+Lint/UselessAssignment:
+ Enabled: true
+
+Lint/DeprecatedClassMethods:
+ Enabled: true
+
Style/ParenthesesAroundCondition:
Enabled: true
diff --git a/.travis.yml b/.travis.yml
index 2e00860c95..8317b58c52 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -35,12 +35,12 @@ bundler_args: --jobs 3 --retry 3
before_install:
- "rm ${BUNDLE_GEMFILE}.lock"
- "travis_retry gem update --system"
- - "travis_retry gem install bundler -v '2.0.0.pre.2'"
+ - "travis_retry gem install bundler"
- "[[ -z $encrypted_0fb9444d0374_key && -z $encrypted_0fb9444d0374_iv ]] || openssl aes-256-cbc -K $encrypted_0fb9444d0374_key -iv $encrypted_0fb9444d0374_iv -in activestorage/test/service/configurations.yml.enc -out activestorage/test/service/configurations.yml -d"
- - "[[ $GEM != 'ac:integration' ]] || yarn install"
- - "[[ $GEM != 'av:ujs' ]] || nvm install node"
- - "[[ $GEM != 'av:ujs' ]] || node --version"
- - "[[ $GEM != 'av:ujs' ]] || (cd actionview && npm install)"
+ - "[[ $GEM != 'actioncable:integration' ]] || yarn install"
+ - "[[ $GEM != 'actionview:ujs' ]] || nvm install node"
+ - "[[ $GEM != 'actionview:ujs' ]] || node --version"
+ - "[[ $GEM != 'actionview:ujs' ]] || (cd actionview && npm install)"
- "[[ $GEM != 'railties' ]] || (curl -o- -L https://yarnpkg.com/install.sh | bash)"
- "[[ $GEM != 'railties' ]] || export PATH=$HOME/.yarn/bin:$PATH"
@@ -56,15 +56,16 @@ env:
global:
- "JRUBY_OPTS='--dev -J-Xmx1024M'"
matrix:
- - "GEM=ap,ac"
- - "GEM=am,amo,as,av,aj,ast"
- - "GEM=as PRESERVE_TIMEZONES=1"
- - "GEM=ar:sqlite3"
+ - "GEM=actionpack,actioncable"
+ - "GEM=actionmailer,activemodel,activesupport,actionview,activejob,activestorage,actionmailbox,actiontext"
+ - "GEM=activesupport PRESERVE_TIMEZONES=1"
+ - "GEM=activerecord:sqlite3"
- "GEM=guides"
- - "GEM=ac:integration"
+ - "GEM=actioncable:integration"
rvm:
- 2.5.3
+ - 2.6.0
- ruby-head
matrix:
@@ -78,6 +79,15 @@ matrix:
- "travis_retry gem install bundler"
- "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf"
- "sudo service postgresql restart 10"
+ - rvm: 2.6.0
+ env: "GEM=railties"
+ sudo: required
+ before_install:
+ - "rm ${BUNDLE_GEMFILE}.lock"
+ - "travis_retry gem update --system"
+ - "travis_retry gem install bundler"
+ - "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf"
+ - "sudo service postgresql restart 10"
- rvm: ruby-head
env: "GEM=railties"
sudo: required
@@ -88,10 +98,23 @@ matrix:
- "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf"
- "sudo service postgresql restart 10"
- rvm: 2.5.3
- env: "GEM=av:ujs"
+ env: "GEM=actionview:ujs"
- rvm: 2.5.3
sudo: required
- env: "GEM=aj:integration"
+ env: "GEM=activejob:integration"
+ services:
+ - memcached
+ - redis-server
+ - rabbitmq
+ before_install:
+ - sudo sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/*/main/pg_hba.conf
+ - "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf"
+ - "sudo service postgresql restart 10"
+ - "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/beanstalkd/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
+ - "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
+ - rvm: 2.6.0
+ sudo: required
+ env: "GEM=activejob:integration"
services:
- memcached
- redis-server
@@ -104,7 +127,7 @@ matrix:
- "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
- rvm: ruby-head
sudo: required
- env: "GEM=aj:integration"
+ env: "GEM=activejob:integration"
services:
- memcached
- redis-server
@@ -116,14 +139,21 @@ matrix:
- "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/beanstalkd/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
- "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
- rvm: 2.5.3
- env: "GEM=ar:mysql2"
+ env: "GEM=activerecord:mysql2"
+ sudo: required
+ before_install:
+ - "sudo mysql -e \"use mysql; update user set authentication_string='' where User='root'; update user set plugin='mysql_native_password';FLUSH PRIVILEGES;\""
+ - "sudo mysql_upgrade"
+ - "sudo service mysql restart"
+ - rvm: 2.6.0
+ env: "GEM=activerecord:mysql2"
sudo: required
before_install:
- "sudo mysql -e \"use mysql; update user set authentication_string='' where User='root'; update user set plugin='mysql_native_password';FLUSH PRIVILEGES;\""
- "sudo mysql_upgrade"
- "sudo service mysql restart"
- rvm: ruby-head
- env: "GEM=ar:mysql2"
+ env: "GEM=activerecord:mysql2"
sudo: required
before_install:
- "sudo mysql -e \"use mysql; update user set authentication_string='' where User='root'; update user set plugin='mysql_native_password';FLUSH PRIVILEGES;\""
@@ -131,20 +161,26 @@ matrix:
- "sudo service mysql restart"
- rvm: 2.5.3
env:
- - "GEM=ar:mysql2 MYSQL=mariadb"
+ - "GEM=activerecord:mysql2 MYSQL=mariadb"
addons:
mariadb: 10.3
- rvm: 2.5.3
env:
- - "GEM=ar:sqlite3_mem"
+ - "GEM=activerecord:sqlite3_mem"
- rvm: 2.5.3
- env: "GEM=ar:postgresql"
+ env: "GEM=activerecord:postgresql"
+ sudo: required
+ before_install:
+ - "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf"
+ - "sudo service postgresql restart 10"
+ - rvm: 2.6.0
+ env: "GEM=activerecord:postgresql"
sudo: required
before_install:
- "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf"
- "sudo service postgresql restart 10"
- rvm: ruby-head
- env: "GEM=ar:postgresql"
+ env: "GEM=activerecord:postgresql"
sudo: required
before_install:
- "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf"
@@ -152,15 +188,15 @@ matrix:
- rvm: jruby-head
jdk: oraclejdk8
env:
- - "GEM=ap"
+ - "GEM=actionpack"
- rvm: jruby-head
jdk: oraclejdk8
env:
- - "GEM=am,amo,aj"
+ - "GEM=actionmailer,activemodel,activejob"
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
- - env: "GEM=ac:integration"
+ - env: "GEM=actioncable:integration"
fast_finish: true
notifications:
diff --git a/Gemfile b/Gemfile
index 9df706f9c2..fe5f1b2f3c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -14,7 +14,7 @@ gem "capybara", ">= 2.15"
gem "rack-cache", "~> 1.2"
gem "sass-rails"
gem "turbolinks", "~> 5"
-gem "webpacker", github: "rails/webpacker", require: ENV["SKIP_REQUIRE_WEBPACKER"] != "true"
+gem "webpacker", ">= 4.0.0.rc.3", require: ENV["SKIP_REQUIRE_WEBPACKER"] != "true"
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid Active Model (and by extension the entire framework)
# being dependent on a binary library.
@@ -29,9 +29,6 @@ gem "json", ">= 2.0.0"
gem "rubocop", ">= 0.47", require: false
-# https://github.com/guard/rb-inotify/pull/79
-gem "rb-inotify", github: "matthewd/rb-inotify", branch: "close-handling", require: false
-
group :doc do
gem "sdoc", "~> 1.0"
gem "redcarpet", "~> 3.2.3", platforms: :ruby
@@ -39,7 +36,7 @@ group :doc do
gem "kindlerb", "~> 1.2.0"
end
-# Active Support.
+# Active Support
gem "dalli"
gem "listen", ">= 3.0.5", "< 3.2", require: false
gem "libxml-ruby", platforms: :ruby
@@ -48,7 +45,7 @@ gem "connection_pool", require: false
# for railties app_generator_test
gem "bootsnap", ">= 1.1.0", require: false
-# Active Job.
+# Active Job
group :job do
gem "resque", require: false
gem "resque-scheduler", require: false
@@ -88,6 +85,10 @@ group :storage do
gem "image_processing", "~> 1.2"
end
+# Action Mailbox
+gem "aws-sdk-sns", require: false
+gem "webmock"
+
group :ujs do
gem "qunit-selenium"
gem "chromedriver-helper"
diff --git a/Gemfile.lock b/Gemfile.lock
index cfa64ee24c..70a29f501d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,12 +1,4 @@
GIT
- remote: https://github.com/matthewd/rb-inotify.git
- revision: 856730aad4b285969e8dd621e44808a7c5af4242
- branch: close-handling
- specs:
- rb-inotify (0.9.9)
- ffi (~> 1.0)
-
-GIT
remote: https://github.com/matthewd/websocket-client-simple.git
revision: e161305f1a466b9398d86df3b1731b03362da91b
branch: close-race
@@ -23,74 +15,80 @@ GIT
queue_classic (3.2.0.RC1)
pg (>= 0.17, < 2.0)
-GIT
- remote: https://github.com/rails/webpacker.git
- revision: bb132d591da35095e3246082cba3d693f847e0b5
- specs:
- webpacker (4.0.0.pre.3)
- activesupport (>= 4.2)
- rack-proxy (>= 0.6.1)
- railties (>= 4.2)
-
PATH
remote: .
specs:
- actioncable (6.0.0.alpha)
- actionpack (= 6.0.0.alpha)
+ actioncable (6.0.0.beta1)
+ actionpack (= 6.0.0.beta1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailer (6.0.0.alpha)
- actionpack (= 6.0.0.alpha)
- actionview (= 6.0.0.alpha)
- activejob (= 6.0.0.alpha)
+ actionmailbox (6.0.0.beta1)
+ actionpack (= 6.0.0.beta1)
+ activejob (= 6.0.0.beta1)
+ activerecord (= 6.0.0.beta1)
+ activestorage (= 6.0.0.beta1)
+ activesupport (= 6.0.0.beta1)
+ mail (>= 2.7.1)
+ actionmailer (6.0.0.beta1)
+ actionpack (= 6.0.0.beta1)
+ actionview (= 6.0.0.beta1)
+ activejob (= 6.0.0.beta1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (6.0.0.alpha)
- actionview (= 6.0.0.alpha)
- activesupport (= 6.0.0.alpha)
+ actionpack (6.0.0.beta1)
+ actionview (= 6.0.0.beta1)
+ activesupport (= 6.0.0.beta1)
rack (~> 2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (6.0.0.alpha)
- activesupport (= 6.0.0.alpha)
+ actiontext (6.0.0.beta1)
+ actionpack (= 6.0.0.beta1)
+ activerecord (= 6.0.0.beta1)
+ activestorage (= 6.0.0.beta1)
+ activesupport (= 6.0.0.beta1)
+ nokogiri (>= 1.8.5)
+ actionview (6.0.0.beta1)
+ activesupport (= 6.0.0.beta1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
- activejob (6.0.0.alpha)
- activesupport (= 6.0.0.alpha)
+ activejob (6.0.0.beta1)
+ activesupport (= 6.0.0.beta1)
globalid (>= 0.3.6)
- activemodel (6.0.0.alpha)
- activesupport (= 6.0.0.alpha)
- activerecord (6.0.0.alpha)
- activemodel (= 6.0.0.alpha)
- activesupport (= 6.0.0.alpha)
- activestorage (6.0.0.alpha)
- actionpack (= 6.0.0.alpha)
- activerecord (= 6.0.0.alpha)
+ activemodel (6.0.0.beta1)
+ activesupport (= 6.0.0.beta1)
+ activerecord (6.0.0.beta1)
+ activemodel (= 6.0.0.beta1)
+ activesupport (= 6.0.0.beta1)
+ activestorage (6.0.0.beta1)
+ actionpack (= 6.0.0.beta1)
+ activerecord (= 6.0.0.beta1)
marcel (~> 0.3.1)
- activesupport (6.0.0.alpha)
+ activesupport (6.0.0.beta1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
- rails (6.0.0.alpha)
- actioncable (= 6.0.0.alpha)
- actionmailer (= 6.0.0.alpha)
- actionpack (= 6.0.0.alpha)
- actionview (= 6.0.0.alpha)
- activejob (= 6.0.0.alpha)
- activemodel (= 6.0.0.alpha)
- activerecord (= 6.0.0.alpha)
- activestorage (= 6.0.0.alpha)
- activesupport (= 6.0.0.alpha)
+ rails (6.0.0.beta1)
+ actioncable (= 6.0.0.beta1)
+ actionmailbox (= 6.0.0.beta1)
+ actionmailer (= 6.0.0.beta1)
+ actionpack (= 6.0.0.beta1)
+ actiontext (= 6.0.0.beta1)
+ actionview (= 6.0.0.beta1)
+ activejob (= 6.0.0.beta1)
+ activemodel (= 6.0.0.beta1)
+ activerecord (= 6.0.0.beta1)
+ activestorage (= 6.0.0.beta1)
+ activesupport (= 6.0.0.beta1)
bundler (>= 1.3.0)
- railties (= 6.0.0.alpha)
+ railties (= 6.0.0.beta1)
sprockets-rails (>= 2.0.0)
- railties (6.0.0.alpha)
- actionpack (= 6.0.0.alpha)
- activesupport (= 6.0.0.alpha)
+ railties (6.0.0.beta1)
+ actionpack (= 6.0.0.beta1)
+ activesupport (= 6.0.0.beta1)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
@@ -129,6 +127,9 @@ GEM
aws-sdk-core (~> 3, >= 3.26.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.0)
+ aws-sdk-sns (1.8.1)
+ aws-sdk-core (~> 3, >= 3.37.0)
+ aws-sigv4 (~> 1.0)
aws-sigv4 (1.0.3)
azure-core (0.1.14)
faraday (~> 0.9)
@@ -194,6 +195,8 @@ GEM
concurrent-ruby (1.1.3)
connection_pool (2.2.2)
cookiejar (0.3.3)
+ crack (0.4.3)
+ safe_yaml (~> 1.0.0)
crass (1.0.4)
curses (1.0.2)
daemons (1.2.6)
@@ -215,7 +218,7 @@ GEM
http_parser.rb (>= 0.6.0)
em-socksify (0.3.2)
eventmachine (>= 1.0.0.beta.4)
- erubi (1.7.1)
+ erubi (1.8.0)
et-orbi (1.1.6)
tzinfo
event_emitter (0.2.6)
@@ -269,18 +272,19 @@ GEM
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.7)
+ hashdiff (0.3.7)
hiredis (0.6.3)
hiredis (0.6.3-java)
http_parser.rb (0.6.0)
httpclient (2.8.3)
- i18n (1.1.1)
+ i18n (1.5.2)
concurrent-ruby (~> 1.0)
image_processing (1.7.1)
mini_magick (~> 4.0)
ruby-vips (>= 2.0.13, < 3)
io-like (0.3.0)
- jaro_winkler (1.5.1)
- jaro_winkler (1.5.1-java)
+ jaro_winkler (1.5.2)
+ jaro_winkler (1.5.2-java)
jdbc-mysql (5.1.46)
jdbc-postgres (42.1.4)
jdbc-sqlite3 (3.20.1)
@@ -308,10 +312,10 @@ GEM
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2018.0812)
- mimemagic (0.3.2)
+ mimemagic (0.3.3)
mini_magick (4.9.2)
mini_mime (1.0.1)
- mini_portile2 (2.3.0)
+ mini_portile2 (2.4.0)
minitest (5.11.3)
minitest-bisect (1.4.0)
minitest-server (~> 1.0)
@@ -333,17 +337,16 @@ GEM
mysql2 (0.5.2-x64-mingw32)
mysql2 (0.5.2-x86-mingw32)
nio4r (2.3.1)
- nio4r (2.3.1-java)
- nokogiri (1.8.5)
- mini_portile2 (~> 2.3.0)
- nokogiri (1.8.5-java)
- nokogiri (1.8.5-x64-mingw32)
- mini_portile2 (~> 2.3.0)
- nokogiri (1.8.5-x86-mingw32)
- mini_portile2 (~> 2.3.0)
+ nokogiri (1.9.1)
+ mini_portile2 (~> 2.4.0)
+ nokogiri (1.9.1-java)
+ nokogiri (1.9.1-x64-mingw32)
+ mini_portile2 (~> 2.4.0)
+ nokogiri (1.9.1-x86-mingw32)
+ mini_portile2 (~> 2.4.0)
os (1.0.0)
- parallel (1.12.1)
- parser (2.5.3.0)
+ parallel (1.13.0)
+ parser (2.6.0.0)
ast (~> 2.4.0)
path_expander (1.0.3)
pg (1.1.3)
@@ -377,6 +380,8 @@ GEM
rainbow (3.0.0)
rake (12.3.1)
rb-fsevent (0.10.3)
+ rb-inotify (0.10.0)
+ ffi (~> 1.0)
rdoc (6.0.4)
redcarpet (3.2.3)
redis (4.0.3)
@@ -399,7 +404,7 @@ GEM
resque (~> 1.26)
rufus-scheduler (~> 3.2)
retriable (3.1.2)
- rubocop (0.61.1)
+ rubocop (0.63.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1)
@@ -414,6 +419,7 @@ GEM
rubyzip (1.2.2)
rufus-scheduler (3.5.2)
fugit (~> 1.1, >= 1.1.5)
+ safe_yaml (1.0.4)
sass (3.7.2)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
@@ -493,11 +499,17 @@ GEM
json (>= 1.8)
nokogiri (~> 1.6)
wdm (0.1.1)
+ webmock (3.4.2)
+ addressable (>= 2.3.6)
+ crack (>= 0.3.2)
+ hashdiff
+ webpacker (4.0.0.rc.3)
+ activesupport (>= 4.2)
+ rack-proxy (>= 0.6.1)
+ railties (>= 4.2)
websocket (1.2.8)
websocket-driver (0.7.0)
websocket-extensions (>= 0.1.0)
- websocket-driver (0.7.0-java)
- websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
@@ -513,6 +525,7 @@ DEPENDENCIES
activerecord-jdbcpostgresql-adapter (>= 1.3.0)
activerecord-jdbcsqlite3-adapter (>= 1.3.0)
aws-sdk-s3
+ aws-sdk-sns
azure-storage
backburner
bcrypt (~> 3.1.11)
@@ -548,7 +561,6 @@ DEPENDENCIES
rack-cache (~> 1.2)
rails!
rake (>= 11.1)
- rb-inotify!
redcarpet (~> 3.2.3)
redis (~> 4.0)
redis-namespace
@@ -569,8 +581,9 @@ DEPENDENCIES
uglifier (>= 1.3.0)
w3c_validators
wdm (>= 0.1.0)
- webpacker!
+ webmock
+ webpacker (>= 4.0.0.rc.3)
websocket-client-simple!
BUNDLED WITH
- 1.17.1
+ 1.17.2
diff --git a/MIT-LICENSE b/MIT-LICENSE
index 8f769c0767..315a4bc7c4 100644
--- a/MIT-LICENSE
+++ b/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2018 David Heinemeier Hansson
+Copyright (c) 2005-2019 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/RAILS_VERSION b/RAILS_VERSION
index 747766c587..248da9b7b5 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-6.0.0.alpha
+6.0.0.beta1
diff --git a/README.md b/README.md
index 56d2b9909c..566136e2a2 100644
--- a/README.md
+++ b/README.md
@@ -44,11 +44,11 @@ or to generate the body of an email. In Rails, View generation is handled by [Ac
[Active Record](activerecord/README.rdoc), [Active Model](activemodel/README.rdoc), [Action Pack](actionpack/README.rdoc), and [Action View](actionview/README.rdoc) can each be used independently outside Rails.
In addition to that, Rails also comes with [Action Mailer](actionmailer/README.rdoc), a library
-to generate and send emails; [Active Job](activejob/README.md), a
-framework for declaring jobs and making them run on a variety of queuing
+to generate and send emails; [Action Mailbox](actionmailbox/README.md), a library to receive emails within a Rails application;
+[Active Job](activejob/README.md), a framework for declaring jobs and making them run on a variety of queuing
backends; [Action Cable](actioncable/README.md), a framework to
integrate WebSockets with a Rails application; [Active Storage](activestorage/README.md), a library to attach cloud
-and local files to Rails applications;
+and local files to Rails applications; [Action Text](actiontext/README.md), a library to handle rich text content;
and [Active Support](activesupport/README.rdoc), a collection
of utility classes and standard library extensions that are useful for Rails,
and may also be used independently outside Rails.
diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md
index 287dd4fa12..7fa180cc43 100644
--- a/RELEASING_RAILS.md
+++ b/RELEASING_RAILS.md
@@ -109,13 +109,14 @@ browser.
This will stop you from looking silly when you push an RC to rubygems.org and
then realize it is broken.
-### Release to RubyGems and NPM.
-
-IMPORTANT: The Action Cable client and Action View's UJS adapter are released
-as NPM packages, so you must have Node.js installed, have an NPM account
-(npmjs.com), and be a package owner for `actioncable` and `rails-ujs` (you can
-check this via `npm owner ls actioncable` and `npm owner ls rails-ujs`) in
-order to do a full release. Do not release until you're set up with NPM!
+### Release to RubyGems and npm.
+
+IMPORTANT: Several gems have JavaScript components that are released as npm
+packages, so you must have Node.js installed, have an npm account (npmjs.com),
+and be a package owner for `@rails/actioncable`, `@rails/actiontext`,
+`@rails/activestorage`, and `@rails/ujs`. You can check this by making sure your
+npm user (`npm whoami`) is listed as an owner (`npm owner ls <pkg>`) of each
+package. Do not release until you're set up with npm!
The release task will sign the release tag. If you haven't got commit signing
set up, use https://git-scm.com/book/tr/v2/Git-Tools-Signing-Your-Work as a
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index 162de0df0b..7f3177b64e 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,3 +1,28 @@
+* Add `Channel::Base#broadcast_to`.
+
+ You can now call `broadcast_to` within a channel action, which equals to
+ the `self.class.broadcast_to`.
+
+ *Vladimir Dementyev*
+
+* Make `Channel::Base.broadcasting_for` a public API.
+
+ You can use `.broadcasting_for` to generate a unique stream identifier within
+ a channel for the specified target (e.g. Active Record model):
+
+ ```ruby
+ ChatChannel.broadcasting_for(model) # => "chat:<model.to_gid_param>"
+ ```
+
+ *Vladimir Dementyev*
+
+
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Merge [`action-cable-testing`](https://github.com/palkan/action-cable-testing) to Rails.
+
+ *Vladimir Dementyev*
+
* The JavaScript WebSocket client will no longer try to reconnect
when you call `reject_unauthorized_connection` on the connection.
diff --git a/actioncable/MIT-LICENSE b/actioncable/MIT-LICENSE
index a42759f024..bdb42d988a 100644
--- a/actioncable/MIT-LICENSE
+++ b/actioncable/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2015-2018 Basecamp, LLC
+Copyright (c) 2015-2019 Basecamp, LLC
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actioncable/README.md b/actioncable/README.md
index a2783d6f45..60c879e1f4 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -7,550 +7,7 @@ and scalable. It's a full-stack offering that provides both a client-side
JavaScript framework and a server-side Ruby framework. You have access to your full
domain model written with Active Record or your ORM of choice.
-## Terminology
-
-A single Action Cable server can handle multiple connection instances. It has one
-connection instance per WebSocket connection. A single user may have multiple
-WebSockets open to your application if they use multiple browser tabs or devices.
-The client of a WebSocket connection is called the consumer.
-
-Each consumer can in turn subscribe to multiple cable channels. Each channel encapsulates
-a logical unit of work, similar to what a controller does in a regular MVC setup. For example,
-you could have a `ChatChannel` and an `AppearancesChannel`, and a consumer could be subscribed to either
-or to both of these channels. At the very least, a consumer should be subscribed to one channel.
-
-When the consumer is subscribed to a channel, they act as a subscriber. The connection between
-the subscriber and the channel is, surprise-surprise, called a subscription. A consumer
-can act as a subscriber to a given channel any number of times. For example, a consumer
-could subscribe to multiple chat rooms at the same time. (And remember that a physical user may
-have multiple consumers, one per tab/device open to your connection).
-
-Each channel can then again be streaming zero or more broadcastings. A broadcasting is a
-pubsub link where anything transmitted by the broadcaster is sent directly to the channel
-subscribers who are streaming that named broadcasting.
-
-As you can see, this is a fairly deep architectural stack. There's a lot of new terminology
-to identify the new pieces, and on top of that, you're dealing with both client and server side
-reflections of each unit.
-
-## Examples
-
-### A full-stack example
-
-The first thing you must do is define your `ApplicationCable::Connection` class in Ruby. This
-is the place where you authorize the incoming connection, and proceed to establish it,
-if all is well. Here's the simplest example starting with the server-side connection class:
-
-```ruby
-# app/channels/application_cable/connection.rb
-module ApplicationCable
- class Connection < ActionCable::Connection::Base
- identified_by :current_user
-
- def connect
- self.current_user = find_verified_user
- end
-
- private
- def find_verified_user
- if verified_user = User.find_by(id: cookies.encrypted[:user_id])
- verified_user
- else
- reject_unauthorized_connection
- end
- end
- end
-end
-```
-Here `identified_by` is a connection identifier that can be used to find the specific connection again or later.
-Note that anything marked as an identifier will automatically create a delegate by the same name on any channel instances created off the connection.
-
-This relies on the fact that you will already have handled authentication of the user, and
-that a successful authentication sets a signed cookie with the `user_id`. This cookie is then
-automatically sent to the connection instance when a new connection is attempted, and you
-use that to set the `current_user`. By identifying the connection by this same current_user,
-you're also ensuring that you can later retrieve all open connections by a given user (and
-potentially disconnect them all if the user is deleted or deauthorized).
-
-Next, you should define your `ApplicationCable::Channel` class in Ruby. This is the place where you put
-shared logic between your channels.
-
-```ruby
-# app/channels/application_cable/channel.rb
-module ApplicationCable
- class Channel < ActionCable::Channel::Base
- end
-end
-```
-
-The client-side needs to setup a consumer instance of this connection. That's done like so:
-
-```js
-// app/assets/javascripts/cable.js
-//= require action_cable
-//= require_self
-//= require_tree ./channels
-
-(function() {
- this.App || (this.App = {});
-
- App.cable = ActionCable.createConsumer("ws://cable.example.com");
-}).call(this);
-```
-
-The `ws://cable.example.com` address must point to your Action Cable server(s), and it
-must share a cookie namespace with the rest of the application (which may live under http://example.com).
-This ensures that the signed cookie will be correctly sent.
-
-That's all you need to establish the connection! But of course, this isn't very useful in
-itself. This just gives you the plumbing. To make stuff happen, you need content. That content
-is defined by declaring channels on the server and allowing the consumer to subscribe to them.
-
-
-### Channel example 1: User appearances
-
-Here's a simple example of a channel that tracks whether a user is online or not, and also what page they are currently on.
-(This is useful for creating presence features like showing a green dot next to a user's name if they're online).
-
-First you declare the server-side channel:
-
-```ruby
-# app/channels/appearance_channel.rb
-class AppearanceChannel < ApplicationCable::Channel
- def subscribed
- current_user.appear
- end
-
- def unsubscribed
- current_user.disappear
- end
-
- def appear(data)
- current_user.appear on: data['appearing_on']
- end
-
- def away
- current_user.away
- end
-end
-```
-
-The `#subscribed` callback is invoked when, as we'll show below, a client-side subscription is initiated. In this case,
-we take that opportunity to say "the current user has indeed appeared". That appear/disappear API could be backed by
-Redis or a database or whatever else. Here's what the client-side of that looks like:
-
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/appearance.coffee
-App.cable.subscriptions.create "AppearanceChannel",
- # Called when the subscription is ready for use on the server
- connected: ->
- @install()
- @appear()
-
- # Called when the WebSocket connection is closed
- disconnected: ->
- @uninstall()
-
- # Called when the subscription is rejected by the server
- rejected: ->
- @uninstall()
-
- appear: ->
- # Calls `AppearanceChannel#appear(data)` on the server
- @perform("appear", appearing_on: $("main").data("appearing-on"))
-
- away: ->
- # Calls `AppearanceChannel#away` on the server
- @perform("away")
-
-
- buttonSelector = "[data-behavior~=appear_away]"
-
- install: ->
- $(document).on "turbolinks:load.appearance", =>
- @appear()
-
- $(document).on "click.appearance", buttonSelector, =>
- @away()
- false
-
- $(buttonSelector).show()
-
- uninstall: ->
- $(document).off(".appearance")
- $(buttonSelector).hide()
-```
-
-Simply calling `App.cable.subscriptions.create` will setup the subscription, which will call `AppearanceChannel#subscribed`,
-which in turn is linked to the original `App.cable` -> `ApplicationCable::Connection` instances.
-
-Next, we link the client-side `appear` method to `AppearanceChannel#appear(data)`. This is possible because the server-side
-channel instance will automatically expose the public methods declared on the class (minus the callbacks), so that these
-can be reached as remote procedure calls via a subscription's `perform` method.
-
-### Channel example 2: Receiving new web notifications
-
-The appearance example was all about exposing server functionality to client-side invocation over the WebSocket connection.
-But the great thing about WebSockets is that it's a two-way street. So now let's show an example where the server invokes
-an action on the client.
-
-This is a web notification channel that allows you to trigger client-side web notifications when you broadcast to the right
-streams:
-
-```ruby
-# app/channels/web_notifications_channel.rb
-class WebNotificationsChannel < ApplicationCable::Channel
- def subscribed
- stream_from "web_notifications_#{current_user.id}"
- end
-end
-```
-
-```coffeescript
-# Client-side, which assumes you've already requested the right to send web notifications
-App.cable.subscriptions.create "WebNotificationsChannel",
- received: (data) ->
- new Notification data["title"], body: data["body"]
-```
-
-```ruby
-# Somewhere in your app this is called, perhaps from a NewCommentJob
-ActionCable.server.broadcast \
- "web_notifications_#{current_user.id}", { title: 'New things!', body: 'All the news that is fit to print' }
-```
-
-The `ActionCable.server.broadcast` call places a message in the Action Cable pubsub queue under a separate broadcasting name for each user. For a user with an ID of 1, the broadcasting name would be `web_notifications_1`.
-The channel has been instructed to stream everything that arrives at `web_notifications_1` directly to the client by invoking the
-`#received(data)` callback. The data is the hash sent as the second parameter to the server-side broadcast call, JSON encoded for the trip
-across the wire, and unpacked for the data argument arriving to `#received`.
-
-
-### Passing Parameters to Channel
-
-You can pass parameters from the client side to the server side when creating a subscription. For example:
-
-```ruby
-# app/channels/chat_channel.rb
-class ChatChannel < ApplicationCable::Channel
- def subscribed
- stream_from "chat_#{params[:room]}"
- end
-end
-```
-
-If you pass an object as the first argument to `subscriptions.create`, that object will become the params hash in your cable channel. The keyword `channel` is required.
-
-```coffeescript
-# Client-side, which assumes you've already requested the right to send web notifications
-App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
- received: (data) ->
- @appendLine(data)
-
- appendLine: (data) ->
- html = @createLine(data)
- $("[data-chat-room='Best Room']").append(html)
-
- createLine: (data) ->
- """
- <article class="chat-line">
- <span class="speaker">#{data["sent_by"]}</span>
- <span class="body">#{data["body"]}</span>
- </article>
- """
-```
-
-```ruby
-# Somewhere in your app this is called, perhaps from a NewCommentJob
-ActionCable.server.broadcast \
- "chat_#{room}", { sent_by: 'Paul', body: 'This is a cool chat app.' }
-```
-
-
-### Rebroadcasting message
-
-A common use case is to rebroadcast a message sent by one client to any other connected clients.
-
-```ruby
-# app/channels/chat_channel.rb
-class ChatChannel < ApplicationCable::Channel
- def subscribed
- stream_from "chat_#{params[:room]}"
- end
-
- def receive(data)
- ActionCable.server.broadcast "chat_#{params[:room]}", data
- end
-end
-```
-
-```coffeescript
-# Client-side, which assumes you've already requested the right to send web notifications
-App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
- received: (data) ->
- # data => { sent_by: "Paul", body: "This is a cool chat app." }
-
-App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
-```
-
-The rebroadcast will be received by all connected clients, _including_ the client that sent the message. Note that params are the same as they were when you subscribed to the channel.
-
-
-### More complete examples
-
-See the [rails/actioncable-examples](https://github.com/rails/actioncable-examples) repository for a full example of how to setup Action Cable in a Rails app, and how to add channels.
-
-## Configuration
-
-Action Cable has three required configurations: a subscription adapter, allowed request origins, and the cable server URL (which can optionally be set on the client side).
-
-### Redis
-
-By default, `ActionCable::Server::Base` will look for a configuration file in `Rails.root.join('config/cable.yml')`.
-This file must specify an adapter and a URL for each Rails environment. It may use the following format:
-
-```yaml
-production: &production
- adapter: redis
- url: redis://10.10.3.153:6381
-development: &development
- adapter: redis
- url: redis://localhost:6379
-test: *development
-```
-
-You can also change the location of the Action Cable config file in a Rails initializer with something like:
-
-```ruby
-Rails.application.paths.add "config/cable", with: "somewhere/else/cable.yml"
-```
-
-### Allowed Request Origins
-
-Action Cable will only accept requests from specific origins.
-
-By default, only an origin matching the cable server itself will be permitted.
-Additional origins can be specified using strings or regular expressions, provided in an array.
-
-```ruby
-Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/]
-```
-
-When running in the development environment, this defaults to "http://localhost:3000".
-
-To disable protection and allow requests from any origin:
-
-```ruby
-Rails.application.config.action_cable.disable_request_forgery_protection = true
-```
-
-To disable automatic access for same-origin requests, and strictly allow
-only the configured origins:
-
-```ruby
-Rails.application.config.action_cable.allow_same_origin_as_host = false
-```
-
-### Consumer Configuration
-
-Once you have decided how to run your cable server (see below), you must provide the server URL (or path) to your client-side setup.
-There are two ways you can do this.
-
-The first is to simply pass it in when creating your consumer. For a standalone server,
-this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`, and for an in-app server,
-something like: `App.cable = ActionCable.createConsumer("/cable")`.
-
-The second option is to pass the server URL through the `action_cable_meta_tag` in your layout.
-This uses a URL or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable".
-
-This method is especially useful if your WebSocket URL might change between environments. If you host your production server via https, you will need to use the wss scheme
-for your Action Cable server, but development might remain http and use the ws scheme. You might use localhost in development and your
-domain in production.
-
-In any case, to vary the WebSocket URL between environments, add the following configuration to each environment:
-
-```ruby
-config.action_cable.url = "ws://example.com:28080"
-```
-
-Then add the following line to your layout before your JavaScript tag:
-
-```erb
-<%= action_cable_meta_tag %>
-```
-
-And finally, create your consumer like so:
-
-```coffeescript
-App.cable = ActionCable.createConsumer()
-```
-
-### Other Configurations
-
-The other common option to configure is the log tags applied to the per-connection logger. Here's an example that uses the user account id if available, else "no-account" while tagging:
-
-```ruby
-config.action_cable.log_tags = [
- -> request { request.env['user_account_id'] || "no-account" },
- :action_cable,
- -> request { request.uuid }
-]
-```
-
-For a full list of all configuration options, see the `ActionCable::Server::Configuration` class.
-
-Also note that your server must provide at least the same number of database connections as you have workers. The default worker pool is set to 4, so that means you have to make at least that available. You can change that in `config/database.yml` through the `pool` attribute.
-
-
-## Running the cable server
-
-### Standalone
-The cable server(s) is separated from your normal application server. It's still a Rack application, but it is its own Rack
-application. The recommended basic setup is as follows:
-
-```ruby
-# cable/config.ru
-require_relative '../config/environment'
-Rails.application.eager_load!
-
-run ActionCable.server
-```
-
-Then you start the server using a binstub in bin/cable ala:
-```sh
-#!/bin/bash
-bundle exec puma -p 28080 cable/config.ru
-```
-
-The above will start a cable server on port 28080.
-
-### In app
-
-If you are using a server that supports the [Rack socket hijacking API](https://www.rubydoc.info/github/rack/rack/file/SPEC#label-Hijacking), Action Cable can run alongside your Rails application. For example, to listen for WebSocket requests on `/websocket`, specify that path to `config.action_cable.mount_path`:
-
-```ruby
-# config/application.rb
-class Application < Rails::Application
- config.action_cable.mount_path = '/websocket'
-end
-```
-
-For every instance of your server you create and for every worker your server spawns, you will also have a new instance of Action Cable, but the use of Redis keeps messages synced across connections.
-
-### Notes
-
-Beware that currently, the cable server will _not_ auto-reload any changes in the framework. As we've discussed, long-running cable connections mean long-running objects. We don't yet have a way of reloading the classes of those objects in a safe manner. So when you change your channels, or the model your channels use, you must restart the cable server.
-
-We'll get all this abstracted properly when the framework is integrated into Rails.
-
-The WebSocket server doesn't have access to the session, but it has access to the cookies. This can be used when you need to handle authentication. You can see one way of doing that with Devise in this [article](https://greg.molnar.io/blog/actioncable-devise-authentication/).
-
-## Dependencies
-
-Action Cable provides a subscription adapter interface to process its pubsub internals. By default, asynchronous, inline, PostgreSQL, and Redis adapters are included. The default adapter in new Rails applications is the asynchronous (`async`) adapter. To create your own adapter, you can look at `ActionCable::SubscriptionAdapter::Base` for all methods that must be implemented, and any of the adapters included within Action Cable as example implementations.
-
-The Ruby side of things is built on top of [websocket-driver](https://github.com/faye/websocket-driver-ruby), [nio4r](https://github.com/celluloid/nio4r), and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).
-
-
-## Deployment
-
-Action Cable is powered by a combination of WebSockets and threads. All of the
-connection management is handled internally by utilizing Ruby's native thread
-support, which means you can use all your regular Rails models with no problems
-as long as you haven't committed any thread-safety sins.
-
-The Action Cable server does _not_ need to be a multi-threaded application server.
-This is because Action Cable uses the [Rack socket hijacking API](https://www.rubydoc.info/github/rack/rack/file/SPEC#label-Hijacking)
-to take over control of connections from the application server. Action Cable
-then manages connections internally, in a multithreaded manner, regardless of
-whether the application server is multi-threaded or not. So Action Cable works
-with all the popular application servers -- Unicorn, Puma and Passenger.
-
-Action Cable does not work with WEBrick, because WEBrick does not support the
-Rack socket hijacking API.
-
-## Frontend assets
-
-Action Cable's frontend assets are distributed through two channels: the
-official gem and npm package, both titled `actioncable`.
-
-### Gem usage
-
-Through the `actioncable` gem, Action Cable's frontend assets are
-available through the Rails Asset Pipeline. Create a `cable.js` or
-`cable.coffee` file (this is automatically done for you with Rails
-generators), and then simply require the assets:
-
-In JavaScript...
-
-```javascript
-//= require action_cable
-```
-
-... and in CoffeeScript:
-
-```coffeescript
-#= require action_cable
-```
-
-### npm usage
-
-In addition to being available through the `actioncable` gem, Action Cable's
-frontend JS assets are also bundled in an officially supported npm module,
-intended for usage in standalone frontend applications that communicate with a
-Rails application. A common use case for this could be if you have a decoupled
-frontend application written in React, Ember.js, etc. and want to add real-time
-WebSocket functionality.
-
-### Installation
-
-```
-npm install actioncable --save
-```
-
-### Usage
-
-The `ActionCable` constant is available as a `require`-able module, so
-you only have to require the package to gain access to the API that is
-provided.
-
-In JavaScript...
-
-```javascript
-ActionCable = require('actioncable')
-
-var cable = ActionCable.createConsumer('wss://RAILS-API-PATH.com/cable')
-
-cable.subscriptions.create('AppearanceChannel', {
- // normal channel code goes here...
-});
-```
-
-and in CoffeeScript...
-
-```coffeescript
-ActionCable = require('actioncable')
-
-cable = ActionCable.createConsumer('wss://RAILS-API-PATH.com/cable')
-
-cable.subscriptions.create 'AppearanceChannel',
- # normal channel code goes here...
-```
-
-## Download and Installation
-
-The latest version of Action Cable can be installed with [RubyGems](#gem-usage),
-or with [npm](#npm-usage).
-
-Source code can be downloaded as part of the Rails project on GitHub
-
-* https://github.com/rails/rails/tree/master/actioncable
-
-## License
-
-Action Cable is released under the MIT license:
-
-* https://opensource.org/licenses/MIT
-
+You can read more about Action Cable in the [Action Cable Overview](https://edgeguides.rubyonrails.org/action_cable_overview.html) guide.
## Support
diff --git a/actioncable/app/assets/javascripts/action_cable.js b/actioncable/app/assets/javascripts/action_cable.js
index 65e32d6c3f..280adbfa83 100644
--- a/actioncable/app/assets/javascripts/action_cable.js
+++ b/actioncable/app/assets/javascripts/action_cable.js
@@ -3,8 +3,8 @@
})(this, function(exports) {
"use strict";
var adapters = {
- logger: window.console,
- WebSocket: window.WebSocket
+ logger: self.console,
+ WebSocket: self.WebSocket
};
var logger = {
log: function log() {
@@ -49,7 +49,7 @@
this.startedAt = now();
delete this.stoppedAt;
this.startPolling();
- document.addEventListener("visibilitychange", this.visibilityDidChange);
+ addEventListener("visibilitychange", this.visibilityDidChange);
logger.log("ConnectionMonitor started. pollInterval = " + this.getPollInterval() + " ms");
}
};
@@ -57,7 +57,7 @@
if (this.isRunning()) {
this.stoppedAt = now();
this.stopPolling();
- document.removeEventListener("visibilitychange", this.visibilityDidChange);
+ removeEventListener("visibilitychange", this.visibilityDidChange);
logger.log("ConnectionMonitor stopped");
}
};
@@ -192,7 +192,7 @@
this.monitor.stop();
}
if (this.isActive()) {
- return this.webSocket ? this.webSocket.close() : undefined;
+ return this.webSocket.close();
}
};
Connection.prototype.reopen = function reopen() {
@@ -211,7 +211,9 @@
}
};
Connection.prototype.getProtocol = function getProtocol() {
- return this.webSocket ? this.webSocket.protocol : undefined;
+ if (this.webSocket) {
+ return this.webSocket.protocol;
+ }
};
Connection.prototype.isOpen = function isOpen() {
return this.isState("open");
@@ -452,16 +454,15 @@
};
return Consumer;
}();
- function createConsumer(url) {
- if (url == null) {
- var urlConfig = getConfig("url");
- url = urlConfig ? urlConfig : INTERNAL.default_mount_path;
- }
+ function createConsumer() {
+ var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getConfig("url") || INTERNAL.default_mount_path;
return new Consumer(createWebSocketURL(url));
}
function getConfig(name) {
var element = document.head.querySelector("meta[name='action-cable-" + name + "']");
- return element ? element.getAttribute("content") : undefined;
+ if (element) {
+ return element.getAttribute("content");
+ }
}
function createWebSocketURL(url) {
if (url && !/^wss?:/i.test(url)) {
diff --git a/actioncable/app/javascript/action_cable/adapters.js b/actioncable/app/javascript/action_cable/adapters.js
index 9ba6d338ee..4de8131438 100644
--- a/actioncable/app/javascript/action_cable/adapters.js
+++ b/actioncable/app/javascript/action_cable/adapters.js
@@ -1,4 +1,4 @@
export default {
- logger: window.console,
- WebSocket: window.WebSocket
+ logger: self.console,
+ WebSocket: self.WebSocket
}
diff --git a/actioncable/app/javascript/action_cable/connection.js b/actioncable/app/javascript/action_cable/connection.js
index b2910cb2a6..96bac132c1 100644
--- a/actioncable/app/javascript/action_cable/connection.js
+++ b/actioncable/app/javascript/action_cable/connection.js
@@ -44,7 +44,9 @@ class Connection {
close({allowReconnect} = {allowReconnect: true}) {
if (!allowReconnect) { this.monitor.stop() }
- if (this.isActive()) { return (this.webSocket ? this.webSocket.close() : undefined) }
+ if (this.isActive()) {
+ return this.webSocket.close()
+ }
}
reopen() {
@@ -65,7 +67,9 @@ class Connection {
}
getProtocol() {
- return (this.webSocket ? this.webSocket.protocol : undefined)
+ if (this.webSocket) {
+ return this.webSocket.protocol
+ }
}
isOpen() {
diff --git a/actioncable/app/javascript/action_cable/connection_monitor.js b/actioncable/app/javascript/action_cable/connection_monitor.js
index f0e75ae137..312a71d154 100644
--- a/actioncable/app/javascript/action_cable/connection_monitor.js
+++ b/actioncable/app/javascript/action_cable/connection_monitor.js
@@ -21,7 +21,7 @@ class ConnectionMonitor {
this.startedAt = now()
delete this.stoppedAt
this.startPolling()
- document.addEventListener("visibilitychange", this.visibilityDidChange)
+ addEventListener("visibilitychange", this.visibilityDidChange)
logger.log(`ConnectionMonitor started. pollInterval = ${this.getPollInterval()} ms`)
}
}
@@ -30,7 +30,7 @@ class ConnectionMonitor {
if (this.isRunning()) {
this.stoppedAt = now()
this.stopPolling()
- document.removeEventListener("visibilitychange", this.visibilityDidChange)
+ removeEventListener("visibilitychange", this.visibilityDidChange)
logger.log("ConnectionMonitor stopped")
}
}
diff --git a/actioncable/app/javascript/action_cable/index.js b/actioncable/app/javascript/action_cable/index.js
index 9f41c14e94..659418396f 100644
--- a/actioncable/app/javascript/action_cable/index.js
+++ b/actioncable/app/javascript/action_cable/index.js
@@ -18,17 +18,15 @@ export {
logger,
}
-export function createConsumer(url) {
- if (url == null) {
- const urlConfig = getConfig("url")
- url = (urlConfig ? urlConfig : INTERNAL.default_mount_path)
- }
+export function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
return new Consumer(createWebSocketURL(url))
}
export function getConfig(name) {
const element = document.head.querySelector(`meta[name='action-cable-${name}']`)
- return (element ? element.getAttribute("content") : undefined)
+ if (element) {
+ return element.getAttribute("content")
+ }
}
export function createWebSocketURL(url) {
diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb
index cb9dfa2268..ad5fd43155 100644
--- a/actioncable/lib/action_cable.rb
+++ b/actioncable/lib/action_cable.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2015-2018 Basecamp, LLC
+# Copyright (c) 2015-2019 Basecamp, LLC
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actioncable/lib/action_cable/channel/broadcasting.rb b/actioncable/lib/action_cable/channel/broadcasting.rb
index 9a96720f4a..9f702e425e 100644
--- a/actioncable/lib/action_cable/channel/broadcasting.rb
+++ b/actioncable/lib/action_cable/channel/broadcasting.rb
@@ -7,22 +7,32 @@ module ActionCable
module Broadcasting
extend ActiveSupport::Concern
- delegate :broadcasting_for, to: :class
+ delegate :broadcasting_for, :broadcast_to, to: :class
module ClassMethods
# Broadcast a hash to a unique broadcasting for this <tt>model</tt> in this channel.
def broadcast_to(model, message)
- ActionCable.server.broadcast(broadcasting_for([ channel_name, model ]), message)
+ ActionCable.server.broadcast(broadcasting_for(model), message)
end
- def broadcasting_for(model) #:nodoc:
+ # Returns a unique broadcasting identifier for this <tt>model</tt> in this channel:
+ #
+ # CommentsChannel.broadcasting_for("all") # => "comments:all"
+ #
+ # You can pass any object as a target (e.g. Active Record model), and it
+ # would be serialized into a string under the hood.
+ def broadcasting_for(model)
+ serialize_broadcasting([ channel_name, model ])
+ end
+
+ def serialize_broadcasting(object) #:nodoc:
case
- when model.is_a?(Array)
- model.map { |m| broadcasting_for(m) }.join(":")
- when model.respond_to?(:to_gid_param)
- model.to_gid_param
+ when object.is_a?(Array)
+ object.map { |m| serialize_broadcasting(m) }.join(":")
+ when object.respond_to?(:to_gid_param)
+ object.to_gid_param
else
- model.to_param
+ object.to_param
end
end
end
diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb
index 81c2c38064..7e1ed3c850 100644
--- a/actioncable/lib/action_cable/channel/streams.rb
+++ b/actioncable/lib/action_cable/channel/streams.rb
@@ -99,7 +99,7 @@ module ActionCable
# Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
# Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
def stream_for(model, callback = nil, coder: nil, &block)
- stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder)
+ stream_from(broadcasting_for(model), callback || block, coder: coder)
end
# Unsubscribes all streams associated with this channel from the pubsub queue.
diff --git a/actioncable/lib/action_cable/channel/test_case.rb b/actioncable/lib/action_cable/channel/test_case.rb
index dd2762ccd0..b05d51a61a 100644
--- a/actioncable/lib/action_cable/channel/test_case.rb
+++ b/actioncable/lib/action_cable/channel/test_case.rb
@@ -84,7 +84,18 @@ module ActionCable
# assert subscription.confirmed?
#
# # Asserts that the channel subscribes connection to a stream
- # assert_equal "chat_1", streams.last
+ # assert_has_stream "chat_1"
+ #
+ # # Asserts that the channel subscribes connection to a specific
+ # # stream created for a model
+ # assert_has_stream_for Room.find(1)
+ # end
+ #
+ # def test_does_not_stream_with_incorrect_room_number
+ # subscribe room_number: -1
+ #
+ # # Asserts that not streams was started
+ # assert_no_streams
# end
#
# def test_does_not_subscribe_without_room_number
@@ -115,8 +126,6 @@ module ActionCable
# An instance of the current channel, created when you call `subscribe`.
# <b>transmissions</b>::
# A list of all messages that have been transmitted into the channel.
- # <b>streams</b>::
- # A list of all created streams subscriptions (as identifiers) for the subscription.
#
#
# == Channel is automatically inferred
@@ -134,7 +143,7 @@ module ActionCable
# You need to set up your connection manually to provide values for the identifiers.
# To do this just use:
#
- # stub_connection(user: users[:john])
+ # stub_connection(user: users(:john))
#
# == Testing broadcasting
#
@@ -148,9 +157,9 @@ module ActionCable
# end
#
# def test_speak
- # subscribe room_id: rooms[:chat].id
+ # subscribe room_id: rooms(:chat).id
#
- # assert_broadcasts_on(rooms[:chat], text: "Hello, Rails!") do
+ # assert_broadcasts_on(rooms(:chat), text: "Hello, Rails!") do
# perform :speak, message: "Hello, Rails!"
# end
# end
@@ -167,7 +176,6 @@ module ActionCable
class_attribute :_channel_class
attr_reader :connection, :subscription
- delegate :streams, to: :subscription
ActiveSupport.run_load_hooks(:action_cable_channel_test_case, self)
end
@@ -251,6 +259,39 @@ module ActionCable
super(broadcasting_for(stream_or_object), *args)
end
+ # Asserts that no streams have been started.
+ #
+ # def test_assert_no_started_stream
+ # subscribe
+ # assert_no_streams
+ # end
+ #
+ def assert_no_streams
+ assert subscription.streams.empty?, "No streams started was expected, but #{subscription.streams.count} found"
+ end
+
+ # Asserts that the specified stream has been started.
+ #
+ # def test_assert_started_stream
+ # subscribe
+ # assert_has_stream 'messages'
+ # end
+ #
+ def assert_has_stream(stream)
+ assert subscription.streams.include?(stream), "Stream #{stream} has not been started"
+ end
+
+ # Asserts that the specified stream for a model has started.
+ #
+ # def test_assert_started_stream_for
+ # subscribe id: 42
+ # assert_has_stream_for User.find(42)
+ # end
+ #
+ def assert_has_stream_for(object)
+ assert_has_stream(broadcasting_for(object))
+ end
+
private
def check_subscribed!
raise "Must be subscribed!" if subscription.nil? || subscription.rejected?
@@ -259,9 +300,7 @@ module ActionCable
def broadcasting_for(stream_or_object)
return stream_or_object if stream_or_object.is_a?(String)
- self.class.channel_class.broadcasting_for(
- [self.class.channel_class.channel_name, stream_or_object]
- )
+ self.class.channel_class.broadcasting_for(stream_or_object)
end
end
diff --git a/actioncable/lib/action_cable/connection.rb b/actioncable/lib/action_cable/connection.rb
index 804b89a707..20b5dbe78d 100644
--- a/actioncable/lib/action_cable/connection.rb
+++ b/actioncable/lib/action_cable/connection.rb
@@ -15,6 +15,7 @@ module ActionCable
autoload :StreamEventLoop
autoload :Subscriptions
autoload :TaggedLoggerProxy
+ autoload :TestCase
autoload :WebSocket
end
end
diff --git a/actioncable/lib/action_cable/connection/test_case.rb b/actioncable/lib/action_cable/connection/test_case.rb
new file mode 100644
index 0000000000..8d25a55c8a
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/test_case.rb
@@ -0,0 +1,234 @@
+# frozen_string_literal: true
+
+require "active_support"
+require "active_support/test_case"
+require "active_support/core_ext/hash/indifferent_access"
+require "action_dispatch"
+require "action_dispatch/http/headers"
+require "action_dispatch/testing/test_request"
+
+module ActionCable
+ module Connection
+ class NonInferrableConnectionError < ::StandardError
+ def initialize(name)
+ super "Unable to determine the connection to test from #{name}. " +
+ "You'll need to specify it using `tests YourConnection` in your " +
+ "test case definition."
+ end
+ end
+
+ module Assertions
+ # Asserts that the connection is rejected (via +reject_unauthorized_connection+).
+ #
+ # # Asserts that connection without user_id fails
+ # assert_reject_connection { connect params: { user_id: '' } }
+ def assert_reject_connection(&block)
+ assert_raises(Authorization::UnauthorizedError, "Expected to reject connection but no rejection was made", &block)
+ end
+ end
+
+ # We don't want to use the whole "encryption stack" for connection
+ # unit-tests, but we want to make sure that users test against the correct types
+ # of cookies (i.e. signed or encrypted or plain)
+ class TestCookieJar < ActiveSupport::HashWithIndifferentAccess
+ def signed
+ self[:signed] ||= {}.with_indifferent_access
+ end
+
+ def encrypted
+ self[:encrypted] ||= {}.with_indifferent_access
+ end
+ end
+
+ class TestRequest < ActionDispatch::TestRequest
+ attr_accessor :session, :cookie_jar
+ end
+
+ module TestConnection
+ attr_reader :logger, :request
+
+ def initialize(request)
+ inner_logger = ActiveSupport::Logger.new(StringIO.new)
+ tagged_logging = ActiveSupport::TaggedLogging.new(inner_logger)
+ @logger = ActionCable::Connection::TaggedLoggerProxy.new(tagged_logging, tags: [])
+ @request = request
+ @env = request.env
+ end
+ end
+
+ # Unit test Action Cable connections.
+ #
+ # Useful to check whether a connection's +identified_by+ gets assigned properly
+ # and that any improper connection requests are rejected.
+ #
+ # == Basic example
+ #
+ # Unit tests are written as follows:
+ #
+ # 1. Simulate a connection attempt by calling +connect+.
+ # 2. Assert state, e.g. identifiers, has been assigned.
+ #
+ #
+ # class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
+ # def test_connects_with_proper_cookie
+ # # Simulate the connection request with a cookie.
+ # cookies["user_id"] = users(:john).id
+ #
+ # connect
+ #
+ # # Assert the connection identifier matches the fixture.
+ # assert_equal users(:john).id, connection.user.id
+ # end
+ #
+ # def test_rejects_connection_without_proper_cookie
+ # assert_reject_connection { connect }
+ # end
+ # end
+ #
+ # +connect+ accepts additional information the HTTP request with the
+ # +params+, +headers+, +session+ and Rack +env+ options.
+ #
+ # def test_connect_with_headers_and_query_string
+ # connect params: { user_id: 1 }, headers: { "X-API-TOKEN" => "secret-my" }
+ #
+ # assert_equal "1", connection.user.id
+ # assert_equal "secret-my", connection.token
+ # end
+ #
+ # def test_connect_with_params
+ # connect params: { user_id: 1 }
+ #
+ # assert_equal "1", connection.user.id
+ # end
+ #
+ # You can also setup the correct cookies before the connection request:
+ #
+ # def test_connect_with_cookies
+ # # Plain cookies:
+ # cookies["user_id"] = 1
+ #
+ # # Or signed/encrypted:
+ # # cookies.signed["user_id"] = 1
+ # # cookies.encrypted["user_id"] = 1
+ #
+ # connect
+ #
+ # assert_equal "1", connection.user_id
+ # end
+ #
+ # == Connection is automatically inferred
+ #
+ # ActionCable::Connection::TestCase will automatically infer the connection under test
+ # from the test class name. If the channel cannot be inferred from the test
+ # class name, you can explicitly set it with +tests+.
+ #
+ # class ConnectionTest < ActionCable::Connection::TestCase
+ # tests ApplicationCable::Connection
+ # end
+ #
+ class TestCase < ActiveSupport::TestCase
+ module Behavior
+ extend ActiveSupport::Concern
+
+ DEFAULT_PATH = "/cable"
+
+ include ActiveSupport::Testing::ConstantLookup
+ include Assertions
+
+ included do
+ class_attribute :_connection_class
+
+ attr_reader :connection
+
+ ActiveSupport.run_load_hooks(:action_cable_connection_test_case, self)
+ end
+
+ module ClassMethods
+ def tests(connection)
+ case connection
+ when String, Symbol
+ self._connection_class = connection.to_s.camelize.constantize
+ when Module
+ self._connection_class = connection
+ else
+ raise NonInferrableConnectionError.new(connection)
+ end
+ end
+
+ def connection_class
+ if connection = self._connection_class
+ connection
+ else
+ tests determine_default_connection(name)
+ end
+ end
+
+ def determine_default_connection(name)
+ connection = determine_constant_from_test_name(name) do |constant|
+ Class === constant && constant < ActionCable::Connection::Base
+ end
+ raise NonInferrableConnectionError.new(name) if connection.nil?
+ connection
+ end
+ end
+
+ # Performs connection attempt to exert #connect on the connection under test.
+ #
+ # Accepts request path as the first argument and the following request options:
+ #
+ # - params – url parameters (Hash)
+ # - headers – request headers (Hash)
+ # - session – session data (Hash)
+ # - env – additional Rack env configuration (Hash)
+ def connect(path = ActionCable.server.config.mount_path, **request_params)
+ path ||= DEFAULT_PATH
+
+ connection = self.class.connection_class.allocate
+ connection.singleton_class.include(TestConnection)
+ connection.send(:initialize, build_test_request(path, request_params))
+ connection.connect if connection.respond_to?(:connect)
+
+ # Only set instance variable if connected successfully
+ @connection = connection
+ end
+
+ # Exert #disconnect on the connection under test.
+ def disconnect
+ raise "Must be connected!" if connection.nil?
+
+ connection.disconnect if connection.respond_to?(:disconnect)
+ @connection = nil
+ end
+
+ def cookies
+ @cookie_jar ||= TestCookieJar.new
+ end
+
+ private
+ def build_test_request(path, params: nil, headers: {}, session: {}, env: {})
+ wrapped_headers = ActionDispatch::Http::Headers.from_hash(headers)
+
+ uri = URI.parse(path)
+
+ query_string = params.nil? ? uri.query : params.to_query
+
+ request_env = {
+ "QUERY_STRING" => query_string,
+ "PATH_INFO" => uri.path
+ }.merge(env)
+
+ if wrapped_headers.present?
+ ActionDispatch::Http::Headers.from_hash(request_env).merge!(wrapped_headers)
+ end
+
+ TestRequest.create(request_env).tap do |request|
+ request.session = session.with_indifferent_access
+ request.cookie_jar = cookies
+ end
+ end
+ end
+
+ include Behavior
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb
index cd1d9bccef..5082827417 100644
--- a/actioncable/lib/action_cable/gem_version.rb
+++ b/actioncable/lib/action_cable/gem_version.rb
@@ -10,7 +10,7 @@ module ActionCable
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actioncable/lib/action_cable/test_helper.rb b/actioncable/lib/action_cable/test_helper.rb
index 7bc877663c..26b849a273 100644
--- a/actioncable/lib/action_cable/test_helper.rb
+++ b/actioncable/lib/action_cable/test_helper.rb
@@ -81,7 +81,7 @@ module ActionCable
# Asserts that the specified message has been sent to the stream.
#
- # def test_assert_transmited_message
+ # def test_assert_transmitted_message
# ActionCable.server.broadcast 'messages', text: 'hello'
# assert_broadcast_on('messages', text: 'hello')
# end
diff --git a/actioncable/lib/rails/generators/channel/USAGE b/actioncable/lib/rails/generators/channel/USAGE
index ea9662436c..bb5dd7e2db 100644
--- a/actioncable/lib/rails/generators/channel/USAGE
+++ b/actioncable/lib/rails/generators/channel/USAGE
@@ -7,6 +7,7 @@ Example:
========
rails generate channel Chat speak
- creates a Chat channel class and JavaScript asset:
+ creates a Chat channel class, test and JavaScript asset:
Channel: app/channels/chat_channel.rb
+ Test: test/channels/chat_channel_test.rb
Assets: app/javascript/channels/chat_channel.js
diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb
index ef51981e89..0b80d1f96b 100644
--- a/actioncable/lib/rails/generators/channel/channel_generator.rb
+++ b/actioncable/lib/rails/generators/channel/channel_generator.rb
@@ -11,6 +11,8 @@ module Rails
check_class_collision suffix: "Channel"
+ hook_for :test_framework
+
def create_channel_file
template "channel.rb", File.join("app/channels", class_path, "#{file_name}_channel.rb")
diff --git a/actioncable/lib/rails/generators/channel/templates/javascript/consumer.js.tt b/actioncable/lib/rails/generators/channel/templates/javascript/consumer.js.tt
index 76ca3d0f2f..eec7e54b8a 100644
--- a/actioncable/lib/rails/generators/channel/templates/javascript/consumer.js.tt
+++ b/actioncable/lib/rails/generators/channel/templates/javascript/consumer.js.tt
@@ -1,6 +1,6 @@
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
-import ActionCable from "actioncable"
+import ActionCable from "@rails/actioncable"
export default ActionCable.createConsumer()
diff --git a/actioncable/lib/rails/generators/test_unit/channel_generator.rb b/actioncable/lib/rails/generators/test_unit/channel_generator.rb
new file mode 100644
index 0000000000..7d13a12f0a
--- /dev/null
+++ b/actioncable/lib/rails/generators/test_unit/channel_generator.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module TestUnit
+ module Generators
+ class ChannelGenerator < ::Rails::Generators::NamedBase
+ source_root File.expand_path("templates", __dir__)
+
+ check_class_collision suffix: "ChannelTest"
+
+ def create_test_files
+ template "channel_test.rb", File.join("test/channels", class_path, "#{file_name}_channel_test.rb")
+ end
+
+ private
+ def file_name # :doc:
+ @_file_name ||= super.sub(/_channel\z/i, "")
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/rails/generators/test_unit/templates/channel_test.rb.tt b/actioncable/lib/rails/generators/test_unit/templates/channel_test.rb.tt
new file mode 100644
index 0000000000..7307654611
--- /dev/null
+++ b/actioncable/lib/rails/generators/test_unit/templates/channel_test.rb.tt
@@ -0,0 +1,8 @@
+require "test_helper"
+
+class <%= class_name %>ChannelTest < ActionCable::Channel::TestCase
+ # test "subscribes" do
+ # subscribe
+ # assert subscription.confirmed?
+ # end
+end
diff --git a/actioncable/package.json b/actioncable/package.json
index db78c1a09a..801fcc0c22 100644
--- a/actioncable/package.json
+++ b/actioncable/package.json
@@ -1,6 +1,6 @@
{
- "name": "actioncable",
- "version": "6.0.0-alpha",
+ "name": "@rails/actioncable",
+ "version": "6.0.0-beta1",
"description": "WebSocket framework for Ruby on Rails.",
"main": "app/assets/javascripts/action_cable.js",
"files": [
diff --git a/actioncable/test/channel/broadcasting_test.rb b/actioncable/test/channel/broadcasting_test.rb
index 2cbfabc1d0..fb501a1bc2 100644
--- a/actioncable/test/channel/broadcasting_test.rb
+++ b/actioncable/test/channel/broadcasting_test.rb
@@ -26,14 +26,23 @@ class ActionCable::Channel::BroadcastingTest < ActionCable::TestCase
end
test "broadcasting_for with an object" do
- assert_equal "Room#1-Campfire", ChatChannel.broadcasting_for(Room.new(1))
+ assert_equal(
+ "action_cable:channel:broadcasting_test:chat:Room#1-Campfire",
+ ChatChannel.broadcasting_for(Room.new(1))
+ )
end
test "broadcasting_for with an array" do
- assert_equal "Room#1-Campfire:Room#2-Campfire", ChatChannel.broadcasting_for([ Room.new(1), Room.new(2) ])
+ assert_equal(
+ "action_cable:channel:broadcasting_test:chat:Room#1-Campfire:Room#2-Campfire",
+ ChatChannel.broadcasting_for([ Room.new(1), Room.new(2) ])
+ )
end
test "broadcasting_for with a string" do
- assert_equal "hello", ChatChannel.broadcasting_for("hello")
+ assert_equal(
+ "action_cable:channel:broadcasting_test:chat:hello",
+ ChatChannel.broadcasting_for("hello")
+ )
end
end
diff --git a/actioncable/test/channel/test_case_test.rb b/actioncable/test/channel/test_case_test.rb
index 63d0d6207e..a166c41e11 100644
--- a/actioncable/test/channel/test_case_test.rb
+++ b/actioncable/test/channel/test_case_test.rb
@@ -93,13 +93,39 @@ class StreamsTestChannelTest < ActionCable::Channel::TestCase
def test_stream_without_params
subscribe
- assert_equal "test_0", streams.last
+ assert_has_stream "test_0"
end
def test_stream_with_params
subscribe id: 42
- assert_equal "test_42", streams.last
+ assert_has_stream "test_42"
+ end
+end
+
+class StreamsForTestChannel < ActionCable::Channel::Base
+ def subscribed
+ stream_for User.new(params[:id])
+ end
+end
+
+class StreamsForTestChannelTest < ActionCable::Channel::TestCase
+ def test_stream_with_params
+ subscribe id: 42
+
+ assert_has_stream_for User.new(42)
+ end
+end
+
+class NoStreamsTestChannel < ActionCable::Channel::Base
+ def subscribed; end # no-op
+end
+
+class NoStreamsTestChannelTest < ActionCable::Channel::TestCase
+ def test_stream_with_params
+ subscribe
+
+ assert_no_streams
end
end
@@ -154,7 +180,7 @@ class BroadcastsTestChannel < ActionCable::Channel::Base
def broadcast_to_user(data)
user = User.new user_id
- self.class.broadcast_to user, text: data["message"]
+ broadcast_to user, text: data["message"]
end
end
diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb
index e5f43488c4..bf141df458 100644
--- a/actioncable/test/client_test.rb
+++ b/actioncable/test/client_test.rb
@@ -140,7 +140,7 @@ class ClientTest < ActionCable::TestCase
end
end
- ws.on(:close) do |event|
+ ws.on(:close) do |_|
closed.set
end
end
diff --git a/actioncable/test/connection/test_case_test.rb b/actioncable/test/connection/test_case_test.rb
new file mode 100644
index 0000000000..3b19465d7b
--- /dev/null
+++ b/actioncable/test/connection/test_case_test.rb
@@ -0,0 +1,197 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class SimpleConnection < ActionCable::Connection::Base
+ identified_by :user_id
+
+ class << self
+ attr_accessor :disconnected_user_id
+ end
+
+ def connect
+ self.user_id = request.params[:user_id] || cookies[:user_id]
+ end
+
+ def disconnect
+ self.class.disconnected_user_id = user_id
+ end
+end
+
+class ConnectionSimpleTest < ActionCable::Connection::TestCase
+ tests SimpleConnection
+
+ def test_connected
+ connect
+
+ assert_nil connection.user_id
+ end
+
+ def test_url_params
+ connect "/cable?user_id=323"
+
+ assert_equal "323", connection.user_id
+ end
+
+ def test_params
+ connect params: { user_id: 323 }
+
+ assert_equal "323", connection.user_id
+ end
+
+ def test_plain_cookie
+ cookies["user_id"] = "456"
+
+ connect
+
+ assert_equal "456", connection.user_id
+ end
+
+ def test_disconnect
+ cookies["user_id"] = "456"
+
+ connect
+
+ assert_equal "456", connection.user_id
+
+ disconnect
+
+ assert_equal "456", SimpleConnection.disconnected_user_id
+ end
+end
+
+class Connection < ActionCable::Connection::Base
+ identified_by :current_user_id
+ identified_by :token
+
+ class << self
+ attr_accessor :disconnected_user_id
+ end
+
+ def connect
+ self.current_user_id = verify_user
+ self.token = request.headers["X-API-TOKEN"]
+ logger.add_tags("ActionCable")
+ end
+
+ private
+ def verify_user
+ cookies.signed[:user_id].presence || reject_unauthorized_connection
+ end
+end
+
+class ConnectionTest < ActionCable::Connection::TestCase
+ def test_connected_with_signed_cookies_and_headers
+ cookies.signed["user_id"] = "456"
+
+ connect headers: { "X-API-TOKEN" => "abc" }
+
+ assert_equal "abc", connection.token
+ assert_equal "456", connection.current_user_id
+ end
+
+ def test_connected_when_no_signed_cookies_set
+ cookies["user_id"] = "456"
+
+ assert_reject_connection { connect }
+ end
+
+ def test_connection_rejected
+ assert_reject_connection { connect }
+ end
+
+ def test_connection_rejected_assertion_message
+ error = assert_raises Minitest::Assertion do
+ assert_reject_connection { "Intentionally doesn't connect." }
+ end
+
+ assert_match(/Expected to reject connection/, error.message)
+ end
+end
+
+class EncryptedCookiesConnection < ActionCable::Connection::Base
+ identified_by :user_id
+
+ def connect
+ self.user_id = verify_user
+ end
+
+ private
+ def verify_user
+ cookies.encrypted[:user_id].presence || reject_unauthorized_connection
+ end
+end
+
+class EncryptedCookiesConnectionTest < ActionCable::Connection::TestCase
+ tests EncryptedCookiesConnection
+
+ def test_connected_with_encrypted_cookies
+ cookies.encrypted["user_id"] = "456"
+
+ connect
+
+ assert_equal "456", connection.user_id
+ end
+
+ def test_connection_rejected
+ assert_reject_connection { connect }
+ end
+end
+
+class SessionConnection < ActionCable::Connection::Base
+ identified_by :user_id
+
+ def connect
+ self.user_id = verify_user
+ end
+
+ private
+ def verify_user
+ request.session[:user_id].presence || reject_unauthorized_connection
+ end
+end
+
+class SessionConnectionTest < ActionCable::Connection::TestCase
+ tests SessionConnection
+
+ def test_connected_with_encrypted_cookies
+ connect session: { user_id: "789" }
+ assert_equal "789", connection.user_id
+ end
+
+ def test_connection_rejected
+ assert_reject_connection { connect }
+ end
+end
+
+class EnvConnection < ActionCable::Connection::Base
+ identified_by :user
+
+ def connect
+ self.user = verify_user
+ end
+
+ private
+ def verify_user
+ # Warden-like authentication
+ env["authenticator"]&.user || reject_unauthorized_connection
+ end
+end
+
+class EnvConnectionTest < ActionCable::Connection::TestCase
+ tests EnvConnection
+
+ def test_connected_with_env
+ authenticator = Class.new do
+ def user; "David"; end
+ end
+
+ connect env: { "authenticator" => authenticator.new }
+
+ assert_equal "David", connection.user
+ end
+
+ def test_connection_rejected
+ assert_reject_connection { connect }
+ end
+end
diff --git a/actioncable/test/javascript/src/unit/action_cable_test.js b/actioncable/test/javascript/src/unit/action_cable_test.js
index daad900aca..83426fa32e 100644
--- a/actioncable/test/javascript/src/unit/action_cable_test.js
+++ b/actioncable/test/javascript/src/unit/action_cable_test.js
@@ -6,14 +6,14 @@ const {module, test} = QUnit
module("ActionCable", () => {
module("Adapters", () => {
module("WebSocket", () => {
- test("default is window.WebSocket", assert => {
- assert.equal(ActionCable.adapters.WebSocket, window.WebSocket)
+ test("default is self.WebSocket", assert => {
+ assert.equal(ActionCable.adapters.WebSocket, self.WebSocket)
})
})
module("logger", () => {
- test("default is window.console", assert => {
- assert.equal(ActionCable.adapters.logger, window.console)
+ test("default is self.console", assert => {
+ assert.equal(ActionCable.adapters.logger, self.console)
})
})
})
diff --git a/actioncable/test/test_helper_test.rb b/actioncable/test/test_helper_test.rb
index 90e3dbf01f..02eaefc4f8 100644
--- a/actioncable/test/test_helper_test.rb
+++ b/actioncable/test/test_helper_test.rb
@@ -74,7 +74,7 @@ class TransmissionsTest < ActionCable::TestCase
end
end
-class TransmitedDataTest < ActionCable::TestCase
+class TransmittedDataTest < ActionCable::TestCase
include ActionCable::TestHelper
def test_assert_broadcast_on
diff --git a/actionmailbox/.gitignore b/actionmailbox/.gitignore
new file mode 100644
index 0000000000..739e00a2cb
--- /dev/null
+++ b/actionmailbox/.gitignore
@@ -0,0 +1,5 @@
+/test/dummy/db/*.sqlite3
+/test/dummy/db/*.sqlite3-journal
+/test/dummy/log/*.log
+/test/dummy/tmp/
+/tmp/
diff --git a/actionmailbox/CHANGELOG.md b/actionmailbox/CHANGELOG.md
new file mode 100644
index 0000000000..d92367dc17
--- /dev/null
+++ b/actionmailbox/CHANGELOG.md
@@ -0,0 +1,5 @@
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Added to Rails.
+
+ *DHH*
diff --git a/actionmailbox/MIT-LICENSE b/actionmailbox/MIT-LICENSE
new file mode 100644
index 0000000000..e28efa78e1
--- /dev/null
+++ b/actionmailbox/MIT-LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Basecamp, LLC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/actionmailbox/README.md b/actionmailbox/README.md
new file mode 100644
index 0000000000..9a47223d3b
--- /dev/null
+++ b/actionmailbox/README.md
@@ -0,0 +1,13 @@
+# Action Mailbox
+
+Action Mailbox routes incoming emails to controller-like mailboxes for processing in Rails. It ships with ingresses for Amazon SES, Mailgun, Mandrill, Postmark, and SendGrid. You can also handle inbound mails directly via the built-in Exim, Postfix, and Qmail ingresses.
+
+The inbound emails are turned into `InboundEmail` records using Active Record and feature lifecycle tracking, storage of the original email on cloud storage via Active Storage, and responsible data handling with on-by-default incineration.
+
+These inbound emails are routed asynchronously using Active Job to one or several dedicated mailboxes, which are capable of interacting directly with the rest of your domain model.
+
+You can read more about Action Mailbox in the [Action Mailbox Basics](https://edgeguides.rubyonrails.org/action_mailbox_basics.html) guide.
+
+## License
+
+Action Mailbox is released under the [MIT License](https://opensource.org/licenses/MIT).
diff --git a/actionmailbox/Rakefile b/actionmailbox/Rakefile
new file mode 100644
index 0000000000..36aed17282
--- /dev/null
+++ b/actionmailbox/Rakefile
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require "bundler/setup"
+require "bundler/gem_tasks"
+require "rake/testtask"
+
+task :package
+
+Rake::TestTask.new do |t|
+ t.libs << "test"
+ t.pattern = "test/**/*_test.rb"
+ t.verbose = true
+end
+
+task default: :test
diff --git a/actionmailbox/actionmailbox.gemspec b/actionmailbox/actionmailbox.gemspec
new file mode 100644
index 0000000000..c38857f6d7
--- /dev/null
+++ b/actionmailbox/actionmailbox.gemspec
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
+
+Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = "actionmailbox"
+ s.version = version
+ s.summary = "Inbound email handling framework."
+ s.description = "Receive and process incoming emails in Rails applications."
+
+ s.required_ruby_version = ">= 2.5.0"
+
+ s.license = "MIT"
+
+ s.authors = ["David Heinemeier Hansson", "George Claghorn"]
+ s.email = ["david@loudthinking.com", "george@basecamp.com"]
+ s.homepage = "https://rubyonrails.org"
+
+ s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*", "app/**/*", "config/**/*", "db/**/*"]
+ s.require_path = "lib"
+
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/actionmailbox",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actionmailbox/CHANGELOG.md"
+ }
+
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
+ s.add_dependency "activesupport", version
+ s.add_dependency "activerecord", version
+ s.add_dependency "activestorage", version
+ s.add_dependency "activejob", version
+ s.add_dependency "actionpack", version
+
+ s.add_dependency "mail", ">= 2.7.1"
+end
diff --git a/actionmailbox/app/controllers/action_mailbox/base_controller.rb b/actionmailbox/app/controllers/action_mailbox/base_controller.rb
new file mode 100644
index 0000000000..f0f1f555e6
--- /dev/null
+++ b/actionmailbox/app/controllers/action_mailbox/base_controller.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # The base class for all Action Mailbox ingress controllers.
+ class BaseController < ActionController::Base
+ skip_forgery_protection
+
+ before_action :ensure_configured
+
+ def self.prepare
+ # Override in concrete controllers to run code on load.
+ end
+
+ private
+ def ensure_configured
+ unless ActionMailbox.ingress == ingress_name
+ head :not_found
+ end
+ end
+
+ def ingress_name
+ self.class.name.remove(/\AActionMailbox::Ingresses::/, /::InboundEmailsController\z/).underscore.to_sym
+ end
+
+
+ def authenticate_by_password
+ if password.present?
+ http_basic_authenticate_or_request_with name: "actionmailbox", password: password, realm: "Action Mailbox"
+ else
+ raise ArgumentError, "Missing required ingress credentials"
+ end
+ end
+
+ def password
+ Rails.application.credentials.dig(:action_mailbox, :ingress_password) || ENV["RAILS_INBOUND_EMAIL_PASSWORD"]
+ end
+ end
+end
diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb
new file mode 100644
index 0000000000..e0a187054e
--- /dev/null
+++ b/actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # Ingests inbound emails from Amazon's Simple Email Service (SES).
+ #
+ # Requires the full RFC 822 message in the +content+ parameter. Authenticates requests by validating their signatures.
+ #
+ # Returns:
+ #
+ # - <tt>204 No Content</tt> if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox
+ # - <tt>401 Unauthorized</tt> if the request's signature could not be validated
+ # - <tt>404 Not Found</tt> if Action Mailbox is not configured to accept inbound emails from SES
+ # - <tt>422 Unprocessable Entity</tt> if the request is missing the required +content+ parameter
+ # - <tt>500 Server Error</tt> if one of the Active Record database, the Active Storage service, or
+ # the Active Job backend is misconfigured or unavailable
+ #
+ # == Usage
+ #
+ # 1. Install the {aws-sdk-sns}[https://rubygems.org/gems/aws-sdk-sns] gem:
+ #
+ # # Gemfile
+ # gem "aws-sdk-sns", ">= 1.9.0", require: false
+ #
+ # 2. Tell Action Mailbox to accept emails from SES:
+ #
+ # # config/environments/production.rb
+ # config.action_mailbox.ingress = :amazon
+ #
+ # 3. {Configure SES}[https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-notifications.html]
+ # to deliver emails to your application via POST requests to +/rails/action_mailbox/amazon/inbound_emails+.
+ # If your application lived at <tt>https://example.com</tt>, you would specify the fully-qualified URL
+ # <tt>https://example.com/rails/action_mailbox/amazon/inbound_emails</tt>.
+ class Ingresses::Amazon::InboundEmailsController < BaseController
+ before_action :authenticate
+
+ cattr_accessor :verifier
+
+ def self.prepare
+ self.verifier ||= begin
+ require "aws-sdk-sns"
+ Aws::SNS::MessageVerifier.new
+ end
+ end
+
+ def create
+ ActionMailbox::InboundEmail.create_and_extract_message_id! params.require(:content)
+ end
+
+ private
+ def authenticate
+ head :unauthorized unless verifier.authentic?(request.body)
+ end
+ end
+end
diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb
new file mode 100644
index 0000000000..bf0fd562fe
--- /dev/null
+++ b/actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # Ingests inbound emails from Mailgun. Requires the following parameters:
+ #
+ # - +body-mime+: The full RFC 822 message
+ # - +timestamp+: The current time according to Mailgun as the number of seconds passed since the UNIX epoch
+ # - +token+: A randomly-generated, 50-character string
+ # - +signature+: A hexadecimal HMAC-SHA256 of the timestamp concatenated with the token, generated using the Mailgun API key
+ #
+ # Authenticates requests by validating their signatures.
+ #
+ # Returns:
+ #
+ # - <tt>204 No Content</tt> if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox
+ # - <tt>401 Unauthorized</tt> if the request's signature could not be validated, or if its timestamp is more than 2 minutes old
+ # - <tt>404 Not Found</tt> if Action Mailbox is not configured to accept inbound emails from Mailgun
+ # - <tt>422 Unprocessable Entity</tt> if the request is missing required parameters
+ # - <tt>500 Server Error</tt> if the Mailgun API key is missing, or one of the Active Record database,
+ # the Active Storage service, or the Active Job backend is misconfigured or unavailable
+ #
+ # == Usage
+ #
+ # 1. Give Action Mailbox your {Mailgun API key}[https://help.mailgun.com/hc/en-us/articles/203380100-Where-can-I-find-my-API-key-and-SMTP-credentials-]
+ # so it can authenticate requests to the Mailgun ingress.
+ #
+ # Use <tt>rails credentials:edit</tt> to add your API key to your application's encrypted credentials under
+ # +action_mailbox.mailgun_api_key+, where Action Mailbox will automatically find it:
+ #
+ # action_mailbox:
+ # mailgun_api_key: ...
+ #
+ # Alternatively, provide your API key in the +MAILGUN_INGRESS_API_KEY+ environment variable.
+ #
+ # 2. Tell Action Mailbox to accept emails from Mailgun:
+ #
+ # # config/environments/production.rb
+ # config.action_mailbox.ingress = :mailgun
+ #
+ # 3. {Configure Mailgun}[https://documentation.mailgun.com/en/latest/user_manual.html#receiving-forwarding-and-storing-messages]
+ # to forward inbound emails to +/rails/action_mailbox/mailgun/inbound_emails/mime+.
+ #
+ # If your application lived at <tt>https://example.com</tt>, you would specify the fully-qualified URL
+ # <tt>https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime</tt>.
+ class Ingresses::Mailgun::InboundEmailsController < ActionMailbox::BaseController
+ before_action :authenticate
+
+ def create
+ ActionMailbox::InboundEmail.create_and_extract_message_id! params.require("body-mime")
+ end
+
+ private
+ def authenticate
+ head :unauthorized unless authenticated?
+ end
+
+ def authenticated?
+ if key.present?
+ Authenticator.new(
+ key: key,
+ timestamp: params.require(:timestamp),
+ token: params.require(:token),
+ signature: params.require(:signature)
+ ).authenticated?
+ else
+ raise ArgumentError, <<~MESSAGE.squish
+ Missing required Mailgun API key. Set action_mailbox.mailgun_api_key in your application's
+ encrypted credentials or provide the MAILGUN_INGRESS_API_KEY environment variable.
+ MESSAGE
+ end
+ end
+
+ def key
+ Rails.application.credentials.dig(:action_mailbox, :mailgun_api_key) || ENV["MAILGUN_INGRESS_API_KEY"]
+ end
+
+ class Authenticator
+ attr_reader :key, :timestamp, :token, :signature
+
+ def initialize(key:, timestamp:, token:, signature:)
+ @key, @timestamp, @token, @signature = key, Integer(timestamp), token, signature
+ end
+
+ def authenticated?
+ signed? && recent?
+ end
+
+ private
+ def signed?
+ ActiveSupport::SecurityUtils.secure_compare signature, expected_signature
+ end
+
+ # Allow for 2 minutes of drift between Mailgun time and local server time.
+ def recent?
+ Time.at(timestamp) >= 2.minutes.ago
+ end
+
+ def expected_signature
+ OpenSSL::HMAC.hexdigest OpenSSL::Digest::SHA256.new, key, "#{timestamp}#{token}"
+ end
+ end
+ end
+end
diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/mandrill/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/mandrill/inbound_emails_controller.rb
new file mode 100644
index 0000000000..2a3b1619b9
--- /dev/null
+++ b/actionmailbox/app/controllers/action_mailbox/ingresses/mandrill/inbound_emails_controller.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # Ingests inbound emails from Mandrill.
+ #
+ # Requires a +mandrill_events+ parameter containing a JSON array of Mandrill inbound email event objects.
+ # Each event is expected to have a +msg+ object containing a full RFC 822 message in its +raw_msg+ property.
+ #
+ # Returns:
+ #
+ # - <tt>204 No Content</tt> if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox
+ # - <tt>401 Unauthorized</tt> if the request's signature could not be validated
+ # - <tt>404 Not Found</tt> if Action Mailbox is not configured to accept inbound emails from Mandrill
+ # - <tt>422 Unprocessable Entity</tt> if the request is missing required parameters
+ # - <tt>500 Server Error</tt> if the Mandrill API key is missing, or one of the Active Record database,
+ # the Active Storage service, or the Active Job backend is misconfigured or unavailable
+ class Ingresses::Mandrill::InboundEmailsController < ActionMailbox::BaseController
+ before_action :authenticate
+
+ def create
+ raw_emails.each { |raw_email| ActionMailbox::InboundEmail.create_and_extract_message_id! raw_email }
+ head :ok
+ rescue JSON::ParserError => error
+ logger.error error.message
+ head :unprocessable_entity
+ end
+
+ private
+ def raw_emails
+ events.select { |event| event["event"] == "inbound" }.collect { |event| event.dig("msg", "raw_msg") }
+ end
+
+ def events
+ JSON.parse params.require(:mandrill_events)
+ end
+
+
+ def authenticate
+ head :unauthorized unless authenticated?
+ end
+
+ def authenticated?
+ if key.present?
+ Authenticator.new(request, key).authenticated?
+ else
+ raise ArgumentError, <<~MESSAGE.squish
+ Missing required Mandrill API key. Set action_mailbox.mandrill_api_key in your application's
+ encrypted credentials or provide the MANDRILL_INGRESS_API_KEY environment variable.
+ MESSAGE
+ end
+ end
+
+ def key
+ Rails.application.credentials.dig(:action_mailbox, :mandrill_api_key) || ENV["MANDRILL_INGRESS_API_KEY"]
+ end
+
+ class Authenticator
+ attr_reader :request, :key
+
+ def initialize(request, key)
+ @request, @key = request, key
+ end
+
+ def authenticated?
+ ActiveSupport::SecurityUtils.secure_compare given_signature, expected_signature
+ end
+
+ private
+ def given_signature
+ request.headers["X-Mandrill-Signature"]
+ end
+
+ def expected_signature
+ Base64.strict_encode64 OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, key, message)
+ end
+
+ def message
+ request.url + request.POST.sort.flatten.join
+ end
+ end
+ end
+end
diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb
new file mode 100644
index 0000000000..309085c82a
--- /dev/null
+++ b/actionmailbox/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # Ingests inbound emails from Postmark. Requires a +RawEmail+ parameter containing a full RFC 822 message.
+ #
+ # Authenticates requests using HTTP basic access authentication. The username is always +actionmailbox+, and the
+ # password is read from the application's encrypted credentials or an environment variable. See the Usage section below.
+ #
+ # Note that basic authentication is insecure over unencrypted HTTP. An attacker that intercepts cleartext requests to
+ # the Postmark ingress can learn its password. You should only use the Postmark ingress over HTTPS.
+ #
+ # Returns:
+ #
+ # - <tt>204 No Content</tt> if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox
+ # - <tt>401 Unauthorized</tt> if the request's signature could not be validated
+ # - <tt>404 Not Found</tt> if Action Mailbox is not configured to accept inbound emails from Postmark
+ # - <tt>422 Unprocessable Entity</tt> if the request is missing the required +RawEmail+ parameter
+ # - <tt>500 Server Error</tt> if the ingress password is not configured, or if one of the Active Record database,
+ # the Active Storage service, or the Active Job backend is misconfigured or unavailable
+ #
+ # == Usage
+ #
+ # 1. Tell Action Mailbox to accept emails from Postmark:
+ #
+ # # config/environments/production.rb
+ # config.action_mailbox.ingress = :postmark
+ #
+ # 2. Generate a strong password that Action Mailbox can use to authenticate requests to the Postmark ingress.
+ #
+ # Use <tt>rails credentials:edit</tt> to add the password to your application's encrypted credentials under
+ # +action_mailbox.ingress_password+, where Action Mailbox will automatically find it:
+ #
+ # action_mailbox:
+ # ingress_password: ...
+ #
+ # Alternatively, provide the password in the +RAILS_INBOUND_EMAIL_PASSWORD+ environment variable.
+ #
+ # 3. {Configure Postmark}[https://postmarkapp.com/manual#configure-your-inbound-webhook-url] to forward inbound emails
+ # to +/rails/action_mailbox/postmark/inbound_emails+ with the username +actionmailbox+ and the password you
+ # previously generated. If your application lived at <tt>https://example.com</tt>, you would configure your
+ # Postmark inbound webhook with the following fully-qualified URL:
+ #
+ # https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails
+ #
+ # *NOTE:* When configuring your Postmark inbound webhook, be sure to check the box labeled *"Include raw email
+ # content in JSON payload"*. Action Mailbox needs the raw email content to work.
+ class Ingresses::Postmark::InboundEmailsController < ActionMailbox::BaseController
+ before_action :authenticate_by_password
+
+ def create
+ ActionMailbox::InboundEmail.create_and_extract_message_id! params.require("RawEmail")
+ rescue ActionController::ParameterMissing => error
+ logger.error <<~MESSAGE
+ #{error.message}
+
+ When configuring your Postmark inbound webhook, be sure to check the box
+ labeled "Include raw email content in JSON payload".
+ MESSAGE
+ head :unprocessable_entity
+ end
+ end
+end
diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/relay/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/relay/inbound_emails_controller.rb
new file mode 100644
index 0000000000..2d91c968c8
--- /dev/null
+++ b/actionmailbox/app/controllers/action_mailbox/ingresses/relay/inbound_emails_controller.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # Ingests inbound emails relayed from an SMTP server.
+ #
+ # Authenticates requests using HTTP basic access authentication. The username is always +actionmailbox+, and the
+ # password is read from the application's encrypted credentials or an environment variable. See the Usage section below.
+ #
+ # Note that basic authentication is insecure over unencrypted HTTP. An attacker that intercepts cleartext requests to
+ # the ingress can learn its password. You should only use this ingress over HTTPS.
+ #
+ # Returns:
+ #
+ # - <tt>204 No Content</tt> if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox
+ # - <tt>401 Unauthorized</tt> if the request could not be authenticated
+ # - <tt>404 Not Found</tt> if Action Mailbox is not configured to accept inbound emails relayed from an SMTP server
+ # - <tt>415 Unsupported Media Type</tt> if the request does not contain an RFC 822 message
+ # - <tt>500 Server Error</tt> if the ingress password is not configured, or if one of the Active Record database,
+ # the Active Storage service, or the Active Job backend is misconfigured or unavailable
+ #
+ # == Usage
+ #
+ # 1. Tell Action Mailbox to accept emails from an SMTP relay:
+ #
+ # # config/environments/production.rb
+ # config.action_mailbox.ingress = :relay
+ #
+ # 2. Generate a strong password that Action Mailbox can use to authenticate requests to the ingress.
+ #
+ # Use <tt>rails credentials:edit</tt> to add the password to your application's encrypted credentials under
+ # +action_mailbox.ingress_password+, where Action Mailbox will automatically find it:
+ #
+ # action_mailbox:
+ # ingress_password: ...
+ #
+ # Alternatively, provide the password in the +RAILS_INBOUND_EMAIL_PASSWORD+ environment variable.
+ #
+ # 3. Configure your SMTP server to pipe inbound emails to the appropriate ingress command, providing the +URL+ of the
+ # relay ingress and the +INGRESS_PASSWORD+ you previously generated.
+ #
+ # If your application lives at <tt>https://example.com</tt>, you would configure the Postfix SMTP server to pipe
+ # inbound emails to the following command:
+ #
+ # bin/rails action_mailbox:ingress:postfix URL=https://example.com/rails/action_mailbox/postfix/inbound_emails INGRESS_PASSWORD=...
+ #
+ # Built-in ingress commands are available for these popular SMTP servers:
+ #
+ # - Exim (<tt>bin/rails action_mailbox:ingress:exim)
+ # - Postfix (<tt>bin/rails action_mailbox:ingress:postfix)
+ # - Qmail (<tt>bin/rails action_mailbox:ingress:qmail)
+ class Ingresses::Relay::InboundEmailsController < ActionMailbox::BaseController
+ before_action :authenticate_by_password, :require_valid_rfc822_message
+
+ def create
+ ActionMailbox::InboundEmail.create_and_extract_message_id! request.body.read
+ end
+
+ private
+ def require_valid_rfc822_message
+ unless request.content_type == "message/rfc822"
+ head :unsupported_media_type
+ end
+ end
+ end
+end
diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb
new file mode 100644
index 0000000000..58da33d85e
--- /dev/null
+++ b/actionmailbox/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # Ingests inbound emails from SendGrid. Requires an +email+ parameter containing a full RFC 822 message.
+ #
+ # Authenticates requests using HTTP basic access authentication. The username is always +actionmailbox+, and the
+ # password is read from the application's encrypted credentials or an environment variable. See the Usage section below.
+ #
+ # Note that basic authentication is insecure over unencrypted HTTP. An attacker that intercepts cleartext requests to
+ # the SendGrid ingress can learn its password. You should only use the SendGrid ingress over HTTPS.
+ #
+ # Returns:
+ #
+ # - <tt>204 No Content</tt> if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox
+ # - <tt>401 Unauthorized</tt> if the request's signature could not be validated
+ # - <tt>404 Not Found</tt> if Action Mailbox is not configured to accept inbound emails from SendGrid
+ # - <tt>422 Unprocessable Entity</tt> if the request is missing the required +email+ parameter
+ # - <tt>500 Server Error</tt> if the ingress password is not configured, or if one of the Active Record database,
+ # the Active Storage service, or the Active Job backend is misconfigured or unavailable
+ #
+ # == Usage
+ #
+ # 1. Tell Action Mailbox to accept emails from SendGrid:
+ #
+ # # config/environments/production.rb
+ # config.action_mailbox.ingress = :sendgrid
+ #
+ # 2. Generate a strong password that Action Mailbox can use to authenticate requests to the SendGrid ingress.
+ #
+ # Use <tt>rails credentials:edit</tt> to add the password to your application's encrypted credentials under
+ # +action_mailbox.ingress_password+, where Action Mailbox will automatically find it:
+ #
+ # action_mailbox:
+ # ingress_password: ...
+ #
+ # Alternatively, provide the password in the +RAILS_INBOUND_EMAIL_PASSWORD+ environment variable.
+ #
+ # 3. {Configure SendGrid Inbound Parse}[https://sendgrid.com/docs/for-developers/parsing-email/setting-up-the-inbound-parse-webhook/]
+ # to forward inbound emails to +/rails/action_mailbox/sendgrid/inbound_emails+ with the username +actionmailbox+ and
+ # the password you previously generated. If your application lived at <tt>https://example.com</tt>, you would
+ # configure SendGrid with the following fully-qualified URL:
+ #
+ # https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/sendgrid/inbound_emails
+ #
+ # *NOTE:* When configuring your SendGrid Inbound Parse webhook, be sure to check the box labeled *"Post the raw,
+ # full MIME message."* Action Mailbox needs the raw MIME message to work.
+ class Ingresses::Sendgrid::InboundEmailsController < ActionMailbox::BaseController
+ before_action :authenticate_by_password
+
+ def create
+ ActionMailbox::InboundEmail.create_and_extract_message_id! params.require(:email)
+ end
+ end
+end
diff --git a/actionmailbox/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb b/actionmailbox/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb
new file mode 100644
index 0000000000..d051dfe665
--- /dev/null
+++ b/actionmailbox/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Rails
+ class Conductor::ActionMailbox::InboundEmailsController < Rails::Conductor::BaseController
+ def index
+ @inbound_emails = ActionMailbox::InboundEmail.order(created_at: :desc)
+ end
+
+ def new
+ end
+
+ def show
+ @inbound_email = ActionMailbox::InboundEmail.find(params[:id])
+ end
+
+ def create
+ inbound_email = create_inbound_email(new_mail)
+ redirect_to main_app.rails_conductor_inbound_email_url(inbound_email)
+ end
+
+ private
+ def new_mail
+ Mail.new(params.require(:mail).permit(:from, :to, :cc, :bcc, :in_reply_to, :subject, :body).to_h).tap do |mail|
+ params[:mail][:attachments].to_a.each do |attachment|
+ mail.add_file(filename: attachment.path, content: attachment.read)
+ end
+ end
+ end
+
+ def create_inbound_email(mail)
+ ActionMailbox::InboundEmail.create_and_extract_message_id!(mail.to_s)
+ end
+ end
+end
diff --git a/actionmailbox/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb b/actionmailbox/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb
new file mode 100644
index 0000000000..05b1ca9a39
--- /dev/null
+++ b/actionmailbox/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Rails
+ # Rerouting will run routing and processing on an email that has already been, or attempted to be, processed.
+ class Conductor::ActionMailbox::ReroutesController < Rails::Conductor::BaseController
+ def create
+ inbound_email = ActionMailbox::InboundEmail.find(params[:inbound_email_id])
+ reroute inbound_email
+
+ redirect_to main_app.rails_conductor_inbound_email_url(inbound_email)
+ end
+
+ private
+ def reroute(inbound_email)
+ inbound_email.pending!
+ inbound_email.route_later
+ end
+ end
+end
diff --git a/actionmailbox/app/controllers/rails/conductor/base_controller.rb b/actionmailbox/app/controllers/rails/conductor/base_controller.rb
new file mode 100644
index 0000000000..93c4e2a66e
--- /dev/null
+++ b/actionmailbox/app/controllers/rails/conductor/base_controller.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Rails
+ # TODO: Move this to Rails::Conductor gem
+ class Conductor::BaseController < ActionController::Base
+ layout "rails/conductor"
+ before_action :ensure_development_env
+
+ private
+ def ensure_development_env
+ head :forbidden unless Rails.env.development?
+ end
+ end
+end
diff --git a/actionmailbox/app/jobs/action_mailbox/incineration_job.rb b/actionmailbox/app/jobs/action_mailbox/incineration_job.rb
new file mode 100644
index 0000000000..1579a3c7c8
--- /dev/null
+++ b/actionmailbox/app/jobs/action_mailbox/incineration_job.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # You can configure when this +IncinerationJob+ will be run as a time-after-processing using the
+ # +config.action_mailbox.incinerate_after+ or +ActionMailbox.incinerate_after+ setting.
+ #
+ # Since this incineration is set for the future, it'll automatically ignore any <tt>InboundEmail</tt>s
+ # that have already been deleted and discard itself if so.
+ class IncinerationJob < ActiveJob::Base
+ queue_as { ActionMailbox.queues[:incineration] }
+
+ discard_on ActiveRecord::RecordNotFound
+
+ def self.schedule(inbound_email)
+ set(wait: ActionMailbox.incinerate_after).perform_later(inbound_email)
+ end
+
+ def perform(inbound_email)
+ inbound_email.incinerate
+ end
+ end
+end
diff --git a/actionmailbox/app/jobs/action_mailbox/routing_job.rb b/actionmailbox/app/jobs/action_mailbox/routing_job.rb
new file mode 100644
index 0000000000..4ddf6e4231
--- /dev/null
+++ b/actionmailbox/app/jobs/action_mailbox/routing_job.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # Routing a new InboundEmail is an asynchronous operation, which allows the ingress controllers to quickly
+ # accept new incoming emails without being burdened to hang while they're actually being processed.
+ class RoutingJob < ActiveJob::Base
+ queue_as { ActionMailbox.queues[:routing] }
+
+ def perform(inbound_email)
+ inbound_email.route
+ end
+ end
+end
diff --git a/actionmailbox/app/models/action_mailbox/inbound_email.rb b/actionmailbox/app/models/action_mailbox/inbound_email.rb
new file mode 100644
index 0000000000..023de19ccc
--- /dev/null
+++ b/actionmailbox/app/models/action_mailbox/inbound_email.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require "mail"
+
+module ActionMailbox
+ # The +InboundEmail+ is an Active Record that keeps a reference to the raw email stored in Active Storage
+ # and tracks the status of processing. By default, incoming emails will go through the following lifecycle:
+ #
+ # * Pending: Just received by one of the ingress controllers and scheduled for routing.
+ # * Processing: During active processing, while a specific mailbox is running its #process method.
+ # * Delivered: Successfully processed by the specific mailbox.
+ # * Failed: An exception was raised during the specific mailbox's execution of the +#process+ method.
+ # * Bounced: Rejected processing by the specific mailbox and bounced to sender.
+ #
+ # Once the +InboundEmail+ has reached the status of being either +delivered+, +failed+, or +bounced+,
+ # it'll count as having been +#processed?+. Once processed, the +InboundEmail+ will be scheduled for
+ # automatic incineration at a later point.
+ #
+ # When working with an +InboundEmail+, you'll usually interact with the parsed version of the source,
+ # which is available as a +Mail+ object from +#mail+. But you can also access the raw source directly
+ # using the +#source+ method.
+ #
+ # Examples:
+ #
+ # inbound_email.mail.from # => 'david@loudthinking.com'
+ # inbound_email.source # Returns the full rfc822 source of the email as text
+ class InboundEmail < ActiveRecord::Base
+ self.table_name = "action_mailbox_inbound_emails"
+
+ include Incineratable, MessageId, Routable
+
+ has_one_attached :raw_email
+ enum status: %i[ pending processing delivered failed bounced ]
+
+ def mail
+ @mail ||= Mail.from_source(source)
+ end
+
+ def source
+ @source ||= raw_email.download
+ end
+
+ def processed?
+ delivered? || failed? || bounced?
+ end
+ end
+end
+
+ActiveSupport.run_load_hooks :action_mailbox_inbound_email, ActionMailbox::InboundEmail
diff --git a/actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb
new file mode 100644
index 0000000000..697331ede4
--- /dev/null
+++ b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# Ensure that the +InboundEmail+ is automatically scheduled for later incineration if the status has been
+# changed to +processed+. The later incineration will be invoked at the time specified by the
+# +ActionMailbox.incinerate_after+ time using the +IncinerationJob+.
+module ActionMailbox::InboundEmail::Incineratable
+ extend ActiveSupport::Concern
+
+ included do
+ after_update_commit :incinerate_later, if: -> { status_previously_changed? && processed? }
+ end
+
+ def incinerate_later
+ ActionMailbox::IncinerationJob.schedule self
+ end
+
+ def incinerate
+ Incineration.new(self).run
+ end
+end
diff --git a/actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb
new file mode 100644
index 0000000000..dabc83fae6
--- /dev/null
+++ b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # Command class for carrying out the actual incineration of the +InboundMail+ that's been scheduled
+ # for removal. Before the incineration – which really is just a call to +#destroy!+ – is run, we verify
+ # that it's both eligible (by virtue of having already been processed) and time to do so (that is,
+ # the +InboundEmail+ was processed after the +incinerate_after+ time).
+ class InboundEmail::Incineratable::Incineration
+ def initialize(inbound_email)
+ @inbound_email = inbound_email
+ end
+
+ def run
+ @inbound_email.destroy! if due? && processed?
+ end
+
+ private
+ def due?
+ @inbound_email.updated_at < ActionMailbox.incinerate_after.ago.end_of_day
+ end
+
+ def processed?
+ @inbound_email.processed?
+ end
+ end
+end
diff --git a/actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb b/actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb
new file mode 100644
index 0000000000..470b93ca20
--- /dev/null
+++ b/actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+# The +Message-ID+ as specified by rfc822 is supposed to be a unique identifier for that individual email.
+# That makes it an ideal tracking token for debugging and forensics, just like +X-Request-Id+ does for
+# web request.
+#
+# If an inbound email does not, against the rfc822 mandate, specify a Message-ID, one will be generated
+# using the approach from <tt>Mail::MessageIdField</tt>.
+module ActionMailbox::InboundEmail::MessageId
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Create a new +InboundEmail+ from the raw +source+ of the email, which be uploaded as a Active Storage
+ # attachment called +raw_email+. Before the upload, extract the Message-ID from the +source+ and set
+ # it as an attribute on the new +InboundEmail+.
+ def create_and_extract_message_id!(source, **options)
+ message_checksum = Digest::SHA1.hexdigest(source)
+ message_id = extract_message_id(source) || generate_missing_message_id(message_checksum)
+
+ create! options.merge(message_id: message_id, message_checksum: message_checksum) do |inbound_email|
+ inbound_email.raw_email.attach io: StringIO.new(source), filename: "message.eml", content_type: "message/rfc822"
+ end
+ rescue ActiveRecord::RecordNotUnique
+ nil
+ end
+
+ private
+ def extract_message_id(source)
+ Mail.from_source(source).message_id rescue nil
+ end
+
+ def generate_missing_message_id(message_checksum)
+ Mail::MessageIdField.new("<#{message_checksum}@#{::Socket.gethostname}.mail>").message_id.tap do |message_id|
+ logger.warn "Message-ID couldn't be parsed or is missing. Generated a new Message-ID: #{message_id}"
+ end
+ end
+ end
+end
diff --git a/actionmailbox/app/models/action_mailbox/inbound_email/routable.rb b/actionmailbox/app/models/action_mailbox/inbound_email/routable.rb
new file mode 100644
index 0000000000..39565df166
--- /dev/null
+++ b/actionmailbox/app/models/action_mailbox/inbound_email/routable.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+# A newly received +InboundEmail+ will not be routed synchronously as part of ingress controller's receival.
+# Instead, the routing will be done asynchronously, using a +RoutingJob+, to ensure maximum parallel capacity.
+#
+# By default, all newly created +InboundEmail+ records that have the status of +pending+, which is the default,
+# will be scheduled for automatic, deferred routing.
+module ActionMailbox::InboundEmail::Routable
+ extend ActiveSupport::Concern
+
+ included do
+ after_create_commit :route_later, if: :pending?
+ end
+
+ # Enqueue a +RoutingJob+ for this +InboundEmail+.
+ def route_later
+ ActionMailbox::RoutingJob.perform_later self
+ end
+
+ # Route this +InboundEmail+ using the routing rules declared on the +ApplicationMailbox+.
+ def route
+ ApplicationMailbox.route self
+ end
+end
diff --git a/actionmailbox/app/views/layouts/rails/conductor.html.erb b/actionmailbox/app/views/layouts/rails/conductor.html.erb
new file mode 100644
index 0000000000..1cad6560c4
--- /dev/null
+++ b/actionmailbox/app/views/layouts/rails/conductor.html.erb
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>Rails Conductor: <%= yield :title %></title>
+</head>
+<body>
+<%= yield %>
+</body>
+</html>
diff --git a/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/index.html.erb b/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/index.html.erb
new file mode 100644
index 0000000000..19c53984e2
--- /dev/null
+++ b/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/index.html.erb
@@ -0,0 +1,15 @@
+<% provide :title, "Deliver new inbound email" %>
+
+<h1>All inbound emails</h1>
+
+<table>
+ <tr><th>Message ID</th><th>Status</th></tr>
+ <% @inbound_emails.each do |inbound_email| %>
+ <tr>
+ <td><%= link_to inbound_email.message_id, main_app.rails_conductor_inbound_email_path(inbound_email) %></td>
+ <td><%= inbound_email.status %></td>
+ </tr>
+ <% end %>
+</table>
+
+<%= link_to "Deliver new inbound email", main_app.new_rails_conductor_inbound_email_path %> \ No newline at end of file
diff --git a/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb b/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb
new file mode 100644
index 0000000000..a3c3d39064
--- /dev/null
+++ b/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb
@@ -0,0 +1,47 @@
+<% provide :title, "Deliver new inbound email" %>
+
+<h1>Deliver new inbound email</h1>
+
+<%= form_with(url: main_app.rails_conductor_inbound_emails_path, scope: :mail, local: true) do |form| %>
+ <div>
+ <%= form.label :from, "From" %><br>
+ <%= form.text_field :from %>
+ </div>
+
+ <div>
+ <%= form.label :to, "To" %><br>
+ <%= form.text_field :to %>
+ </div>
+
+ <div>
+ <%= form.label :cc, "CC" %><br>
+ <%= form.text_field :cc %>
+ </div>
+
+ <div>
+ <%= form.label :bcc, "BCC" %><br>
+ <%= form.text_field :bcc %>
+ </div>
+
+ <div>
+ <%= form.label :in_reply_to, "In-Reply-To" %><br>
+ <%= form.text_field :in_reply_to %>
+ </div>
+
+ <div>
+ <%= form.label :subject, "Subject" %><br>
+ <%= form.text_field :subject %>
+ </div>
+
+ <div>
+ <%= form.label :body, "Body" %><br>
+ <%= form.text_area :body, size: "40x20" %>
+ </div>
+
+ <div>
+ <%= form.label :attachments, "Attachments" %><br>
+ <%= form.file_field :attachments, multiple: true %>
+ </div>
+
+ <%= form.submit "Deliver inbound email" %>
+<% end %>
diff --git a/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/show.html.erb b/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/show.html.erb
new file mode 100644
index 0000000000..e761904196
--- /dev/null
+++ b/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/show.html.erb
@@ -0,0 +1,15 @@
+<% provide :title, @inbound_email.message_id %>
+
+<h1><%= @inbound_email.message_id %>: <%= @inbound_email.status %></h1>
+
+<ul>
+ <li><%= button_to "Route again", main_app.rails_conductor_inbound_email_reroute_path(@inbound_email), method: :post %></li>
+ <li>Incinerate</li>
+</ul>
+
+<details>
+ <summary>Full email source</summary>
+ <pre><%= @inbound_email.source %></pre>
+</details>
+
+<%= link_to "Back to all inbound emails", main_app.rails_conductor_inbound_emails_path %> \ No newline at end of file
diff --git a/actionmailbox/bin/test b/actionmailbox/bin/test
new file mode 100755
index 0000000000..c53377cc97
--- /dev/null
+++ b/actionmailbox/bin/test
@@ -0,0 +1,5 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+COMPONENT_ROOT = File.expand_path("..", __dir__)
+require_relative "../../tools/test"
diff --git a/actionmailbox/config/routes.rb b/actionmailbox/config/routes.rb
new file mode 100644
index 0000000000..517d2835af
--- /dev/null
+++ b/actionmailbox/config/routes.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+Rails.application.routes.draw do
+ scope "/rails/action_mailbox", module: "action_mailbox/ingresses" do
+ post "/amazon/inbound_emails" => "amazon/inbound_emails#create", as: :rails_amazon_inbound_emails
+ post "/mandrill/inbound_emails" => "mandrill/inbound_emails#create", as: :rails_mandrill_inbound_emails
+ post "/postmark/inbound_emails" => "postmark/inbound_emails#create", as: :rails_postmark_inbound_emails
+ post "/relay/inbound_emails" => "relay/inbound_emails#create", as: :rails_relay_inbound_emails
+ post "/sendgrid/inbound_emails" => "sendgrid/inbound_emails#create", as: :rails_sendgrid_inbound_emails
+
+ # Mailgun requires that a webhook's URL end in 'mime' for it to receive the raw contents of emails.
+ post "/mailgun/inbound_emails/mime" => "mailgun/inbound_emails#create", as: :rails_mailgun_inbound_emails
+ end
+
+ # TODO: Should these be mounted within the engine only?
+ scope "rails/conductor/action_mailbox/", module: "rails/conductor/action_mailbox" do
+ resources :inbound_emails, as: :rails_conductor_inbound_emails
+ post ":inbound_email_id/reroute" => "reroutes#create", as: :rails_conductor_inbound_email_reroute
+ end
+end
diff --git a/actionmailbox/db/migrate/20180917164000_create_action_mailbox_tables.rb b/actionmailbox/db/migrate/20180917164000_create_action_mailbox_tables.rb
new file mode 100644
index 0000000000..89ab66c1a9
--- /dev/null
+++ b/actionmailbox/db/migrate/20180917164000_create_action_mailbox_tables.rb
@@ -0,0 +1,17 @@
+class CreateActionMailboxTables < ActiveRecord::Migration[6.0]
+ def change
+ create_table :action_mailbox_inbound_emails do |t|
+ t.integer :status, default: 0, null: false
+ t.string :message_id, null: false
+ t.string :message_checksum, null: false
+
+ if supports_datetime_with_precision?
+ t.timestamps precision: 6
+ else
+ t.timestamps
+ end
+
+ t.index [ :message_id, :message_checksum ], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true
+ end
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox.rb b/actionmailbox/lib/action_mailbox.rb
new file mode 100644
index 0000000000..b4ff25a9ab
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require "action_mailbox/mail_ext"
+
+module ActionMailbox
+ extend ActiveSupport::Autoload
+
+ autoload :Base
+ autoload :Router
+ autoload :TestCase
+
+ mattr_accessor :ingress
+ mattr_accessor :logger
+ mattr_accessor :incinerate_after, default: 30.days
+ mattr_accessor :queues, default: {}
+end
diff --git a/actionmailbox/lib/action_mailbox/base.rb b/actionmailbox/lib/action_mailbox/base.rb
new file mode 100644
index 0000000000..ff8587acd1
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/base.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+require "active_support/rescuable"
+
+require "action_mailbox/callbacks"
+require "action_mailbox/routing"
+
+module ActionMailbox
+ # The base class for all application mailboxes. Not intended to be inherited from directly. Inherit from
+ # +ApplicationMailbox+ instead, as that's where the app-specific routing is configured. This routing
+ # is specified in the following ways:
+ #
+ # class ApplicationMailbox < ActionMailbox::Base
+ # # Any of the recipients of the mail (whether to, cc, bcc) are matched against the regexp.
+ # routing /^replies@/i => :replies
+ #
+ # # Any of the recipients of the mail (whether to, cc, bcc) needs to be an exact match for the string.
+ # routing "help@example.com" => :help
+ #
+ # # Any callable (proc, lambda, etc) object is passed the inbound_email record and is a match if true.
+ # routing ->(inbound_email) { inbound_email.mail.to.size > 2 } => :multiple_recipients
+ #
+ # # Any object responding to #match? is called with the inbound_email record as an argument. Match if true.
+ # routing CustomAddress.new => :custom
+ #
+ # # Any inbound_email that has not been already matched will be sent to the BackstopMailbox.
+ # routing :all => :backstop
+ # end
+ #
+ # Application mailboxes need to overwrite the +#process+ method, which is invoked by the framework after
+ # callbacks have been run. The callbacks available are: +before_processing+, +after_processing+, and
+ # +around_processing+. The primary use case is ensure certain preconditions to processing are fulfilled
+ # using +before_processing+ callbacks.
+ #
+ # If a precondition fails to be met, you can halt the processing using the +#bounced!+ method,
+ # which will silently prevent any further processing, but not actually send out any bounce notice. You
+ # can also pair this behavior with the invocation of an Action Mailer class responsible for sending out
+ # an actual bounce email. This is done using the +#bounce_with+ method, which takes the mail object returned
+ # by an Action Mailer method, like so:
+ #
+ # class ForwardsMailbox < ApplicationMailbox
+ # before_processing :ensure_sender_is_a_user
+ #
+ # private
+ # def ensure_sender_is_a_user
+ # unless User.exist?(email_address: mail.from)
+ # bounce_with UserRequiredMailer.missing(inbound_email)
+ # end
+ # end
+ # end
+ #
+ # During the processing of the inbound email, the status will be tracked. Before processing begins,
+ # the email will normally have the +pending+ status. Once processing begins, just before callbacks
+ # and the +#process+ method is called, the status is changed to +processing+. If processing is allowed to
+ # complete, the status is changed to +delivered+. If a bounce is triggered, then +bounced+. If an unhandled
+ # exception is bubbled up, then +failed+.
+ #
+ # Exceptions can be handled at the class level using the familiar +Rescuable+ approach:
+ #
+ # class ForwardsMailbox < ApplicationMailbox
+ # rescue_from(ApplicationSpecificVerificationError) { bounced! }
+ # end
+ class Base
+ include ActiveSupport::Rescuable
+ include ActionMailbox::Callbacks, ActionMailbox::Routing
+
+ attr_reader :inbound_email
+ delegate :mail, :delivered!, :bounced!, to: :inbound_email
+
+ delegate :logger, to: ActionMailbox
+
+ def self.receive(inbound_email)
+ new(inbound_email).perform_processing
+ end
+
+ def initialize(inbound_email)
+ @inbound_email = inbound_email
+ end
+
+ def perform_processing #:nodoc:
+ track_status_of_inbound_email do
+ run_callbacks :process do
+ process
+ end
+ end
+ rescue => exception
+ # TODO: Include a reference to the inbound_email in the exception raised so error handling becomes easier
+ rescue_with_handler(exception) || raise
+ end
+
+ def process
+ # Overwrite in subclasses
+ end
+
+ def finished_processing? #:nodoc:
+ inbound_email.delivered? || inbound_email.bounced?
+ end
+
+
+ # Enqueues the given +message+ for delivery and changes the inbound email's status to +:bounced+.
+ def bounce_with(message)
+ inbound_email.bounced!
+ message.deliver_later
+ end
+
+ private
+ def track_status_of_inbound_email
+ inbound_email.processing!
+ yield
+ inbound_email.delivered! unless inbound_email.bounced?
+ rescue
+ inbound_email.failed!
+ raise
+ end
+ end
+end
+
+ActiveSupport.run_load_hooks :action_mailbox, ActionMailbox::Base
diff --git a/actionmailbox/lib/action_mailbox/callbacks.rb b/actionmailbox/lib/action_mailbox/callbacks.rb
new file mode 100644
index 0000000000..2b7212284b
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/callbacks.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require "active_support/callbacks"
+
+module ActionMailbox
+ # Defines the callbacks related to processing.
+ module Callbacks
+ extend ActiveSupport::Concern
+ include ActiveSupport::Callbacks
+
+ TERMINATOR = ->(mailbox, chain) do
+ chain.call
+ mailbox.finished_processing?
+ end
+
+ included do
+ define_callbacks :process, terminator: TERMINATOR, skip_after_callbacks_if_terminated: true
+ end
+
+ class_methods do
+ def before_processing(*methods, &block)
+ set_callback(:process, :before, *methods, &block)
+ end
+
+ def after_processing(*methods, &block)
+ set_callback(:process, :after, *methods, &block)
+ end
+
+ def around_processing(*methods, &block)
+ set_callback(:process, :around, *methods, &block)
+ end
+ end
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/engine.rb b/actionmailbox/lib/action_mailbox/engine.rb
new file mode 100644
index 0000000000..d5a07a7dce
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/engine.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require "rails"
+require "action_controller/railtie"
+require "active_job/railtie"
+require "active_record/railtie"
+require "active_storage/engine"
+
+require "action_mailbox"
+
+module ActionMailbox
+ class Engine < Rails::Engine
+ isolate_namespace ActionMailbox
+ config.eager_load_namespaces << ActionMailbox
+
+ config.action_mailbox = ActiveSupport::OrderedOptions.new
+ config.action_mailbox.incinerate_after = 30.days
+
+ config.action_mailbox.queues = ActiveSupport::InheritableOptions.new \
+ incineration: :action_mailbox_incineration, routing: :action_mailbox_routing
+
+ initializer "action_mailbox.config" do
+ config.after_initialize do |app|
+ ActionMailbox.logger = app.config.action_mailbox.logger || Rails.logger
+ ActionMailbox.incinerate_after = app.config.action_mailbox.incinerate_after || 30.days
+ ActionMailbox.queues = app.config.action_mailbox.queues || {}
+ end
+ end
+
+ initializer "action_mailbox.ingress" do
+ config.after_initialize do |app|
+ if ActionMailbox.ingress = app.config.action_mailbox.ingress.presence
+ config.to_prepare do
+ if ingress_controller_class = "ActionMailbox::Ingresses::#{ActionMailbox.ingress.to_s.classify}::InboundEmailsController".safe_constantize
+ ingress_controller_class.prepare
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/gem_version.rb b/actionmailbox/lib/action_mailbox/gem_version.rb
new file mode 100644
index 0000000000..1f9c4272b9
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/gem_version.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # Returns the currently-loaded version of Action Mailbox as a <tt>Gem::Version</tt>.
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 6
+ MINOR = 0
+ TINY = 0
+ PRE = "beta1"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/mail_ext.rb b/actionmailbox/lib/action_mailbox/mail_ext.rb
new file mode 100644
index 0000000000..c4d277a1f9
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/mail_ext.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require "mail"
+
+# The hope is to upstream most of these basic additions to the Mail gem's Mail object. But until then, here they lay!
+Dir["#{File.expand_path(File.dirname(__FILE__))}/mail_ext/*"].each { |path| require "action_mailbox/mail_ext/#{File.basename(path)}" }
diff --git a/actionmailbox/lib/action_mailbox/mail_ext/address_equality.rb b/actionmailbox/lib/action_mailbox/mail_ext/address_equality.rb
new file mode 100644
index 0000000000..39a43b3468
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/mail_ext/address_equality.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Mail
+ class Address
+ def ==(other_address)
+ other_address.is_a?(Mail::Address) && to_s == other_address.to_s
+ end
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/mail_ext/address_wrapping.rb b/actionmailbox/lib/action_mailbox/mail_ext/address_wrapping.rb
new file mode 100644
index 0000000000..19eb624c1c
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/mail_ext/address_wrapping.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Mail
+ class Address
+ def self.wrap(address)
+ address.is_a?(Mail::Address) ? address : Mail::Address.new(address)
+ end
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/mail_ext/addresses.rb b/actionmailbox/lib/action_mailbox/mail_ext/addresses.rb
new file mode 100644
index 0000000000..5eab1feb3d
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/mail_ext/addresses.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Mail
+ class Message
+ def from_address
+ header[:from]&.address_list&.addresses&.first
+ end
+
+ def recipients_addresses
+ to_addresses + cc_addresses + bcc_addresses + x_original_to_addresses
+ end
+
+ def to_addresses
+ Array(header[:to]&.address_list&.addresses)
+ end
+
+ def cc_addresses
+ Array(header[:cc]&.address_list&.addresses)
+ end
+
+ def bcc_addresses
+ Array(header[:bcc]&.address_list&.addresses)
+ end
+
+ def x_original_to_addresses
+ Array(header[:x_original_to]).collect { |header| Mail::Address.new header.to_s }
+ end
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/mail_ext/from_source.rb b/actionmailbox/lib/action_mailbox/mail_ext/from_source.rb
new file mode 100644
index 0000000000..17b7fc80ad
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/mail_ext/from_source.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module Mail
+ def self.from_source(source)
+ Mail.new Mail::Utilities.binary_unsafe_to_crlf(source.to_s)
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/mail_ext/recipients.rb b/actionmailbox/lib/action_mailbox/mail_ext/recipients.rb
new file mode 100644
index 0000000000..1f8a713218
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/mail_ext/recipients.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Mail
+ class Message
+ def recipients
+ Array(to) + Array(cc) + Array(bcc) + Array(header[:x_original_to]).map(&:to_s)
+ end
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/relayer.rb b/actionmailbox/lib/action_mailbox/relayer.rb
new file mode 100644
index 0000000000..e2890acb60
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/relayer.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require "action_mailbox/version"
+require "net/http"
+require "uri"
+
+module ActionMailbox
+ class Relayer
+ class Result < Struct.new(:status_code, :message)
+ def success?
+ !failure?
+ end
+
+ def failure?
+ transient_failure? || permanent_failure?
+ end
+
+ def transient_failure?
+ status_code.start_with?("4.")
+ end
+
+ def permanent_failure?
+ status_code.start_with?("5.")
+ end
+ end
+
+ CONTENT_TYPE = "message/rfc822"
+ USER_AGENT = "Action Mailbox relayer v#{ActionMailbox.version}"
+
+ attr_reader :uri, :username, :password
+
+ def initialize(url:, username: "actionmailbox", password:)
+ @uri, @username, @password = URI(url), username, password
+ end
+
+ def relay(source)
+ case response = post(source)
+ when Net::HTTPSuccess
+ Result.new "2.0.0", "Successfully relayed message to ingress"
+ when Net::HTTPUnauthorized
+ Result.new "4.7.0", "Invalid credentials for ingress"
+ else
+ Result.new "4.0.0", "HTTP #{response.code}"
+ end
+ rescue IOError, SocketError, SystemCallError => error
+ Result.new "4.4.2", "Network error relaying to ingress: #{error.message}"
+ rescue Timeout::Error
+ Result.new "4.4.2", "Timed out relaying to ingress"
+ rescue => error
+ Result.new "4.0.0", "Error relaying to ingress: #{error.message}"
+ end
+
+ private
+ def post(source)
+ client.post uri, source,
+ "Content-Type" => CONTENT_TYPE,
+ "User-Agent" => USER_AGENT,
+ "Authorization" => "Basic #{Base64.strict_encode64(username + ":" + password)}"
+ end
+
+ def client
+ @client ||= Net::HTTP.new(uri.host, uri.port).tap do |connection|
+ if uri.scheme == "https"
+ require "openssl"
+
+ connection.use_ssl = true
+ connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ end
+
+ connection.open_timeout = 1
+ connection.read_timeout = 10
+ end
+ end
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/router.rb b/actionmailbox/lib/action_mailbox/router.rb
new file mode 100644
index 0000000000..71370e409d
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/router.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # Encapsulates the routes that live on the ApplicationMailbox and performs the actual routing when
+ # an inbound_email is received.
+ class Router
+ class RoutingError < StandardError; end
+
+ def initialize
+ @routes = []
+ end
+
+ def add_routes(routes)
+ routes.each do |(address, mailbox_name)|
+ add_route address, to: mailbox_name
+ end
+ end
+
+ def add_route(address, to:)
+ routes.append Route.new(address, to: to)
+ end
+
+ def route(inbound_email)
+ if mailbox = match_to_mailbox(inbound_email)
+ mailbox.receive(inbound_email)
+ else
+ inbound_email.bounced!
+
+ raise RoutingError
+ end
+ end
+
+ private
+ attr_reader :routes
+
+ def match_to_mailbox(inbound_email)
+ routes.detect { |route| route.match?(inbound_email) }.try(:mailbox_class)
+ end
+ end
+end
+
+require "action_mailbox/router/route"
diff --git a/actionmailbox/lib/action_mailbox/router/route.rb b/actionmailbox/lib/action_mailbox/router/route.rb
new file mode 100644
index 0000000000..7e98e83382
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/router/route.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # Encapsulates a route, which can then be matched against an inbound_email and provide a lookup of the matching
+ # mailbox class. See examples for the different route addresses and how to use them in the +ActionMailbox::Base+
+ # documentation.
+ class Router::Route
+ attr_reader :address, :mailbox_name
+
+ def initialize(address, to:)
+ @address, @mailbox_name = address, to
+
+ ensure_valid_address
+ end
+
+ def match?(inbound_email)
+ case address
+ when :all
+ true
+ when String
+ inbound_email.mail.recipients.any? { |recipient| address.casecmp?(recipient) }
+ when Regexp
+ inbound_email.mail.recipients.any? { |recipient| address.match?(recipient) }
+ when Proc
+ address.call(inbound_email)
+ else
+ address.match?(inbound_email)
+ end
+ end
+
+ def mailbox_class
+ "#{mailbox_name.to_s.camelize}Mailbox".constantize
+ end
+
+ private
+ def ensure_valid_address
+ unless [ Symbol, String, Regexp, Proc ].any? { |klass| address.is_a?(klass) } || address.respond_to?(:match?)
+ raise ArgumentError, "Expected a Symbol, String, Regexp, Proc, or matchable, got #{address.inspect}"
+ end
+ end
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/routing.rb b/actionmailbox/lib/action_mailbox/routing.rb
new file mode 100644
index 0000000000..58462a44c6
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/routing.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module ActionMailbox
+ # See +ActionMailbox::Base+ for how to specify routing.
+ module Routing
+ extend ActiveSupport::Concern
+
+ included do
+ cattr_accessor :router, default: ActionMailbox::Router.new
+ end
+
+ class_methods do
+ def routing(routes)
+ router.add_routes(routes)
+ end
+
+ def route(inbound_email)
+ router.route(inbound_email)
+ end
+ end
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/test_case.rb b/actionmailbox/lib/action_mailbox/test_case.rb
new file mode 100644
index 0000000000..5e78e428d3
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/test_case.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require "action_mailbox/test_helper"
+require "active_support/test_case"
+
+module ActionMailbox
+ class TestCase < ActiveSupport::TestCase
+ include ActionMailbox::TestHelper
+ end
+end
+
+ActiveSupport.run_load_hooks :action_mailbox_test_case, ActionMailbox::TestCase
diff --git a/actionmailbox/lib/action_mailbox/test_helper.rb b/actionmailbox/lib/action_mailbox/test_helper.rb
new file mode 100644
index 0000000000..0ec9152844
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/test_helper.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require "mail"
+
+module ActionMailbox
+ module TestHelper
+ # Create an +InboundEmail+ record using an eml fixture in the format of message/rfc822
+ # referenced with +fixture_name+ located in +test/fixtures/files/fixture_name+.
+ def create_inbound_email_from_fixture(fixture_name, status: :processing)
+ create_inbound_email_from_source file_fixture(fixture_name).read, status: status
+ end
+
+ # Create an +InboundEmail+ by specifying it using +Mail.new+ options. Example:
+ #
+ # create_inbound_email_from_mail(from: "david@loudthinking.com", subject: "Hello!")
+ def create_inbound_email_from_mail(status: :processing, **mail_options)
+ create_inbound_email_from_source Mail.new(mail_options).to_s, status: status
+ end
+
+ # Create an +InboundEmail+ using the raw rfc822 +source+ as text.
+ def create_inbound_email_from_source(source, status: :processing)
+ ActionMailbox::InboundEmail.create_and_extract_message_id! source, status: status
+ end
+
+
+ # Create an +InboundEmail+ from fixture using the same arguments as +create_inbound_email_from_fixture+
+ # and immediately route it to processing.
+ def receive_inbound_email_from_fixture(*args)
+ create_inbound_email_from_fixture(*args).tap(&:route)
+ end
+
+ # Create an +InboundEmail+ from fixture using the same arguments as +create_inbound_email_from_mail+
+ # and immediately route it to processing.
+ def receive_inbound_email_from_mail(**kwargs)
+ create_inbound_email_from_mail(**kwargs).tap(&:route)
+ end
+
+ # Create an +InboundEmail+ from fixture using the same arguments as +create_inbound_email_from_source+
+ # and immediately route it to processing.
+ def receive_inbound_email_from_source(**kwargs)
+ create_inbound_email_from_source(**kwargs).tap(&:route)
+ end
+ end
+end
diff --git a/actionmailbox/lib/action_mailbox/version.rb b/actionmailbox/lib/action_mailbox/version.rb
new file mode 100644
index 0000000000..e65d27f5dd
--- /dev/null
+++ b/actionmailbox/lib/action_mailbox/version.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require_relative "gem_version"
+
+module ActionMailbox
+ # Returns the currently-loaded version of Action Mailbox as a <tt>Gem::Version</tt>.
+ def self.version
+ gem_version
+ end
+end
diff --git a/actionmailbox/lib/rails/generators/installer.rb b/actionmailbox/lib/rails/generators/installer.rb
new file mode 100644
index 0000000000..25cf528ef5
--- /dev/null
+++ b/actionmailbox/lib/rails/generators/installer.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+say "Copying application_mailbox.rb to app/mailboxes"
+copy_file "#{__dir__}/mailbox/templates/application_mailbox.rb", "app/mailboxes/application_mailbox.rb"
+
+environment <<~end_of_config, env: "production"
+ # Prepare the ingress controller used to receive mail
+ # config.action_mailbox.ingress = :amazon
+
+end_of_config
diff --git a/actionmailbox/lib/rails/generators/mailbox/USAGE b/actionmailbox/lib/rails/generators/mailbox/USAGE
new file mode 100644
index 0000000000..d679dd63ae
--- /dev/null
+++ b/actionmailbox/lib/rails/generators/mailbox/USAGE
@@ -0,0 +1,12 @@
+Description:
+============
+ Stubs out a new mailbox class in app/mailboxes and invokes your template
+ engine and test framework generators.
+
+Example:
+========
+ rails generate mailbox inbox
+
+ creates a InboxMailbox class and test:
+ Mailbox: app/mailboxes/inbox_mailbox.rb
+ Test: test/mailboxes/inbox_mailbox_test.rb
diff --git a/actionmailbox/lib/rails/generators/mailbox/mailbox_generator.rb b/actionmailbox/lib/rails/generators/mailbox/mailbox_generator.rb
new file mode 100644
index 0000000000..c2c403b8f6
--- /dev/null
+++ b/actionmailbox/lib/rails/generators/mailbox/mailbox_generator.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Rails
+ module Generators
+ class MailboxGenerator < NamedBase
+ source_root File.expand_path("templates", __dir__)
+
+ check_class_collision suffix: "Mailbox"
+
+ def create_mailbox_file
+ template "mailbox.rb", File.join("app/mailboxes", class_path, "#{file_name}_mailbox.rb")
+
+ in_root do
+ if behavior == :invoke && !File.exist?(application_mailbox_file_name)
+ template "application_mailbox.rb", application_mailbox_file_name
+ end
+ end
+ end
+
+ hook_for :test_framework
+
+ private
+ def file_name # :doc:
+ @_file_name ||= super.sub(/_mailbox\z/i, "")
+ end
+
+ def application_mailbox_file_name
+ "app/mailboxes/application_mailbox.rb"
+ end
+ end
+ end
+end
diff --git a/actionmailbox/lib/rails/generators/mailbox/templates/application_mailbox.rb.tt b/actionmailbox/lib/rails/generators/mailbox/templates/application_mailbox.rb.tt
new file mode 100644
index 0000000000..ac22d03cd2
--- /dev/null
+++ b/actionmailbox/lib/rails/generators/mailbox/templates/application_mailbox.rb.tt
@@ -0,0 +1,3 @@
+class ApplicationMailbox < ActionMailbox::Base
+ # routing /something/i => :somewhere
+end
diff --git a/actionmailbox/lib/rails/generators/mailbox/templates/mailbox.rb.tt b/actionmailbox/lib/rails/generators/mailbox/templates/mailbox.rb.tt
new file mode 100644
index 0000000000..110b3b9d7e
--- /dev/null
+++ b/actionmailbox/lib/rails/generators/mailbox/templates/mailbox.rb.tt
@@ -0,0 +1,4 @@
+class <%= class_name %>Mailbox < ApplicationMailbox
+ def process
+ end
+end
diff --git a/actionmailbox/lib/rails/generators/test_unit/mailbox_generator.rb b/actionmailbox/lib/rails/generators/test_unit/mailbox_generator.rb
new file mode 100644
index 0000000000..2ec7d11a2f
--- /dev/null
+++ b/actionmailbox/lib/rails/generators/test_unit/mailbox_generator.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module TestUnit
+ module Generators
+ class MailboxGenerator < ::Rails::Generators::NamedBase
+ source_root File.expand_path("templates", __dir__)
+
+ check_class_collision suffix: "MailboxTest"
+
+ def create_test_files
+ template "mailbox_test.rb", File.join("test/mailboxes", class_path, "#{file_name}_mailbox_test.rb")
+ end
+
+ private
+ def file_name # :doc:
+ @_file_name ||= super.sub(/_mailbox\z/i, "")
+ end
+ end
+ end
+end
diff --git a/actionmailbox/lib/rails/generators/test_unit/templates/mailbox_test.rb.tt b/actionmailbox/lib/rails/generators/test_unit/templates/mailbox_test.rb.tt
new file mode 100644
index 0000000000..0b51f29fe4
--- /dev/null
+++ b/actionmailbox/lib/rails/generators/test_unit/templates/mailbox_test.rb.tt
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class <%= class_name %>MailboxTest < ActionMailbox::TestCase
+ # test "receive mail" do
+ # receive_inbound_email_from_mail \
+ # to: '"someone" <someone@example.com>',
+ # from: '"else" <else@example.com>',
+ # subject: "Hello world!",
+ # body: "Hello?"
+ # end
+end
diff --git a/actionmailbox/lib/tasks/ingress.rake b/actionmailbox/lib/tasks/ingress.rake
new file mode 100644
index 0000000000..43b613ea12
--- /dev/null
+++ b/actionmailbox/lib/tasks/ingress.rake
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+namespace :action_mailbox do
+ namespace :ingress do
+ task :environment do
+ require "active_support"
+ require "active_support/core_ext/object/blank"
+ require "action_mailbox/relayer"
+ end
+
+ desc "Relay an inbound email from Exim to Action Mailbox (URL and INGRESS_PASSWORD required)"
+ task exim: "action_mailbox:ingress:environment" do
+ url, password = ENV.values_at("URL", "INGRESS_PASSWORD")
+
+ if url.blank? || password.blank?
+ print "URL and INGRESS_PASSWORD are required"
+ exit 64 # EX_USAGE
+ end
+
+ ActionMailbox::Relayer.new(url: url, password: password).relay(STDIN.read).tap do |result|
+ print result.message
+
+ case
+ when result.success?
+ exit 0
+ when result.transient_failure?
+ exit 75 # EX_TEMPFAIL
+ else
+ exit 69 # EX_UNAVAILABLE
+ end
+ end
+ end
+
+ desc "Relay an inbound email from Postfix to Action Mailbox (URL and INGRESS_PASSWORD required)"
+ task postfix: "action_mailbox:ingress:environment" do
+ url, password = ENV.values_at("URL", "INGRESS_PASSWORD")
+
+ if url.blank? || password.blank?
+ print "4.3.5 URL and INGRESS_PASSWORD are required"
+ exit 1
+ end
+
+ ActionMailbox::Relayer.new(url: url, password: password).relay(STDIN.read).tap do |result|
+ print "#{result.status_code} #{result.message}"
+ exit result.success?
+ end
+ end
+
+ desc "Relay an inbound email from Qmail to Action Mailbox (URL and INGRESS_PASSWORD required)"
+ task qmail: "action_mailbox:ingress:environment" do
+ url, password = ENV.values_at("URL", "INGRESS_PASSWORD")
+
+ if url.blank? || password.blank?
+ print "URL and INGRESS_PASSWORD are required"
+ exit 111
+ end
+
+ ActionMailbox::Relayer.new(url: url, password: password).relay(STDIN.read).tap do |result|
+ print result.message
+
+ case
+ when result.success?
+ exit 0
+ when result.transient_failure?
+ exit 111
+ else
+ exit 100
+ end
+ end
+ end
+ end
+end
diff --git a/actionmailbox/lib/tasks/install.rake b/actionmailbox/lib/tasks/install.rake
new file mode 100644
index 0000000000..0885e2d6a5
--- /dev/null
+++ b/actionmailbox/lib/tasks/install.rake
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+namespace :action_mailbox do
+ # Prevent migration installation task from showing up twice.
+ Rake::Task["install:migrations"].clear_comments
+
+ desc "Copy over the migration"
+ task install: %w[ environment run_installer copy_migrations ]
+
+ task :run_installer do
+ installer_template = File.expand_path("../rails/generators/installer.rb", __dir__)
+ system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{installer_template}"
+ end
+
+ task :copy_migrations do
+ Rake::Task["active_storage:install:migrations"].invoke
+ Rake::Task["railties:install:migrations"].reenable # Otherwise you can't run 2 migration copy tasks in one invocation
+ Rake::Task["action_mailbox:install:migrations"].invoke
+ end
+end
diff --git a/actionmailbox/test/controllers/ingresses/amazon/inbound_emails_controller_test.rb b/actionmailbox/test/controllers/ingresses/amazon/inbound_emails_controller_test.rb
new file mode 100644
index 0000000000..e10985553e
--- /dev/null
+++ b/actionmailbox/test/controllers/ingresses/amazon/inbound_emails_controller_test.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+ActionMailbox::Ingresses::Amazon::InboundEmailsController.verifier =
+ Module.new { def self.authentic?(message); true; end }
+
+class ActionMailbox::Ingresses::Amazon::InboundEmailsControllerTest < ActionDispatch::IntegrationTest
+ setup { ActionMailbox.ingress = :amazon }
+
+ test "receiving an inbound email from Amazon" do
+ assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do
+ post rails_amazon_inbound_emails_url, params: { content: file_fixture("../files/welcome.eml").read }, as: :json
+ end
+
+ assert_response :no_content
+
+ inbound_email = ActionMailbox::InboundEmail.last
+ assert_equal file_fixture("../files/welcome.eml").read, inbound_email.raw_email.download
+ assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id
+ end
+end
diff --git a/actionmailbox/test/controllers/ingresses/mailgun/inbound_emails_controller_test.rb b/actionmailbox/test/controllers/ingresses/mailgun/inbound_emails_controller_test.rb
new file mode 100644
index 0000000000..3f225b8953
--- /dev/null
+++ b/actionmailbox/test/controllers/ingresses/mailgun/inbound_emails_controller_test.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+ENV["MAILGUN_INGRESS_API_KEY"] = "tbsy84uSV1Kt3ZJZELY2TmShPRs91E3yL4tzf96297vBCkDWgL"
+
+class ActionMailbox::Ingresses::Mailgun::InboundEmailsControllerTest < ActionDispatch::IntegrationTest
+ setup { ActionMailbox.ingress = :mailgun }
+
+ test "receiving an inbound email from Mailgun" do
+ assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do
+ travel_to "2018-10-09 15:15:00 EDT"
+ post rails_mailgun_inbound_emails_url, params: {
+ timestamp: 1539112500,
+ token: "7VwW7k6Ak7zcTwoSoNm7aTtbk1g67MKAnsYLfUB7PdszbgR5Xi",
+ signature: "ef24c5225322217bb065b80bb54eb4f9206d764e3e16abab07f0a64d1cf477cc",
+ "body-mime" => file_fixture("../files/welcome.eml").read
+ }
+ end
+
+ assert_response :no_content
+
+ inbound_email = ActionMailbox::InboundEmail.last
+ assert_equal file_fixture("../files/welcome.eml").read, inbound_email.raw_email.download
+ assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id
+ end
+
+ test "rejecting a delayed inbound email from Mailgun" do
+ assert_no_difference -> { ActionMailbox::InboundEmail.count } do
+ travel_to "2018-10-09 15:26:00 EDT"
+ post rails_mailgun_inbound_emails_url, params: {
+ timestamp: 1539112500,
+ token: "7VwW7k6Ak7zcTwoSoNm7aTtbk1g67MKAnsYLfUB7PdszbgR5Xi",
+ signature: "ef24c5225322217bb065b80bb54eb4f9206d764e3e16abab07f0a64d1cf477cc",
+ "body-mime" => file_fixture("../files/welcome.eml").read
+ }
+ end
+
+ assert_response :unauthorized
+ end
+
+ test "rejecting a forged inbound email from Mailgun" do
+ assert_no_difference -> { ActionMailbox::InboundEmail.count } do
+ travel_to "2018-10-09 15:15:00 EDT"
+ post rails_mailgun_inbound_emails_url, params: {
+ timestamp: 1539112500,
+ token: "Zx8mJBiGmiiyyfWnho3zKyjCg2pxLARoCuBM7X9AKCioShGiMX",
+ signature: "ef24c5225322217bb065b80bb54eb4f9206d764e3e16abab07f0a64d1cf477cc",
+ "body-mime" => file_fixture("../files/welcome.eml").read
+ }
+ end
+
+ assert_response :unauthorized
+ end
+
+ test "raising when the configured Mailgun API key is nil" do
+ switch_key_to nil do
+ assert_raises ArgumentError do
+ travel_to "2018-10-09 15:15:00 EDT"
+ post rails_mailgun_inbound_emails_url, params: {
+ timestamp: 1539112500,
+ token: "7VwW7k6Ak7zcTwoSoNm7aTtbk1g67MKAnsYLfUB7PdszbgR5Xi",
+ signature: "ef24c5225322217bb065b80bb54eb4f9206d764e3e16abab07f0a64d1cf477cc",
+ "body-mime" => file_fixture("../files/welcome.eml").read
+ }
+ end
+ end
+ end
+
+ test "raising when the configured Mailgun API key is blank" do
+ switch_key_to "" do
+ assert_raises ArgumentError do
+ travel_to "2018-10-09 15:15:00 EDT"
+ post rails_mailgun_inbound_emails_url, params: {
+ timestamp: 1539112500,
+ token: "7VwW7k6Ak7zcTwoSoNm7aTtbk1g67MKAnsYLfUB7PdszbgR5Xi",
+ signature: "ef24c5225322217bb065b80bb54eb4f9206d764e3e16abab07f0a64d1cf477cc",
+ "body-mime" => file_fixture("../files/welcome.eml").read
+ }
+ end
+ end
+ end
+
+ private
+ def switch_key_to(new_key)
+ previous_key, ENV["MAILGUN_INGRESS_API_KEY"] = ENV["MAILGUN_INGRESS_API_KEY"], new_key
+ yield
+ ensure
+ ENV["MAILGUN_INGRESS_API_KEY"] = previous_key
+ end
+end
diff --git a/actionmailbox/test/controllers/ingresses/mandrill/inbound_emails_controller_test.rb b/actionmailbox/test/controllers/ingresses/mandrill/inbound_emails_controller_test.rb
new file mode 100644
index 0000000000..40f956c930
--- /dev/null
+++ b/actionmailbox/test/controllers/ingresses/mandrill/inbound_emails_controller_test.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+ENV["MANDRILL_INGRESS_API_KEY"] = "1l9Qf7lutEf7h73VXfBwhw"
+
+class ActionMailbox::Ingresses::Mandrill::InboundEmailsControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ ActionMailbox.ingress = :mandrill
+ @events = JSON.generate([{ event: "inbound", msg: { raw_msg: file_fixture("../files/welcome.eml").read } }])
+ end
+
+ test "receiving an inbound email from Mandrill" do
+ assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do
+ post rails_mandrill_inbound_emails_url,
+ headers: { "X-Mandrill-Signature" => "gldscd2tAb/G+DmpiLcwukkLrC4=" }, params: { mandrill_events: @events }
+ end
+
+ assert_response :ok
+
+ inbound_email = ActionMailbox::InboundEmail.last
+ assert_equal file_fixture("../files/welcome.eml").read, inbound_email.raw_email.download
+ assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id
+ end
+
+ test "rejecting a forged inbound email from Mandrill" do
+ assert_no_difference -> { ActionMailbox::InboundEmail.count } do
+ post rails_mandrill_inbound_emails_url,
+ headers: { "X-Mandrill-Signature" => "forged" }, params: { mandrill_events: @events }
+ end
+
+ assert_response :unauthorized
+ end
+
+ test "raising when Mandrill API key is nil" do
+ switch_key_to nil do
+ assert_raises ArgumentError do
+ post rails_mandrill_inbound_emails_url,
+ headers: { "X-Mandrill-Signature" => "gldscd2tAb/G+DmpiLcwukkLrC4=" }, params: { mandrill_events: @events }
+ end
+ end
+ end
+
+ test "raising when Mandrill API key is blank" do
+ switch_key_to "" do
+ assert_raises ArgumentError do
+ post rails_mandrill_inbound_emails_url,
+ headers: { "X-Mandrill-Signature" => "gldscd2tAb/G+DmpiLcwukkLrC4=" }, params: { mandrill_events: @events }
+ end
+ end
+ end
+
+ private
+ def switch_key_to(new_key)
+ previous_key, ENV["MANDRILL_INGRESS_API_KEY"] = ENV["MANDRILL_INGRESS_API_KEY"], new_key
+ yield
+ ensure
+ ENV["MANDRILL_INGRESS_API_KEY"] = previous_key
+ end
+end
diff --git a/actionmailbox/test/controllers/ingresses/postmark/inbound_emails_controller_test.rb b/actionmailbox/test/controllers/ingresses/postmark/inbound_emails_controller_test.rb
new file mode 100644
index 0000000000..11b579b39c
--- /dev/null
+++ b/actionmailbox/test/controllers/ingresses/postmark/inbound_emails_controller_test.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionMailbox::Ingresses::Postmark::InboundEmailsControllerTest < ActionDispatch::IntegrationTest
+ setup { ActionMailbox.ingress = :postmark }
+
+ test "receiving an inbound email from Postmark" do
+ assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do
+ post rails_postmark_inbound_emails_url,
+ headers: { authorization: credentials }, params: { RawEmail: file_fixture("../files/welcome.eml").read }
+ end
+
+ assert_response :no_content
+
+ inbound_email = ActionMailbox::InboundEmail.last
+ assert_equal file_fixture("../files/welcome.eml").read, inbound_email.raw_email.download
+ assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id
+ end
+
+ test "rejecting when RawEmail param is missing" do
+ assert_no_difference -> { ActionMailbox::InboundEmail.count } do
+ post rails_postmark_inbound_emails_url,
+ headers: { authorization: credentials }, params: { From: "someone@example.com" }
+ end
+
+ assert_response :unprocessable_entity
+ end
+
+ test "rejecting an unauthorized inbound email from Postmark" do
+ assert_no_difference -> { ActionMailbox::InboundEmail.count } do
+ post rails_postmark_inbound_emails_url, params: { RawEmail: file_fixture("../files/welcome.eml").read }
+ end
+
+ assert_response :unauthorized
+ end
+
+ test "raising when the configured password is nil" do
+ switch_password_to nil do
+ assert_raises ArgumentError do
+ post rails_postmark_inbound_emails_url,
+ headers: { authorization: credentials }, params: { RawEmail: file_fixture("../files/welcome.eml").read }
+ end
+ end
+ end
+
+ test "raising when the configured password is blank" do
+ switch_password_to "" do
+ assert_raises ArgumentError do
+ post rails_postmark_inbound_emails_url,
+ headers: { authorization: credentials }, params: { RawEmail: file_fixture("../files/welcome.eml").read }
+ end
+ end
+ end
+end
diff --git a/actionmailbox/test/controllers/ingresses/relay/inbound_emails_controller_test.rb b/actionmailbox/test/controllers/ingresses/relay/inbound_emails_controller_test.rb
new file mode 100644
index 0000000000..67c5993f7f
--- /dev/null
+++ b/actionmailbox/test/controllers/ingresses/relay/inbound_emails_controller_test.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionMailbox::Ingresses::Relay::InboundEmailsControllerTest < ActionDispatch::IntegrationTest
+ setup { ActionMailbox.ingress = :relay }
+
+ test "receiving an inbound email relayed from an SMTP server" do
+ assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do
+ post rails_relay_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "message/rfc822" },
+ params: file_fixture("../files/welcome.eml").read
+ end
+
+ assert_response :no_content
+
+ inbound_email = ActionMailbox::InboundEmail.last
+ assert_equal file_fixture("../files/welcome.eml").read, inbound_email.raw_email.download
+ assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id
+ end
+
+ test "rejecting an unauthorized inbound email" do
+ assert_no_difference -> { ActionMailbox::InboundEmail.count } do
+ post rails_relay_inbound_emails_url, headers: { "Content-Type" => "message/rfc822" },
+ params: file_fixture("../files/welcome.eml").read
+ end
+
+ assert_response :unauthorized
+ end
+
+ test "rejecting an inbound email of an unsupported media type" do
+ assert_no_difference -> { ActionMailbox::InboundEmail.count } do
+ post rails_relay_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "text/plain" },
+ params: file_fixture("../files/welcome.eml").read
+ end
+
+ assert_response :unsupported_media_type
+ end
+
+ test "raising when the configured password is nil" do
+ switch_password_to nil do
+ assert_raises ArgumentError do
+ post rails_relay_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "message/rfc822" },
+ params: file_fixture("../files/welcome.eml").read
+ end
+ end
+ end
+
+ test "raising when the configured password is blank" do
+ switch_password_to "" do
+ assert_raises ArgumentError do
+ post rails_relay_inbound_emails_url, headers: { "Authorization" => credentials, "Content-Type" => "message/rfc822" },
+ params: file_fixture("../files/welcome.eml").read
+ end
+ end
+ end
+end
diff --git a/actionmailbox/test/controllers/ingresses/sendgrid/inbound_emails_controller_test.rb b/actionmailbox/test/controllers/ingresses/sendgrid/inbound_emails_controller_test.rb
new file mode 100644
index 0000000000..59a87e9248
--- /dev/null
+++ b/actionmailbox/test/controllers/ingresses/sendgrid/inbound_emails_controller_test.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionMailbox::Ingresses::Sendgrid::InboundEmailsControllerTest < ActionDispatch::IntegrationTest
+ setup { ActionMailbox.ingress = :sendgrid }
+
+ test "receiving an inbound email from Sendgrid" do
+ assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do
+ post rails_sendgrid_inbound_emails_url,
+ headers: { authorization: credentials }, params: { email: file_fixture("../files/welcome.eml").read }
+ end
+
+ assert_response :no_content
+
+ inbound_email = ActionMailbox::InboundEmail.last
+ assert_equal file_fixture("../files/welcome.eml").read, inbound_email.raw_email.download
+ assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id
+ end
+
+ test "rejecting an unauthorized inbound email from Sendgrid" do
+ assert_no_difference -> { ActionMailbox::InboundEmail.count } do
+ post rails_sendgrid_inbound_emails_url, params: { email: file_fixture("../files/welcome.eml").read }
+ end
+
+ assert_response :unauthorized
+ end
+
+ test "raising when the configured password is nil" do
+ switch_password_to nil do
+ assert_raises ArgumentError do
+ post rails_sendgrid_inbound_emails_url,
+ headers: { authorization: credentials }, params: { email: file_fixture("../files/welcome.eml").read }
+ end
+ end
+ end
+
+ test "raising when the configured password is blank" do
+ switch_password_to "" do
+ assert_raises ArgumentError do
+ post rails_sendgrid_inbound_emails_url,
+ headers: { authorization: credentials }, params: { email: file_fixture("../files/welcome.eml").read }
+ end
+ end
+ end
+end
diff --git a/actionmailbox/test/controllers/rails/action_mailbox/inbound_emails_controller_test.rb b/actionmailbox/test/controllers/rails/action_mailbox/inbound_emails_controller_test.rb
new file mode 100644
index 0000000000..fcd9ad839f
--- /dev/null
+++ b/actionmailbox/test/controllers/rails/action_mailbox/inbound_emails_controller_test.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class Rails::Conductor::ActionMailbox::InboundEmailsControllerTest < ActionDispatch::IntegrationTest
+ test "create inbound email" do
+ with_rails_env("development") do
+ assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do
+ post rails_conductor_inbound_emails_path, params: {
+ mail: {
+ from: "Jason Fried <jason@37signals.com>",
+ to: "Replies <replies@example.com>",
+ in_reply_to: "<4e6e35f5a38b4_479f13bb90078178@small-app-01.mail>",
+ subject: "Hey there",
+ body: "How's it going?"
+ }
+ }
+ end
+
+ mail = ActionMailbox::InboundEmail.last.mail
+ assert_equal %w[ jason@37signals.com ], mail.from
+ assert_equal %w[ replies@example.com ], mail.to
+ assert_equal "4e6e35f5a38b4_479f13bb90078178@small-app-01.mail", mail.in_reply_to
+ assert_equal "Hey there", mail.subject
+ assert_equal "How's it going?", mail.body.decoded
+ end
+ end
+
+ test "create inbound email with attachments" do
+ with_rails_env("development") do
+ assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do
+ post rails_conductor_inbound_emails_path, params: {
+ mail: {
+ from: "Jason Fried <jason@37signals.com>",
+ to: "Replies <replies@example.com>",
+ subject: "Let's debate some attachments",
+ body: "Let's talk about these images:",
+ attachments: [ fixture_file_upload("files/avatar1.jpeg"), fixture_file_upload("files/avatar2.jpeg") ]
+ }
+ }
+ end
+
+ mail = ActionMailbox::InboundEmail.last.mail
+ assert_equal "Let's talk about these images:", mail.text_part.decoded
+ assert_equal 2, mail.attachments.count
+ end
+ end
+
+ private
+ def with_rails_env(env)
+ old_rails_env = Rails.env
+ Rails.env = env
+ yield
+ ensure
+ Rails.env = old_rails_env
+ end
+end
diff --git a/actionmailbox/test/dummy/.babelrc b/actionmailbox/test/dummy/.babelrc
new file mode 100644
index 0000000000..ded31c0d80
--- /dev/null
+++ b/actionmailbox/test/dummy/.babelrc
@@ -0,0 +1,18 @@
+{
+ "presets": [
+ ["env", {
+ "modules": false,
+ "targets": {
+ "browsers": "> 1%",
+ "uglify": true
+ },
+ "useBuiltIns": true
+ }]
+ ],
+
+ "plugins": [
+ "syntax-dynamic-import",
+ "transform-object-rest-spread",
+ ["transform-class-properties", { "spec": true }]
+ ]
+}
diff --git a/actionmailbox/test/dummy/.postcssrc.yml b/actionmailbox/test/dummy/.postcssrc.yml
new file mode 100644
index 0000000000..150dac3c6c
--- /dev/null
+++ b/actionmailbox/test/dummy/.postcssrc.yml
@@ -0,0 +1,3 @@
+plugins:
+ postcss-import: {}
+ postcss-cssnext: {}
diff --git a/actionmailbox/test/dummy/Rakefile b/actionmailbox/test/dummy/Rakefile
new file mode 100644
index 0000000000..e85f913914
--- /dev/null
+++ b/actionmailbox/test/dummy/Rakefile
@@ -0,0 +1,6 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require_relative 'config/application'
+
+Rails.application.load_tasks
diff --git a/actionmailbox/test/dummy/app/assets/config/manifest.js b/actionmailbox/test/dummy/app/assets/config/manifest.js
new file mode 100644
index 0000000000..b16e53d6d5
--- /dev/null
+++ b/actionmailbox/test/dummy/app/assets/config/manifest.js
@@ -0,0 +1,3 @@
+//= link_tree ../images
+//= link_directory ../javascripts .js
+//= link_directory ../stylesheets .css
diff --git a/actionmailbox/test/dummy/app/assets/images/.keep b/actionmailbox/test/dummy/app/assets/images/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailbox/test/dummy/app/assets/images/.keep
diff --git a/actionmailbox/test/dummy/app/assets/stylesheets/application.css b/actionmailbox/test/dummy/app/assets/stylesheets/application.css
new file mode 100644
index 0000000000..0ebd7fe829
--- /dev/null
+++ b/actionmailbox/test/dummy/app/assets/stylesheets/application.css
@@ -0,0 +1,15 @@
+/*
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
+ * listed below.
+ *
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
+ *
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
+ * files in this directory. Styles in this file should be added after the last require_* statement.
+ * It is generally better to create a new file per style scope.
+ *
+ *= require_tree .
+ *= require_self
+ */
diff --git a/actionmailbox/test/dummy/app/assets/stylesheets/scaffold.css b/actionmailbox/test/dummy/app/assets/stylesheets/scaffold.css
new file mode 100644
index 0000000000..cd4f3de38d
--- /dev/null
+++ b/actionmailbox/test/dummy/app/assets/stylesheets/scaffold.css
@@ -0,0 +1,80 @@
+body {
+ background-color: #fff;
+ color: #333;
+ margin: 33px;
+}
+
+body, p, ol, ul, td {
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+}
+
+pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+}
+
+a {
+ color: #000;
+}
+
+a:visited {
+ color: #666;
+}
+
+a:hover {
+ color: #fff;
+ background-color: #000;
+}
+
+th {
+ padding-bottom: 5px;
+}
+
+td {
+ padding: 0 5px 7px;
+}
+
+div.field,
+div.actions {
+ margin-bottom: 10px;
+}
+
+#notice {
+ color: green;
+}
+
+.field_with_errors {
+ padding: 2px;
+ background-color: red;
+ display: table;
+}
+
+#error_explanation {
+ width: 450px;
+ border: 2px solid red;
+ padding: 7px 7px 0;
+ margin-bottom: 20px;
+ background-color: #f0f0f0;
+}
+
+#error_explanation h2 {
+ text-align: left;
+ font-weight: bold;
+ padding: 5px 5px 5px 15px;
+ font-size: 12px;
+ margin: -7px -7px 0;
+ background-color: #c00;
+ color: #fff;
+}
+
+#error_explanation ul li {
+ font-size: 12px;
+ list-style: square;
+}
+
+label {
+ display: block;
+}
diff --git a/actionmailbox/test/dummy/app/channels/application_cable/channel.rb b/actionmailbox/test/dummy/app/channels/application_cable/channel.rb
new file mode 100644
index 0000000000..d672697283
--- /dev/null
+++ b/actionmailbox/test/dummy/app/channels/application_cable/channel.rb
@@ -0,0 +1,4 @@
+module ApplicationCable
+ class Channel < ActionCable::Channel::Base
+ end
+end
diff --git a/actionmailbox/test/dummy/app/channels/application_cable/connection.rb b/actionmailbox/test/dummy/app/channels/application_cable/connection.rb
new file mode 100644
index 0000000000..0ff5442f47
--- /dev/null
+++ b/actionmailbox/test/dummy/app/channels/application_cable/connection.rb
@@ -0,0 +1,4 @@
+module ApplicationCable
+ class Connection < ActionCable::Connection::Base
+ end
+end
diff --git a/actionmailbox/test/dummy/app/controllers/application_controller.rb b/actionmailbox/test/dummy/app/controllers/application_controller.rb
new file mode 100644
index 0000000000..09705d12ab
--- /dev/null
+++ b/actionmailbox/test/dummy/app/controllers/application_controller.rb
@@ -0,0 +1,2 @@
+class ApplicationController < ActionController::Base
+end
diff --git a/actionmailbox/test/dummy/app/controllers/concerns/.keep b/actionmailbox/test/dummy/app/controllers/concerns/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailbox/test/dummy/app/controllers/concerns/.keep
diff --git a/actionmailbox/test/dummy/app/helpers/application_helper.rb b/actionmailbox/test/dummy/app/helpers/application_helper.rb
new file mode 100644
index 0000000000..de6be7945c
--- /dev/null
+++ b/actionmailbox/test/dummy/app/helpers/application_helper.rb
@@ -0,0 +1,2 @@
+module ApplicationHelper
+end
diff --git a/actionmailbox/test/dummy/app/javascript/packs/application.js b/actionmailbox/test/dummy/app/javascript/packs/application.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailbox/test/dummy/app/javascript/packs/application.js
diff --git a/actionmailbox/test/dummy/app/jobs/application_job.rb b/actionmailbox/test/dummy/app/jobs/application_job.rb
new file mode 100644
index 0000000000..a009ace51c
--- /dev/null
+++ b/actionmailbox/test/dummy/app/jobs/application_job.rb
@@ -0,0 +1,2 @@
+class ApplicationJob < ActiveJob::Base
+end
diff --git a/actionmailbox/test/dummy/app/mailboxes/application_mailbox.rb b/actionmailbox/test/dummy/app/mailboxes/application_mailbox.rb
new file mode 100644
index 0000000000..be51eb3639
--- /dev/null
+++ b/actionmailbox/test/dummy/app/mailboxes/application_mailbox.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ApplicationMailbox < ActionMailbox::Base
+ # routing /something/i => :somewhere
+end
diff --git a/actionmailbox/test/dummy/app/mailboxes/messages_mailbox.rb b/actionmailbox/test/dummy/app/mailboxes/messages_mailbox.rb
new file mode 100644
index 0000000000..1a046ee13d
--- /dev/null
+++ b/actionmailbox/test/dummy/app/mailboxes/messages_mailbox.rb
@@ -0,0 +1,4 @@
+class MessagesMailbox < ApplicationMailbox
+ def process
+ end
+end
diff --git a/actionmailbox/test/dummy/app/mailers/application_mailer.rb b/actionmailbox/test/dummy/app/mailers/application_mailer.rb
new file mode 100644
index 0000000000..286b2239d1
--- /dev/null
+++ b/actionmailbox/test/dummy/app/mailers/application_mailer.rb
@@ -0,0 +1,4 @@
+class ApplicationMailer < ActionMailer::Base
+ default from: 'from@example.com'
+ layout 'mailer'
+end
diff --git a/actionmailbox/test/dummy/app/models/application_record.rb b/actionmailbox/test/dummy/app/models/application_record.rb
new file mode 100644
index 0000000000..10a4cba84d
--- /dev/null
+++ b/actionmailbox/test/dummy/app/models/application_record.rb
@@ -0,0 +1,3 @@
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+end
diff --git a/actionmailbox/test/dummy/app/models/concerns/.keep b/actionmailbox/test/dummy/app/models/concerns/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailbox/test/dummy/app/models/concerns/.keep
diff --git a/actionmailbox/test/dummy/app/views/layouts/application.html.erb b/actionmailbox/test/dummy/app/views/layouts/application.html.erb
new file mode 100644
index 0000000000..f221f512b7
--- /dev/null
+++ b/actionmailbox/test/dummy/app/views/layouts/application.html.erb
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Dummy</title>
+ <%= csrf_meta_tags %>
+
+ <%= stylesheet_link_tag 'application', media: 'all' %>
+ <%= javascript_pack_tag 'application' %>
+ </head>
+
+ <body>
+ <%= yield %>
+ </body>
+</html>
diff --git a/actionmailbox/test/dummy/app/views/layouts/mailer.html.erb b/actionmailbox/test/dummy/app/views/layouts/mailer.html.erb
new file mode 100644
index 0000000000..cbd34d2e9d
--- /dev/null
+++ b/actionmailbox/test/dummy/app/views/layouts/mailer.html.erb
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <style>
+ /* Email styles need to be inline */
+ </style>
+ </head>
+
+ <body>
+ <%= yield %>
+ </body>
+</html>
diff --git a/actionmailbox/test/dummy/app/views/layouts/mailer.text.erb b/actionmailbox/test/dummy/app/views/layouts/mailer.text.erb
new file mode 100644
index 0000000000..37f0bddbd7
--- /dev/null
+++ b/actionmailbox/test/dummy/app/views/layouts/mailer.text.erb
@@ -0,0 +1 @@
+<%= yield %>
diff --git a/actionmailbox/test/dummy/bin/bundle b/actionmailbox/test/dummy/bin/bundle
new file mode 100755
index 0000000000..f19acf5b5c
--- /dev/null
+++ b/actionmailbox/test/dummy/bin/bundle
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
+load Gem.bin_path('bundler', 'bundle')
diff --git a/actionmailbox/test/dummy/bin/rails b/actionmailbox/test/dummy/bin/rails
new file mode 100755
index 0000000000..0739660237
--- /dev/null
+++ b/actionmailbox/test/dummy/bin/rails
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+APP_PATH = File.expand_path('../config/application', __dir__)
+require_relative '../config/boot'
+require 'rails/commands'
diff --git a/actionmailbox/test/dummy/bin/rake b/actionmailbox/test/dummy/bin/rake
new file mode 100755
index 0000000000..17240489f6
--- /dev/null
+++ b/actionmailbox/test/dummy/bin/rake
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+require_relative '../config/boot'
+require 'rake'
+Rake.application.run
diff --git a/actionmailbox/test/dummy/bin/setup b/actionmailbox/test/dummy/bin/setup
new file mode 100755
index 0000000000..94fd4d7977
--- /dev/null
+++ b/actionmailbox/test/dummy/bin/setup
@@ -0,0 +1,36 @@
+#!/usr/bin/env ruby
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = File.expand_path('..', __dir__)
+
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
+chdir APP_ROOT do
+ # This script is a starting point to setup your application.
+ # Add necessary setup steps to this file.
+
+ puts '== Installing dependencies =='
+ system! 'gem install bundler --conservative'
+ system('bundle check') || system!('bundle install')
+
+ # Install JavaScript dependencies if using Yarn
+ # system('bin/yarn')
+
+ # puts "\n== Copying sample files =="
+ # unless File.exist?('config/database.yml')
+ # cp 'config/database.yml.sample', 'config/database.yml'
+ # end
+
+ puts "\n== Preparing database =="
+ system! 'bin/rails db:setup'
+
+ puts "\n== Removing old logs and tempfiles =="
+ system! 'bin/rails log:clear tmp:clear'
+
+ puts "\n== Restarting application server =="
+ system! 'bin/rails restart'
+end
diff --git a/actionmailbox/test/dummy/bin/update b/actionmailbox/test/dummy/bin/update
new file mode 100755
index 0000000000..58bfaed518
--- /dev/null
+++ b/actionmailbox/test/dummy/bin/update
@@ -0,0 +1,31 @@
+#!/usr/bin/env ruby
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = File.expand_path('..', __dir__)
+
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
+chdir APP_ROOT do
+ # This script is a way to update your development environment automatically.
+ # Add necessary update steps to this file.
+
+ puts '== Installing dependencies =='
+ system! 'gem install bundler --conservative'
+ system('bundle check') || system!('bundle install')
+
+ # Install JavaScript dependencies if using Yarn
+ # system('bin/yarn')
+
+ puts "\n== Updating database =="
+ system! 'bin/rails db:migrate'
+
+ puts "\n== Removing old logs and tempfiles =="
+ system! 'bin/rails log:clear tmp:clear'
+
+ puts "\n== Restarting application server =="
+ system! 'bin/rails restart'
+end
diff --git a/actionmailbox/test/dummy/bin/yarn b/actionmailbox/test/dummy/bin/yarn
new file mode 100755
index 0000000000..460dd565b4
--- /dev/null
+++ b/actionmailbox/test/dummy/bin/yarn
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+APP_ROOT = File.expand_path('..', __dir__)
+Dir.chdir(APP_ROOT) do
+ begin
+ exec "yarnpkg", *ARGV
+ rescue Errno::ENOENT
+ $stderr.puts "Yarn executable was not detected in the system."
+ $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
+ exit 1
+ end
+end
diff --git a/actionmailbox/test/dummy/config.ru b/actionmailbox/test/dummy/config.ru
new file mode 100644
index 0000000000..f7ba0b527b
--- /dev/null
+++ b/actionmailbox/test/dummy/config.ru
@@ -0,0 +1,5 @@
+# This file is used by Rack-based servers to start the application.
+
+require_relative 'config/environment'
+
+run Rails.application
diff --git a/actionmailbox/test/dummy/config/application.rb b/actionmailbox/test/dummy/config/application.rb
new file mode 100644
index 0000000000..ea8ab1fc5e
--- /dev/null
+++ b/actionmailbox/test/dummy/config/application.rb
@@ -0,0 +1,17 @@
+require_relative 'boot'
+
+require 'rails/all'
+
+Bundler.require(*Rails.groups)
+
+module Dummy
+ class Application < Rails::Application
+ # Initialize configuration defaults for originally generated Rails version.
+ config.load_defaults 6.0
+
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration can go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded after loading
+ # the framework and any gems in your application.
+ end
+end
diff --git a/actionmailbox/test/dummy/config/boot.rb b/actionmailbox/test/dummy/config/boot.rb
new file mode 100644
index 0000000000..c9aef85d40
--- /dev/null
+++ b/actionmailbox/test/dummy/config/boot.rb
@@ -0,0 +1,5 @@
+# Set up gems listed in the Gemfile.
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
+
+require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
+$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
diff --git a/actionmailbox/test/dummy/config/cable.yml b/actionmailbox/test/dummy/config/cable.yml
new file mode 100644
index 0000000000..1cd0f8363e
--- /dev/null
+++ b/actionmailbox/test/dummy/config/cable.yml
@@ -0,0 +1,10 @@
+development:
+ adapter: async
+
+test:
+ adapter: async
+
+production:
+ adapter: redis
+ url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
+ channel_prefix: dummy_production
diff --git a/actionmailbox/test/dummy/config/database.yml b/actionmailbox/test/dummy/config/database.yml
new file mode 100644
index 0000000000..0d02f24980
--- /dev/null
+++ b/actionmailbox/test/dummy/config/database.yml
@@ -0,0 +1,25 @@
+# SQLite version 3.x
+# gem install sqlite3
+#
+# Ensure the SQLite 3 gem is defined in your Gemfile
+# gem 'sqlite3'
+#
+default: &default
+ adapter: sqlite3
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+ timeout: 5000
+
+development:
+ <<: *default
+ database: db/development.sqlite3
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ <<: *default
+ database: db/test.sqlite3
+
+production:
+ <<: *default
+ database: db/production.sqlite3
diff --git a/actionmailbox/test/dummy/config/environment.rb b/actionmailbox/test/dummy/config/environment.rb
new file mode 100644
index 0000000000..426333bb46
--- /dev/null
+++ b/actionmailbox/test/dummy/config/environment.rb
@@ -0,0 +1,5 @@
+# Load the Rails application.
+require_relative 'application'
+
+# Initialize the Rails application.
+Rails.application.initialize!
diff --git a/actionmailbox/test/dummy/config/environments/development.rb b/actionmailbox/test/dummy/config/environments/development.rb
new file mode 100644
index 0000000000..f09b9a00c4
--- /dev/null
+++ b/actionmailbox/test/dummy/config/environments/development.rb
@@ -0,0 +1,63 @@
+Rails.application.configure do
+ # Verifies that versions and hashed value of the package contents in the project's package.json
+ # config.webpacker.check_yarn_integrity = true
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for development
+ # since you don't have to restart the web server when you make code changes.
+ config.cache_classes = false
+
+ # Do not eager load code on boot.
+ config.eager_load = false
+
+ # Show full error reports.
+ config.consider_all_requests_local = true
+
+ # Enable/disable caching. By default caching is disabled.
+ # Run rails dev:cache to toggle caching.
+ if Rails.root.join('tmp', 'caching-dev.txt').exist?
+ config.action_controller.perform_caching = true
+
+ config.cache_store = :memory_store
+ config.public_file_server.headers = {
+ 'Cache-Control' => "public, max-age=#{2.days.to_i}"
+ }
+ else
+ config.action_controller.perform_caching = false
+
+ config.cache_store = :null_store
+ end
+
+ # Store uploaded files on the local file system (see config/storage.yml for options)
+ config.active_storage.service = :local
+
+ # Don't care if the mailer can't send.
+ config.action_mailer.raise_delivery_errors = false
+
+ config.action_mailer.perform_caching = false
+
+ # Print deprecation notices to the Rails logger.
+ config.active_support.deprecation = :log
+
+ # Raise an error on page load if there are pending migrations.
+ config.active_record.migration_error = :page_load
+
+ # Highlight code that triggered database queries in logs.
+ config.active_record.verbose_query_logs = true
+
+ # Debug mode disables concatenation and preprocessing of assets.
+ # This option may cause significant delays in view rendering with a large
+ # number of complex assets.
+ config.assets.debug = true
+
+ # Suppress logger output for asset requests.
+ config.assets.quiet = true
+
+ # Raises error for missing translations
+ # config.action_view.raise_on_missing_translations = true
+
+ # Use an evented file watcher to asynchronously detect changes in source code,
+ # routes, locales, etc. This feature depends on the listen gem.
+ # config.file_watcher = ActiveSupport::EventedFileUpdateChecker
+end
diff --git a/actionmailbox/test/dummy/config/environments/production.rb b/actionmailbox/test/dummy/config/environments/production.rb
new file mode 100644
index 0000000000..1731582220
--- /dev/null
+++ b/actionmailbox/test/dummy/config/environments/production.rb
@@ -0,0 +1,99 @@
+Rails.application.configure do
+ # Prepare the ingress controller used to receive mail
+ # config.action_mailbox.ingress = :amazon
+
+ # Verifies that versions and hashed value of the package contents in the project's package.json
+ config.webpacker.check_yarn_integrity = false
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # Code is not reloaded between requests.
+ config.cache_classes = true
+
+ # Eager load code on boot. This eager loads most of Rails and
+ # your application in memory, allowing both threaded web servers
+ # and those relying on copy on write to perform better.
+ # Rake tasks automatically ignore this option for performance.
+ config.eager_load = true
+
+ # Full error reports are disabled and caching is turned on.
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
+ # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
+ # config.require_master_key = true
+
+ # Disable serving static files from the `/public` folder by default since
+ # Apache or NGINX already handles this.
+ config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
+
+ # Compress JavaScripts and CSS.
+ config.assets.js_compressor = :uglifier
+ # config.assets.css_compressor = :sass
+
+ # Do not fallback to assets pipeline if a precompiled asset is missed.
+ config.assets.compile = false
+
+ # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
+
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
+ # config.action_controller.asset_host = 'http://assets.example.com'
+
+ # Specifies the header that your server uses for sending files.
+ # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
+
+ # Store uploaded files on the local file system (see config/storage.yml for options)
+ config.active_storage.service = :local
+
+ # Mount Action Cable outside main process or domain
+ # config.action_cable.mount_path = nil
+ # config.action_cable.url = 'wss://example.com/cable'
+ # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
+
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+ # config.force_ssl = true
+
+ # Use the lowest log level to ensure availability of diagnostic information
+ # when problems arise.
+ config.log_level = :debug
+
+ # Prepend all log lines with the following tags.
+ config.log_tags = [ :request_id ]
+
+ # Use a different cache store in production.
+ # config.cache_store = :mem_cache_store
+
+ # Use a real queuing backend for Active Job (and separate queues per environment)
+ # config.active_job.queue_adapter = :resque
+ # config.active_job.queue_name_prefix = "dummy_#{Rails.env}"
+
+ config.action_mailer.perform_caching = false
+
+ # Ignore bad email addresses and do not raise email delivery errors.
+ # Set this to true and configure the email server for immediate delivery to raise delivery errors.
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation cannot be found).
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners.
+ config.active_support.deprecation = :notify
+
+ # Use default logging formatter so that PID and timestamp are not suppressed.
+ config.log_formatter = ::Logger::Formatter.new
+
+ # Use a different logger for distributed setups.
+ # require 'syslog/logger'
+ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
+
+ if ENV["RAILS_LOG_TO_STDOUT"].present?
+ logger = ActiveSupport::Logger.new(STDOUT)
+ logger.formatter = config.log_formatter
+ config.logger = ActiveSupport::TaggedLogging.new(logger)
+ end
+
+ # Do not dump schema after migrations.
+ config.active_record.dump_schema_after_migration = false
+end
diff --git a/actionmailbox/test/dummy/config/environments/test.rb b/actionmailbox/test/dummy/config/environments/test.rb
new file mode 100644
index 0000000000..0a38fd3ce9
--- /dev/null
+++ b/actionmailbox/test/dummy/config/environments/test.rb
@@ -0,0 +1,46 @@
+Rails.application.configure do
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Do not eager load code on boot. This avoids loading your whole application
+ # just for the purpose of running a single test. If you are using a tool that
+ # preloads Rails for running tests, you may have to set it to true.
+ config.eager_load = false
+
+ # Configure public file server for tests with Cache-Control for performance.
+ config.public_file_server.enabled = true
+ config.public_file_server.headers = {
+ 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
+ }
+
+ # Show full error reports and disable caching.
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates.
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment.
+ config.action_controller.allow_forgery_protection = false
+
+ # Store uploaded files on the local file system in a temporary directory
+ config.active_storage.service = :test
+
+ config.action_mailer.perform_caching = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Print deprecation notices to the stderr.
+ config.active_support.deprecation = :stderr
+
+ # Raises error for missing translations
+ # config.action_view.raise_on_missing_translations = true
+end
diff --git a/actionmailbox/test/dummy/config/initializers/application_controller_renderer.rb b/actionmailbox/test/dummy/config/initializers/application_controller_renderer.rb
new file mode 100644
index 0000000000..89d2efab2b
--- /dev/null
+++ b/actionmailbox/test/dummy/config/initializers/application_controller_renderer.rb
@@ -0,0 +1,8 @@
+# Be sure to restart your server when you modify this file.
+
+# ActiveSupport::Reloader.to_prepare do
+# ApplicationController.renderer.defaults.merge!(
+# http_host: 'example.org',
+# https: false
+# )
+# end
diff --git a/actionmailbox/test/dummy/config/initializers/assets.rb b/actionmailbox/test/dummy/config/initializers/assets.rb
new file mode 100644
index 0000000000..4b828e80cb
--- /dev/null
+++ b/actionmailbox/test/dummy/config/initializers/assets.rb
@@ -0,0 +1,14 @@
+# Be sure to restart your server when you modify this file.
+
+# Version of your assets, change this if you want to expire all your assets.
+Rails.application.config.assets.version = '1.0'
+
+# Add additional assets to the asset load path.
+# Rails.application.config.assets.paths << Emoji.images_path
+# Add Yarn node_modules folder to the asset load path.
+Rails.application.config.assets.paths << Rails.root.join('node_modules')
+
+# Precompile additional assets.
+# application.js, application.css, and all non-JS/CSS in the app/assets
+# folder are already added.
+# Rails.application.config.assets.precompile += %w( admin.js admin.css )
diff --git a/actionmailbox/test/dummy/config/initializers/backtrace_silencers.rb b/actionmailbox/test/dummy/config/initializers/backtrace_silencers.rb
new file mode 100644
index 0000000000..59385cdf37
--- /dev/null
+++ b/actionmailbox/test/dummy/config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
diff --git a/actionmailbox/test/dummy/config/initializers/content_security_policy.rb b/actionmailbox/test/dummy/config/initializers/content_security_policy.rb
new file mode 100644
index 0000000000..edde7f42b8
--- /dev/null
+++ b/actionmailbox/test/dummy/config/initializers/content_security_policy.rb
@@ -0,0 +1,22 @@
+# Be sure to restart your server when you modify this file.
+
+# Define an application-wide content security policy
+# For further information see the following documentation
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
+
+# Rails.application.config.content_security_policy do |policy|
+# policy.default_src :self, :https
+# policy.font_src :self, :https, :data
+# policy.img_src :self, :https, :data
+# policy.object_src :none
+# policy.script_src :self, :https
+# policy.style_src :self, :https, :unsafe_inline
+
+# # Specify URI for violation reports
+# # policy.report_uri "/csp-violation-report-endpoint"
+# end
+
+# Report CSP violations to a specified URI
+# For further information see the following documentation:
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
+# Rails.application.config.content_security_policy_report_only = true
diff --git a/actionmailbox/test/dummy/config/initializers/cookies_serializer.rb b/actionmailbox/test/dummy/config/initializers/cookies_serializer.rb
new file mode 100644
index 0000000000..5a6a32d371
--- /dev/null
+++ b/actionmailbox/test/dummy/config/initializers/cookies_serializer.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Specify a serializer for the signed and encrypted cookie jars.
+# Valid options are :json, :marshal, and :hybrid.
+Rails.application.config.action_dispatch.cookies_serializer = :json
diff --git a/actionmailbox/test/dummy/config/initializers/filter_parameter_logging.rb b/actionmailbox/test/dummy/config/initializers/filter_parameter_logging.rb
new file mode 100644
index 0000000000..4a994e1e7b
--- /dev/null
+++ b/actionmailbox/test/dummy/config/initializers/filter_parameter_logging.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Configure sensitive parameters which will be filtered from the log file.
+Rails.application.config.filter_parameters += [:password]
diff --git a/actionmailbox/test/dummy/config/initializers/inflections.rb b/actionmailbox/test/dummy/config/initializers/inflections.rb
new file mode 100644
index 0000000000..ac033bf9dc
--- /dev/null
+++ b/actionmailbox/test/dummy/config/initializers/inflections.rb
@@ -0,0 +1,16 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format. Inflections
+# are locale specific, and you may define rules for as many different
+# locales as you wish. All of these examples are active by default:
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
+
+# These inflection rules are supported but not enabled by default:
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
+# inflect.acronym 'RESTful'
+# end
diff --git a/actionmailbox/test/dummy/config/initializers/mime_types.rb b/actionmailbox/test/dummy/config/initializers/mime_types.rb
new file mode 100644
index 0000000000..dc1899682b
--- /dev/null
+++ b/actionmailbox/test/dummy/config/initializers/mime_types.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
diff --git a/actionmailbox/test/dummy/config/initializers/wrap_parameters.rb b/actionmailbox/test/dummy/config/initializers/wrap_parameters.rb
new file mode 100644
index 0000000000..bbfc3961bf
--- /dev/null
+++ b/actionmailbox/test/dummy/config/initializers/wrap_parameters.rb
@@ -0,0 +1,14 @@
+# Be sure to restart your server when you modify this file.
+
+# This file contains settings for ActionController::ParamsWrapper which
+# is enabled by default.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters format: [:json]
+end
+
+# To enable root element in JSON for ActiveRecord objects.
+# ActiveSupport.on_load(:active_record) do
+# self.include_root_in_json = true
+# end
diff --git a/actionmailbox/test/dummy/config/locales/en.yml b/actionmailbox/test/dummy/config/locales/en.yml
new file mode 100644
index 0000000000..decc5a8573
--- /dev/null
+++ b/actionmailbox/test/dummy/config/locales/en.yml
@@ -0,0 +1,33 @@
+# Files in the config/locales directory are used for internationalization
+# and are automatically loaded by Rails. If you want to use locales other
+# than English, add the necessary files in this directory.
+#
+# To use the locales, use `I18n.t`:
+#
+# I18n.t 'hello'
+#
+# In views, this is aliased to just `t`:
+#
+# <%= t('hello') %>
+#
+# To use a different locale, set it with `I18n.locale`:
+#
+# I18n.locale = :es
+#
+# This would use the information in config/locales/es.yml.
+#
+# The following keys must be escaped otherwise they will not be retrieved by
+# the default I18n backend:
+#
+# true, false, on, off, yes, no
+#
+# Instead, surround them with single quotes.
+#
+# en:
+# 'true': 'foo'
+#
+# To learn more, please read the Rails Internationalization guide
+# available at http://guides.rubyonrails.org/i18n.html.
+
+en:
+ hello: "Hello world"
diff --git a/actionmailbox/test/dummy/config/puma.rb b/actionmailbox/test/dummy/config/puma.rb
new file mode 100644
index 0000000000..71ed69a895
--- /dev/null
+++ b/actionmailbox/test/dummy/config/puma.rb
@@ -0,0 +1,34 @@
+# Puma can serve each request in a thread from an internal thread pool.
+# The `threads` method setting takes two numbers: a minimum and maximum.
+# Any libraries that use thread pools should be configured to match
+# the maximum value specified for Puma. Default is set to 5 threads for minimum
+# and maximum; this matches the default thread size of Active Record.
+#
+threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
+threads threads_count, threads_count
+
+# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
+#
+port ENV.fetch("PORT") { 3000 }
+
+# Specifies the `environment` that Puma will run in.
+#
+environment ENV.fetch("RAILS_ENV") { "development" }
+
+# Specifies the number of `workers` to boot in clustered mode.
+# Workers are forked web server processes. If using threads and workers together
+# the concurrency of the application would be max `threads` * `workers`.
+# Workers do not work on JRuby or Windows (both of which do not support
+# processes).
+#
+# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
+
+# Use the `preload_app!` method when specifying a `workers` number.
+# This directive tells Puma to first boot the application and load code
+# before forking the application. This takes advantage of Copy On Write
+# process behavior so workers use less memory.
+#
+# preload_app!
+
+# Allow puma to be restarted by `rails restart` command.
+plugin :tmp_restart
diff --git a/actionmailbox/test/dummy/config/routes.rb b/actionmailbox/test/dummy/config/routes.rb
new file mode 100644
index 0000000000..30b05169b3
--- /dev/null
+++ b/actionmailbox/test/dummy/config/routes.rb
@@ -0,0 +1,4 @@
+Rails.application.routes.draw do
+ resources :messages
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+end
diff --git a/actionmailbox/test/dummy/config/spring.rb b/actionmailbox/test/dummy/config/spring.rb
new file mode 100644
index 0000000000..9fa7863f99
--- /dev/null
+++ b/actionmailbox/test/dummy/config/spring.rb
@@ -0,0 +1,6 @@
+%w[
+ .ruby-version
+ .rbenv-vars
+ tmp/restart.txt
+ tmp/caching-dev.txt
+].each { |path| Spring.watch(path) }
diff --git a/actionmailbox/test/dummy/config/storage.yml b/actionmailbox/test/dummy/config/storage.yml
new file mode 100644
index 0000000000..53e562e0fc
--- /dev/null
+++ b/actionmailbox/test/dummy/config/storage.yml
@@ -0,0 +1,35 @@
+test:
+ service: Disk
+ root: <%= Rails.root.join("tmp/storage") %>
+
+local:
+ service: Disk
+ root: <%= Rails.root.join("storage") %>
+
+# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
+# amazon:
+# service: S3
+# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
+# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
+# region: us-east-1
+# bucket: your_own_bucket
+
+# Remember not to checkin your GCS keyfile to a repository
+# google:
+# service: GCS
+# project: your_project
+# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
+# bucket: your_own_bucket
+
+# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
+# microsoft:
+# service: AzureStorage
+# path: your_azure_storage_path
+# storage_account_name: your_account_name
+# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
+# container: your_container_name
+
+# mirror:
+# service: Mirror
+# primary: local
+# mirrors: [ amazon, google, microsoft ]
diff --git a/actionmailbox/test/dummy/config/webpack/development.js b/actionmailbox/test/dummy/config/webpack/development.js
new file mode 100644
index 0000000000..81269f6513
--- /dev/null
+++ b/actionmailbox/test/dummy/config/webpack/development.js
@@ -0,0 +1,3 @@
+const environment = require('./environment')
+
+module.exports = environment.toWebpackConfig()
diff --git a/actionmailbox/test/dummy/config/webpack/environment.js b/actionmailbox/test/dummy/config/webpack/environment.js
new file mode 100644
index 0000000000..d16d9af743
--- /dev/null
+++ b/actionmailbox/test/dummy/config/webpack/environment.js
@@ -0,0 +1,3 @@
+const { environment } = require('@rails/webpacker')
+
+module.exports = environment
diff --git a/actionmailbox/test/dummy/config/webpack/production.js b/actionmailbox/test/dummy/config/webpack/production.js
new file mode 100644
index 0000000000..81269f6513
--- /dev/null
+++ b/actionmailbox/test/dummy/config/webpack/production.js
@@ -0,0 +1,3 @@
+const environment = require('./environment')
+
+module.exports = environment.toWebpackConfig()
diff --git a/actionmailbox/test/dummy/config/webpack/test.js b/actionmailbox/test/dummy/config/webpack/test.js
new file mode 100644
index 0000000000..81269f6513
--- /dev/null
+++ b/actionmailbox/test/dummy/config/webpack/test.js
@@ -0,0 +1,3 @@
+const environment = require('./environment')
+
+module.exports = environment.toWebpackConfig()
diff --git a/actionmailbox/test/dummy/config/webpacker.yml b/actionmailbox/test/dummy/config/webpacker.yml
new file mode 100644
index 0000000000..d3f24e1b4b
--- /dev/null
+++ b/actionmailbox/test/dummy/config/webpacker.yml
@@ -0,0 +1,65 @@
+# Note: You must restart bin/webpack-dev-server for changes to take effect
+
+default: &default
+ source_path: app/javascript
+ source_entry_path: packs
+ public_output_path: packs
+ cache_path: tmp/cache/webpacker
+
+ # Additional paths webpack should lookup modules
+ # ['app/assets', 'engine/foo/app/assets']
+ resolved_paths: []
+
+ # Reload manifest.json on all requests so we reload latest compiled packs
+ cache_manifest: false
+
+ extensions:
+ - .js
+ - .sass
+ - .scss
+ - .css
+ - .png
+ - .svg
+ - .gif
+ - .jpeg
+ - .jpg
+
+development:
+ <<: *default
+ compile: true
+
+ # Reference: https://webpack.js.org/configuration/dev-server/
+ dev_server:
+ https: false
+ host: localhost
+ port: 3035
+ public: localhost:3035
+ hmr: false
+ # Inline should be set to true if using HMR
+ inline: true
+ overlay: true
+ compress: true
+ disable_host_check: true
+ use_local_ip: false
+ quiet: false
+ headers:
+ 'Access-Control-Allow-Origin': '*'
+ watch_options:
+ ignored: /node_modules/
+
+
+test:
+ <<: *default
+ compile: true
+
+ # Compile test packs to a separate directory
+ public_output_path: packs-test
+
+production:
+ <<: *default
+
+ # Production depends on precompilation of packs prior to booting for performance.
+ compile: false
+
+ # Cache manifest.json for performance
+ cache_manifest: true
diff --git a/actionmailbox/test/dummy/db/migrate/20180208205311_create_action_mailbox_tables.rb b/actionmailbox/test/dummy/db/migrate/20180208205311_create_action_mailbox_tables.rb
new file mode 100644
index 0000000000..89ab66c1a9
--- /dev/null
+++ b/actionmailbox/test/dummy/db/migrate/20180208205311_create_action_mailbox_tables.rb
@@ -0,0 +1,17 @@
+class CreateActionMailboxTables < ActiveRecord::Migration[6.0]
+ def change
+ create_table :action_mailbox_inbound_emails do |t|
+ t.integer :status, default: 0, null: false
+ t.string :message_id, null: false
+ t.string :message_checksum, null: false
+
+ if supports_datetime_with_precision?
+ t.timestamps precision: 6
+ else
+ t.timestamps
+ end
+
+ t.index [ :message_id, :message_checksum ], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true
+ end
+ end
+end
diff --git a/actionmailbox/test/dummy/db/migrate/20180212164506_create_active_storage_tables.active_storage.rb b/actionmailbox/test/dummy/db/migrate/20180212164506_create_active_storage_tables.active_storage.rb
new file mode 100644
index 0000000000..0b2ce257c8
--- /dev/null
+++ b/actionmailbox/test/dummy/db/migrate/20180212164506_create_active_storage_tables.active_storage.rb
@@ -0,0 +1,27 @@
+# This migration comes from active_storage (originally 20170806125915)
+class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
+ def change
+ create_table :active_storage_blobs do |t|
+ t.string :key, null: false
+ t.string :filename, null: false
+ t.string :content_type
+ t.text :metadata
+ t.bigint :byte_size, null: false
+ t.string :checksum, null: false
+ t.datetime :created_at, null: false
+
+ t.index [ :key ], unique: true
+ end
+
+ create_table :active_storage_attachments do |t|
+ t.string :name, null: false
+ t.references :record, null: false, polymorphic: true, index: false
+ t.references :blob, null: false
+
+ t.datetime :created_at, null: false
+
+ t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
+ t.foreign_key :active_storage_blobs, column: :blob_id
+ end
+ end
+end
diff --git a/actionmailbox/test/dummy/db/schema.rb b/actionmailbox/test/dummy/db/schema.rb
new file mode 100644
index 0000000000..10d4111a89
--- /dev/null
+++ b/actionmailbox/test/dummy/db/schema.rb
@@ -0,0 +1,45 @@
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# This file is the source Rails uses to define your schema when running `rails
+# db:schema:load`. When creating a new database, `rails db:schema:load` tends to
+# be faster and is potentially less error prone than running all of your
+# migrations from scratch. Old migrations may fail to apply correctly if those
+# migrations use external dependencies or application code.
+#
+# It's strongly recommended that you check this file into your version control system.
+
+ActiveRecord::Schema.define(version: 2018_02_12_164506) do
+
+ create_table "action_mailbox_inbound_emails", force: :cascade do |t|
+ t.integer "status", default: 0, null: false
+ t.string "message_id", null: false
+ t.string "message_checksum", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["message_id", "message_checksum"], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true
+ end
+
+ create_table "active_storage_attachments", force: :cascade do |t|
+ t.string "name", null: false
+ t.string "record_type", null: false
+ t.integer "record_id", null: false
+ t.integer "blob_id", null: false
+ t.datetime "created_at", null: false
+ t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
+ t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
+ end
+
+ create_table "active_storage_blobs", force: :cascade do |t|
+ t.string "key", null: false
+ t.string "filename", null: false
+ t.string "content_type"
+ t.text "metadata"
+ t.bigint "byte_size", null: false
+ t.string "checksum", null: false
+ t.datetime "created_at", null: false
+ t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
+ end
+
+end
diff --git a/actionmailbox/test/dummy/lib/assets/.keep b/actionmailbox/test/dummy/lib/assets/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailbox/test/dummy/lib/assets/.keep
diff --git a/actionmailbox/test/dummy/log/.keep b/actionmailbox/test/dummy/log/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailbox/test/dummy/log/.keep
diff --git a/actionmailbox/test/dummy/package.json b/actionmailbox/test/dummy/package.json
new file mode 100644
index 0000000000..59fdfb8a60
--- /dev/null
+++ b/actionmailbox/test/dummy/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "dummy",
+ "private": true,
+ "dependencies": {
+ "@rails/webpacker": "^3.2.2",
+ "activetext": "file:../.."
+ },
+ "devDependencies": {
+ "webpack-dev-server": "^2.11.1"
+ }
+}
diff --git a/actionmailbox/test/dummy/public/404.html b/actionmailbox/test/dummy/public/404.html
new file mode 100644
index 0000000000..2be3af26fc
--- /dev/null
+++ b/actionmailbox/test/dummy/public/404.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The page you were looking for doesn't exist (404)</title>
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ <style>
+ .rails-default-error-page {
+ background-color: #EFEFEF;
+ color: #2E2F30;
+ text-align: center;
+ font-family: arial, sans-serif;
+ margin: 0;
+ }
+
+ .rails-default-error-page div.dialog {
+ width: 95%;
+ max-width: 33em;
+ margin: 4em auto 0;
+ }
+
+ .rails-default-error-page div.dialog > div {
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #BBB;
+ border-top: #B00100 solid 4px;
+ border-top-left-radius: 9px;
+ border-top-right-radius: 9px;
+ background-color: white;
+ padding: 7px 12% 0;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+
+ .rails-default-error-page h1 {
+ font-size: 100%;
+ color: #730E15;
+ line-height: 1.5em;
+ }
+
+ .rails-default-error-page div.dialog > p {
+ margin: 0 0 1em;
+ padding: 1em;
+ background-color: #F7F7F7;
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #999;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-top-color: #DADADA;
+ color: #666;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+ </style>
+</head>
+
+<body class="rails-default-error-page">
+ <!-- This file lives in public/404.html -->
+ <div class="dialog">
+ <div>
+ <h1>The page you were looking for doesn't exist.</h1>
+ <p>You may have mistyped the address or the page may have moved.</p>
+ </div>
+ <p>If you are the application owner check the logs for more information.</p>
+ </div>
+</body>
+</html>
diff --git a/actionmailbox/test/dummy/public/422.html b/actionmailbox/test/dummy/public/422.html
new file mode 100644
index 0000000000..c08eac0d1d
--- /dev/null
+++ b/actionmailbox/test/dummy/public/422.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The change you wanted was rejected (422)</title>
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ <style>
+ .rails-default-error-page {
+ background-color: #EFEFEF;
+ color: #2E2F30;
+ text-align: center;
+ font-family: arial, sans-serif;
+ margin: 0;
+ }
+
+ .rails-default-error-page div.dialog {
+ width: 95%;
+ max-width: 33em;
+ margin: 4em auto 0;
+ }
+
+ .rails-default-error-page div.dialog > div {
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #BBB;
+ border-top: #B00100 solid 4px;
+ border-top-left-radius: 9px;
+ border-top-right-radius: 9px;
+ background-color: white;
+ padding: 7px 12% 0;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+
+ .rails-default-error-page h1 {
+ font-size: 100%;
+ color: #730E15;
+ line-height: 1.5em;
+ }
+
+ .rails-default-error-page div.dialog > p {
+ margin: 0 0 1em;
+ padding: 1em;
+ background-color: #F7F7F7;
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #999;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-top-color: #DADADA;
+ color: #666;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+ </style>
+</head>
+
+<body class="rails-default-error-page">
+ <!-- This file lives in public/422.html -->
+ <div class="dialog">
+ <div>
+ <h1>The change you wanted was rejected.</h1>
+ <p>Maybe you tried to change something you didn't have access to.</p>
+ </div>
+ <p>If you are the application owner check the logs for more information.</p>
+ </div>
+</body>
+</html>
diff --git a/actionmailbox/test/dummy/public/500.html b/actionmailbox/test/dummy/public/500.html
new file mode 100644
index 0000000000..78a030af22
--- /dev/null
+++ b/actionmailbox/test/dummy/public/500.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>We're sorry, but something went wrong (500)</title>
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ <style>
+ .rails-default-error-page {
+ background-color: #EFEFEF;
+ color: #2E2F30;
+ text-align: center;
+ font-family: arial, sans-serif;
+ margin: 0;
+ }
+
+ .rails-default-error-page div.dialog {
+ width: 95%;
+ max-width: 33em;
+ margin: 4em auto 0;
+ }
+
+ .rails-default-error-page div.dialog > div {
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #BBB;
+ border-top: #B00100 solid 4px;
+ border-top-left-radius: 9px;
+ border-top-right-radius: 9px;
+ background-color: white;
+ padding: 7px 12% 0;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+
+ .rails-default-error-page h1 {
+ font-size: 100%;
+ color: #730E15;
+ line-height: 1.5em;
+ }
+
+ .rails-default-error-page div.dialog > p {
+ margin: 0 0 1em;
+ padding: 1em;
+ background-color: #F7F7F7;
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #999;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-top-color: #DADADA;
+ color: #666;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+ </style>
+</head>
+
+<body class="rails-default-error-page">
+ <!-- This file lives in public/500.html -->
+ <div class="dialog">
+ <div>
+ <h1>We're sorry, but something went wrong.</h1>
+ </div>
+ <p>If you are the application owner check the logs for more information.</p>
+ </div>
+</body>
+</html>
diff --git a/actionmailbox/test/dummy/public/apple-touch-icon-precomposed.png b/actionmailbox/test/dummy/public/apple-touch-icon-precomposed.png
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailbox/test/dummy/public/apple-touch-icon-precomposed.png
diff --git a/actionmailbox/test/dummy/public/apple-touch-icon.png b/actionmailbox/test/dummy/public/apple-touch-icon.png
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailbox/test/dummy/public/apple-touch-icon.png
diff --git a/actionmailbox/test/dummy/public/favicon.ico b/actionmailbox/test/dummy/public/favicon.ico
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailbox/test/dummy/public/favicon.ico
diff --git a/actionmailbox/test/dummy/storage/.keep b/actionmailbox/test/dummy/storage/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailbox/test/dummy/storage/.keep
diff --git a/actionmailbox/test/dummy/yarn.lock b/actionmailbox/test/dummy/yarn.lock
new file mode 100644
index 0000000000..af299bfcf8
--- /dev/null
+++ b/actionmailbox/test/dummy/yarn.lock
@@ -0,0 +1,6071 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@rails/webpacker@^3.2.2":
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-3.2.2.tgz#6d60e1cf729dc2ccc52053c9b6b8d30c9a48a297"
+ dependencies:
+ babel-core "^6.26.0"
+ babel-loader "^7.1.2"
+ babel-plugin-syntax-dynamic-import "^6.18.0"
+ babel-plugin-transform-class-properties "^6.24.1"
+ babel-plugin-transform-object-rest-spread "^6.26.0"
+ babel-polyfill "^6.26.0"
+ babel-preset-env "^1.6.1"
+ case-sensitive-paths-webpack-plugin "^2.1.1"
+ compression-webpack-plugin "^1.1.6"
+ css-loader "^0.28.9"
+ extract-text-webpack-plugin "^3.0.2"
+ file-loader "^1.1.6"
+ glob "^7.1.2"
+ js-yaml "^3.10.0"
+ node-sass "^4.7.2"
+ path-complete-extname "^0.1.0"
+ postcss-cssnext "^3.1.0"
+ postcss-import "^11.0.0"
+ postcss-loader "^2.1.0"
+ sass-loader "^6.0.6"
+ style-loader "^0.20.1"
+ uglifyjs-webpack-plugin "^1.1.8"
+ webpack "^3.10.0"
+ webpack-manifest-plugin "^1.3.2"
+
+abbrev@1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+
+accepts@~1.3.4:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f"
+ dependencies:
+ mime-types "~2.1.16"
+ negotiator "0.6.1"
+
+acorn-dynamic-import@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4"
+ dependencies:
+ acorn "^4.0.3"
+
+acorn@^4.0.3:
+ version "4.0.13"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
+
+acorn@^5.0.0:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102"
+
+"activestorage@>= 5.2.0-rc1":
+ version "5.2.0-rc1"
+ resolved "https://registry.yarnpkg.com/activestorage/-/activestorage-5.2.0-rc1.tgz#79898996eceb0f13575eff41fb109051fbfa49b0"
+
+"activetext@file:../..":
+ version "0.1"
+ dependencies:
+ activestorage ">= 5.2.0-rc1"
+ trix "^0.11.1"
+
+ajv-keywords@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
+
+ajv-keywords@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be"
+
+ajv@^4.9.1:
+ version "4.11.8"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
+ dependencies:
+ co "^4.6.0"
+ json-stable-stringify "^1.0.1"
+
+ajv@^5.0.0, ajv@^5.1.0:
+ version "5.5.2"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
+ dependencies:
+ co "^4.6.0"
+ fast-deep-equal "^1.0.0"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.3.0"
+
+ajv@^6.1.0:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.1.1.tgz#978d597fbc2b7d0e5a5c3ddeb149a682f2abfa0e"
+ dependencies:
+ fast-deep-equal "^1.0.0"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.3.0"
+
+align-text@^0.1.1, align-text@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
+ dependencies:
+ kind-of "^3.0.2"
+ longest "^1.0.1"
+ repeat-string "^1.5.2"
+
+alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
+
+amdefine@>=0.0.4:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
+
+ansi-html@0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
+
+ansi-regex@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+
+ansi-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+
+ansi-styles@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+
+ansi-styles@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
+ dependencies:
+ color-convert "^1.9.0"
+
+anymatch@^1.3.0:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
+ dependencies:
+ micromatch "^2.1.5"
+ normalize-path "^2.0.0"
+
+anymatch@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
+ dependencies:
+ micromatch "^3.1.4"
+ normalize-path "^2.1.1"
+
+aproba@^1.0.3, aproba@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
+
+are-we-there-yet@~1.1.2:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d"
+ dependencies:
+ delegates "^1.0.0"
+ readable-stream "^2.0.6"
+
+argparse@^1.0.7:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
+ dependencies:
+ sprintf-js "~1.0.2"
+
+arr-diff@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
+ dependencies:
+ arr-flatten "^1.0.1"
+
+arr-diff@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
+
+arr-flatten@^1.0.1, arr-flatten@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
+
+arr-union@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
+
+array-find-index@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
+
+array-flatten@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+
+array-flatten@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296"
+
+array-includes@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
+ dependencies:
+ define-properties "^1.1.2"
+ es-abstract "^1.7.0"
+
+array-union@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
+ dependencies:
+ array-uniq "^1.0.1"
+
+array-uniq@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
+
+array-unique@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
+
+array-unique@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
+
+asn1.js@^4.0.0:
+ version "4.9.2"
+ resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.2.tgz#8117ef4f7ed87cd8f89044b5bff97ac243a16c9a"
+ dependencies:
+ bn.js "^4.0.0"
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+
+asn1@~0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
+
+assert-plus@1.0.0, assert-plus@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
+
+assert-plus@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
+
+assert@^1.1.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
+ dependencies:
+ util "0.10.3"
+
+assign-symbols@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
+
+async-each@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
+
+async-foreach@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
+
+async@^1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
+
+async@^2.1.2, async@^2.1.5, async@^2.4.1:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
+ dependencies:
+ lodash "^4.14.0"
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+
+atob@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d"
+
+autoprefixer@^6.3.1:
+ version "6.7.7"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014"
+ dependencies:
+ browserslist "^1.7.6"
+ caniuse-db "^1.0.30000634"
+ normalize-range "^0.1.2"
+ num2fraction "^1.2.2"
+ postcss "^5.2.16"
+ postcss-value-parser "^3.2.3"
+
+autoprefixer@^7.1.1:
+ version "7.2.6"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.2.6.tgz#256672f86f7c735da849c4f07d008abb056067dc"
+ dependencies:
+ browserslist "^2.11.3"
+ caniuse-lite "^1.0.30000805"
+ normalize-range "^0.1.2"
+ num2fraction "^1.2.2"
+ postcss "^6.0.17"
+ postcss-value-parser "^3.2.3"
+
+aws-sign2@~0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
+
+aws-sign2@~0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
+
+aws4@^1.2.1, aws4@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
+
+babel-code-frame@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
+ dependencies:
+ chalk "^1.1.3"
+ esutils "^2.0.2"
+ js-tokens "^3.0.2"
+
+babel-core@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8"
+ dependencies:
+ babel-code-frame "^6.26.0"
+ babel-generator "^6.26.0"
+ babel-helpers "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-register "^6.26.0"
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ convert-source-map "^1.5.0"
+ debug "^2.6.8"
+ json5 "^0.5.1"
+ lodash "^4.17.4"
+ minimatch "^3.0.4"
+ path-is-absolute "^1.0.1"
+ private "^0.1.7"
+ slash "^1.0.0"
+ source-map "^0.5.6"
+
+babel-generator@^6.26.0:
+ version "6.26.1"
+ resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
+ dependencies:
+ babel-messages "^6.23.0"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ detect-indent "^4.0.0"
+ jsesc "^1.3.0"
+ lodash "^4.17.4"
+ source-map "^0.5.7"
+ trim-right "^1.0.1"
+
+babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
+ dependencies:
+ babel-helper-explode-assignable-expression "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-call-delegate@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
+ dependencies:
+ babel-helper-hoist-variables "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-define-map@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f"
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-helper-explode-assignable-expression@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-function-name@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
+ dependencies:
+ babel-helper-get-function-arity "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-get-function-arity@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-hoist-variables@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-optimise-call-expression@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-regex@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72"
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-helper-remap-async-to-generator@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b"
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-replace-supers@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a"
+ dependencies:
+ babel-helper-optimise-call-expression "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helpers@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-loader@^7.1.2:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.2.tgz#f6cbe122710f1aa2af4d881c6d5b54358ca24126"
+ dependencies:
+ find-cache-dir "^1.0.0"
+ loader-utils "^1.0.2"
+ mkdirp "^0.5.1"
+
+babel-messages@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-check-es2015-constants@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-syntax-async-functions@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
+
+babel-plugin-syntax-class-properties@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de"
+
+babel-plugin-syntax-dynamic-import@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
+
+babel-plugin-syntax-exponentiation-operator@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
+
+babel-plugin-syntax-object-rest-spread@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
+
+babel-plugin-syntax-trailing-function-commas@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
+
+babel-plugin-transform-async-to-generator@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
+ dependencies:
+ babel-helper-remap-async-to-generator "^6.24.1"
+ babel-plugin-syntax-async-functions "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-class-properties@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac"
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-plugin-syntax-class-properties "^6.8.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-arrow-functions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-block-scoping@^6.23.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-plugin-transform-es2015-classes@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
+ dependencies:
+ babel-helper-define-map "^6.24.1"
+ babel-helper-function-name "^6.24.1"
+ babel-helper-optimise-call-expression "^6.24.1"
+ babel-helper-replace-supers "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-computed-properties@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-destructuring@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-duplicate-keys@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-for-of@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-function-name@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-literals@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
+ dependencies:
+ babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
+ dependencies:
+ babel-plugin-transform-strict-mode "^6.24.1"
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-types "^6.26.0"
+
+babel-plugin-transform-es2015-modules-systemjs@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
+ dependencies:
+ babel-helper-hoist-variables "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-modules-umd@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
+ dependencies:
+ babel-plugin-transform-es2015-modules-amd "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-object-super@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
+ dependencies:
+ babel-helper-replace-supers "^6.24.1"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-parameters@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
+ dependencies:
+ babel-helper-call-delegate "^6.24.1"
+ babel-helper-get-function-arity "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-shorthand-properties@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-spread@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-sticky-regex@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
+ dependencies:
+ babel-helper-regex "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-template-literals@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-unicode-regex@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
+ dependencies:
+ babel-helper-regex "^6.24.1"
+ babel-runtime "^6.22.0"
+ regexpu-core "^2.0.0"
+
+babel-plugin-transform-exponentiation-operator@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
+ dependencies:
+ babel-helper-builder-binary-assignment-operator-visitor "^6.24.1"
+ babel-plugin-syntax-exponentiation-operator "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-object-rest-spread@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06"
+ dependencies:
+ babel-plugin-syntax-object-rest-spread "^6.8.0"
+ babel-runtime "^6.26.0"
+
+babel-plugin-transform-regenerator@^6.22.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
+ dependencies:
+ regenerator-transform "^0.10.0"
+
+babel-plugin-transform-strict-mode@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-polyfill@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153"
+ dependencies:
+ babel-runtime "^6.26.0"
+ core-js "^2.5.0"
+ regenerator-runtime "^0.10.5"
+
+babel-preset-env@^1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.1.tgz#a18b564cc9b9afdf4aae57ae3c1b0d99188e6f48"
+ dependencies:
+ babel-plugin-check-es2015-constants "^6.22.0"
+ babel-plugin-syntax-trailing-function-commas "^6.22.0"
+ babel-plugin-transform-async-to-generator "^6.22.0"
+ babel-plugin-transform-es2015-arrow-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoping "^6.23.0"
+ babel-plugin-transform-es2015-classes "^6.23.0"
+ babel-plugin-transform-es2015-computed-properties "^6.22.0"
+ babel-plugin-transform-es2015-destructuring "^6.23.0"
+ babel-plugin-transform-es2015-duplicate-keys "^6.22.0"
+ babel-plugin-transform-es2015-for-of "^6.23.0"
+ babel-plugin-transform-es2015-function-name "^6.22.0"
+ babel-plugin-transform-es2015-literals "^6.22.0"
+ babel-plugin-transform-es2015-modules-amd "^6.22.0"
+ babel-plugin-transform-es2015-modules-commonjs "^6.23.0"
+ babel-plugin-transform-es2015-modules-systemjs "^6.23.0"
+ babel-plugin-transform-es2015-modules-umd "^6.23.0"
+ babel-plugin-transform-es2015-object-super "^6.22.0"
+ babel-plugin-transform-es2015-parameters "^6.23.0"
+ babel-plugin-transform-es2015-shorthand-properties "^6.22.0"
+ babel-plugin-transform-es2015-spread "^6.22.0"
+ babel-plugin-transform-es2015-sticky-regex "^6.22.0"
+ babel-plugin-transform-es2015-template-literals "^6.22.0"
+ babel-plugin-transform-es2015-typeof-symbol "^6.23.0"
+ babel-plugin-transform-es2015-unicode-regex "^6.22.0"
+ babel-plugin-transform-exponentiation-operator "^6.22.0"
+ babel-plugin-transform-regenerator "^6.22.0"
+ browserslist "^2.1.2"
+ invariant "^2.2.2"
+ semver "^5.3.0"
+
+babel-register@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
+ dependencies:
+ babel-core "^6.26.0"
+ babel-runtime "^6.26.0"
+ core-js "^2.5.0"
+ home-or-tmp "^2.0.0"
+ lodash "^4.17.4"
+ mkdirp "^0.5.1"
+ source-map-support "^0.4.15"
+
+babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
+ dependencies:
+ core-js "^2.4.0"
+ regenerator-runtime "^0.11.0"
+
+babel-template@^6.24.1, babel-template@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ lodash "^4.17.4"
+
+babel-traverse@^6.24.1, babel-traverse@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
+ dependencies:
+ babel-code-frame "^6.26.0"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ debug "^2.6.8"
+ globals "^9.18.0"
+ invariant "^2.2.2"
+ lodash "^4.17.4"
+
+babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
+ dependencies:
+ babel-runtime "^6.26.0"
+ esutils "^2.0.2"
+ lodash "^4.17.4"
+ to-fast-properties "^1.0.3"
+
+babylon@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
+
+balanced-match@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.1.0.tgz#b504bd05869b39259dd0c5efc35d843176dccc4a"
+
+balanced-match@^0.4.2:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+
+base64-js@^1.0.2:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
+
+base@^0.11.1:
+ version "0.11.2"
+ resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
+ dependencies:
+ cache-base "^1.0.1"
+ class-utils "^0.3.5"
+ component-emitter "^1.2.1"
+ define-property "^1.0.0"
+ isobject "^3.0.1"
+ mixin-deep "^1.2.0"
+ pascalcase "^0.1.1"
+
+batch@0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
+
+bcrypt-pbkdf@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
+ dependencies:
+ tweetnacl "^0.14.3"
+
+big.js@^3.1.3:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
+
+binary-extensions@^1.0.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
+
+block-stream@*:
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
+ dependencies:
+ inherits "~2.0.0"
+
+bluebird@^3.5.0:
+ version "3.5.1"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
+
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
+ version "4.11.8"
+ resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
+
+body-parser@1.18.2:
+ version "1.18.2"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454"
+ dependencies:
+ bytes "3.0.0"
+ content-type "~1.0.4"
+ debug "2.6.9"
+ depd "~1.1.1"
+ http-errors "~1.6.2"
+ iconv-lite "0.4.19"
+ on-finished "~2.3.0"
+ qs "6.5.1"
+ raw-body "2.3.2"
+ type-is "~1.6.15"
+
+bonjour@^3.5.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
+ dependencies:
+ array-flatten "^2.1.0"
+ deep-equal "^1.0.1"
+ dns-equal "^1.0.0"
+ dns-txt "^2.0.2"
+ multicast-dns "^6.0.1"
+ multicast-dns-service-types "^1.1.0"
+
+boom@2.x.x:
+ version "2.10.1"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
+ dependencies:
+ hoek "2.x.x"
+
+boom@4.x.x:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
+ dependencies:
+ hoek "4.x.x"
+
+boom@5.x.x:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
+ dependencies:
+ hoek "4.x.x"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+braces@^1.8.2:
+ version "1.8.5"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
+ dependencies:
+ expand-range "^1.8.1"
+ preserve "^0.2.0"
+ repeat-element "^1.1.2"
+
+braces@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.0.tgz#a46941cb5fb492156b3d6a656e06c35364e3e66e"
+ dependencies:
+ arr-flatten "^1.1.0"
+ array-unique "^0.3.2"
+ define-property "^1.0.0"
+ extend-shallow "^2.0.1"
+ fill-range "^4.0.0"
+ isobject "^3.0.1"
+ repeat-element "^1.1.2"
+ snapdragon "^0.8.1"
+ snapdragon-node "^2.0.1"
+ split-string "^3.0.2"
+ to-regex "^3.0.1"
+
+brorand@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
+
+browserify-aes@^1.0.0, browserify-aes@^1.0.4:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f"
+ dependencies:
+ buffer-xor "^1.0.3"
+ cipher-base "^1.0.0"
+ create-hash "^1.1.0"
+ evp_bytestokey "^1.0.3"
+ inherits "^2.0.1"
+ safe-buffer "^5.0.1"
+
+browserify-cipher@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a"
+ dependencies:
+ browserify-aes "^1.0.4"
+ browserify-des "^1.0.0"
+ evp_bytestokey "^1.0.0"
+
+browserify-des@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd"
+ dependencies:
+ cipher-base "^1.0.1"
+ des.js "^1.0.0"
+ inherits "^2.0.1"
+
+browserify-rsa@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
+ dependencies:
+ bn.js "^4.1.0"
+ randombytes "^2.0.1"
+
+browserify-sign@^4.0.0:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298"
+ dependencies:
+ bn.js "^4.1.1"
+ browserify-rsa "^4.0.0"
+ create-hash "^1.1.0"
+ create-hmac "^1.1.2"
+ elliptic "^6.0.0"
+ inherits "^2.0.1"
+ parse-asn1 "^5.0.0"
+
+browserify-zlib@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
+ dependencies:
+ pako "~1.0.5"
+
+browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
+ version "1.7.7"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9"
+ dependencies:
+ caniuse-db "^1.0.30000639"
+ electron-to-chromium "^1.2.7"
+
+browserslist@^2.0.0, browserslist@^2.1.2, browserslist@^2.11.3:
+ version "2.11.3"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.11.3.tgz#fe36167aed1bbcde4827ebfe71347a2cc70b99b2"
+ dependencies:
+ caniuse-lite "^1.0.30000792"
+ electron-to-chromium "^1.3.30"
+
+buffer-indexof@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
+
+buffer-xor@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
+
+buffer@^4.3.0:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
+ dependencies:
+ base64-js "^1.0.2"
+ ieee754 "^1.1.4"
+ isarray "^1.0.0"
+
+builtin-modules@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+
+builtin-status-codes@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
+
+bytes@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
+
+cacache@^10.0.1:
+ version "10.0.2"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.2.tgz#105a93a162bbedf3a25da42e1939ed99ffb145f8"
+ dependencies:
+ bluebird "^3.5.0"
+ chownr "^1.0.1"
+ glob "^7.1.2"
+ graceful-fs "^4.1.11"
+ lru-cache "^4.1.1"
+ mississippi "^1.3.0"
+ mkdirp "^0.5.1"
+ move-concurrently "^1.0.1"
+ promise-inflight "^1.0.1"
+ rimraf "^2.6.1"
+ ssri "^5.0.0"
+ unique-filename "^1.1.0"
+ y18n "^3.2.1"
+
+cache-base@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
+ dependencies:
+ collection-visit "^1.0.0"
+ component-emitter "^1.2.1"
+ get-value "^2.0.6"
+ has-value "^1.0.0"
+ isobject "^3.0.1"
+ set-value "^2.0.0"
+ to-object-path "^0.3.0"
+ union-value "^1.0.0"
+ unset-value "^1.0.0"
+
+camelcase-keys@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
+ dependencies:
+ camelcase "^2.0.0"
+ map-obj "^1.0.0"
+
+camelcase@^1.0.2:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
+
+camelcase@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
+
+camelcase@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
+
+camelcase@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
+
+caniuse-api@^1.5.2:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"
+ dependencies:
+ browserslist "^1.3.6"
+ caniuse-db "^1.0.30000529"
+ lodash.memoize "^4.1.2"
+ lodash.uniq "^4.5.0"
+
+caniuse-api@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-2.0.0.tgz#b1ddb5a5966b16f48dc4998444d4bbc6c7d9d834"
+ dependencies:
+ browserslist "^2.0.0"
+ caniuse-lite "^1.0.0"
+ lodash.memoize "^4.1.2"
+ lodash.uniq "^4.5.0"
+
+caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
+ version "1.0.30000808"
+ resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000808.tgz#30dfd83009d5704f02dffb37725068ed12a366bb"
+
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805:
+ version "1.0.30000808"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000808.tgz#7d759b5518529ea08b6705a19e70dbf401628ffc"
+
+case-sensitive-paths-webpack-plugin@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.1.1.tgz#3d29ced8c1f124bf6f53846fb3f5894731fdc909"
+
+caseless@~0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
+
+caseless@~0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
+
+center-align@^0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
+ dependencies:
+ align-text "^0.1.3"
+ lazy-cache "^1.0.3"
+
+chalk@^1.1.1, chalk@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
+ dependencies:
+ ansi-styles "^2.2.1"
+ escape-string-regexp "^1.0.2"
+ has-ansi "^2.0.0"
+ strip-ansi "^3.0.0"
+ supports-color "^2.0.0"
+
+chalk@^2.0.1, chalk@^2.3.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
+ dependencies:
+ ansi-styles "^3.2.0"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.2.0"
+
+chokidar@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
+ dependencies:
+ anymatch "^1.3.0"
+ async-each "^1.0.0"
+ glob-parent "^2.0.0"
+ inherits "^2.0.1"
+ is-binary-path "^1.0.0"
+ is-glob "^2.0.0"
+ path-is-absolute "^1.0.0"
+ readdirp "^2.0.0"
+ optionalDependencies:
+ fsevents "^1.0.0"
+
+chokidar@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.1.tgz#6e67e9998fe10e8f651e975ca62460456ff8e297"
+ dependencies:
+ anymatch "^2.0.0"
+ async-each "^1.0.0"
+ braces "^2.3.0"
+ glob-parent "^3.1.0"
+ inherits "^2.0.1"
+ is-binary-path "^1.0.0"
+ is-glob "^4.0.0"
+ normalize-path "^2.1.1"
+ path-is-absolute "^1.0.0"
+ readdirp "^2.0.0"
+ upath "1.0.0"
+ optionalDependencies:
+ fsevents "^1.0.0"
+
+chownr@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
+
+cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
+ dependencies:
+ inherits "^2.0.1"
+ safe-buffer "^5.0.1"
+
+clap@^1.0.9:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51"
+ dependencies:
+ chalk "^1.1.3"
+
+class-utils@^0.3.5:
+ version "0.3.6"
+ resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
+ dependencies:
+ arr-union "^3.1.0"
+ define-property "^0.2.5"
+ isobject "^3.0.0"
+ static-extend "^0.1.1"
+
+cliui@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
+ dependencies:
+ center-align "^0.1.1"
+ right-align "^0.1.1"
+ wordwrap "0.0.2"
+
+cliui@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wrap-ansi "^2.0.0"
+
+clone-deep@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.3.0.tgz#348c61ae9cdbe0edfe053d91ff4cc521d790ede8"
+ dependencies:
+ for-own "^1.0.0"
+ is-plain-object "^2.0.1"
+ kind-of "^3.2.2"
+ shallow-clone "^0.1.2"
+
+clone@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f"
+
+co@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+
+coa@~1.0.1:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd"
+ dependencies:
+ q "^1.1.2"
+
+code-point-at@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+
+collection-visit@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
+ dependencies:
+ map-visit "^1.0.0"
+ object-visit "^1.0.0"
+
+color-convert@^1.3.0, color-convert@^1.8.2, color-convert@^1.9.0, color-convert@^1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed"
+ dependencies:
+ color-name "^1.1.1"
+
+color-name@^1.0.0, color-name@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+
+color-string@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
+ dependencies:
+ color-name "^1.0.0"
+
+color-string@^1.4.0, color-string@^1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.2.tgz#26e45814bc3c9a7cbd6751648a41434514a773a9"
+ dependencies:
+ color-name "^1.0.0"
+ simple-swizzle "^0.2.2"
+
+color@^0.11.0:
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764"
+ dependencies:
+ clone "^1.0.2"
+ color-convert "^1.3.0"
+ color-string "^0.3.0"
+
+color@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/color/-/color-1.0.3.tgz#e48e832d85f14ef694fb468811c2d5cfe729b55d"
+ dependencies:
+ color-convert "^1.8.2"
+ color-string "^1.4.0"
+
+color@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color/-/color-2.0.1.tgz#e4ed78a3c4603d0891eba5430b04b86314f4c839"
+ dependencies:
+ color-convert "^1.9.1"
+ color-string "^1.5.2"
+
+colormin@^1.0.5:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133"
+ dependencies:
+ color "^0.11.0"
+ css-color-names "0.0.4"
+ has "^1.0.1"
+
+colors@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
+
+combined-stream@^1.0.5, combined-stream@~1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
+ dependencies:
+ delayed-stream "~1.0.0"
+
+commander@^2.9.0, commander@~2.14.1:
+ version "2.14.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
+
+commondir@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
+
+component-emitter@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
+
+compressible@~2.0.11:
+ version "2.0.12"
+ resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66"
+ dependencies:
+ mime-db ">= 1.30.0 < 2"
+
+compression-webpack-plugin@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-1.1.6.tgz#450808fe143b4c5216a14f0c315c47bec3d83cec"
+ dependencies:
+ async "^2.4.1"
+ cacache "^10.0.1"
+ find-cache-dir "^1.0.0"
+ serialize-javascript "^1.4.0"
+ webpack-sources "^1.0.1"
+
+compression@^1.5.2:
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.1.tgz#eff2603efc2e22cf86f35d2eb93589f9875373db"
+ dependencies:
+ accepts "~1.3.4"
+ bytes "3.0.0"
+ compressible "~2.0.11"
+ debug "2.6.9"
+ on-headers "~1.0.1"
+ safe-buffer "5.1.1"
+ vary "~1.1.2"
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+
+concat-stream@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
+ dependencies:
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
+connect-history-api-fallback@^1.3.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a"
+
+console-browserify@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
+ dependencies:
+ date-now "^0.1.4"
+
+console-control-strings@^1.0.0, console-control-strings@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+
+constants-browserify@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
+
+content-disposition@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+
+content-type@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+
+convert-source-map@^1.5.0:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
+
+cookie-signature@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+
+cookie@0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
+
+copy-concurrently@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
+ dependencies:
+ aproba "^1.1.1"
+ fs-write-stream-atomic "^1.0.8"
+ iferr "^0.1.5"
+ mkdirp "^0.5.1"
+ rimraf "^2.5.4"
+ run-queue "^1.0.0"
+
+copy-descriptor@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
+
+core-js@^2.4.0, core-js@^2.5.0:
+ version "2.5.3"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
+
+core-util-is@1.0.2, core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+
+cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.2.2.tgz#6173cebd56fac042c1f4390edf7af6c07c7cb892"
+ dependencies:
+ is-directory "^0.3.1"
+ js-yaml "^3.4.3"
+ minimist "^1.2.0"
+ object-assign "^4.1.0"
+ os-homedir "^1.0.1"
+ parse-json "^2.2.0"
+ require-from-string "^1.1.0"
+
+create-ecdh@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
+ dependencies:
+ bn.js "^4.1.0"
+ elliptic "^6.0.0"
+
+create-hash@^1.1.0, create-hash@^1.1.2:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd"
+ dependencies:
+ cipher-base "^1.0.1"
+ inherits "^2.0.1"
+ ripemd160 "^2.0.0"
+ sha.js "^2.4.0"
+
+create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06"
+ dependencies:
+ cipher-base "^1.0.3"
+ create-hash "^1.1.0"
+ inherits "^2.0.1"
+ ripemd160 "^2.0.0"
+ safe-buffer "^5.0.1"
+ sha.js "^2.4.8"
+
+cross-spawn@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
+ dependencies:
+ lru-cache "^4.0.1"
+ which "^1.2.9"
+
+cross-spawn@^5.0.1:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
+ dependencies:
+ lru-cache "^4.0.1"
+ shebang-command "^1.2.0"
+ which "^1.2.9"
+
+cryptiles@2.x.x:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
+ dependencies:
+ boom "2.x.x"
+
+cryptiles@3.x.x:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
+ dependencies:
+ boom "5.x.x"
+
+crypto-browserify@^3.11.0:
+ version "3.12.0"
+ resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
+ dependencies:
+ browserify-cipher "^1.0.0"
+ browserify-sign "^4.0.0"
+ create-ecdh "^4.0.0"
+ create-hash "^1.1.0"
+ create-hmac "^1.1.0"
+ diffie-hellman "^5.0.0"
+ inherits "^2.0.1"
+ pbkdf2 "^3.0.3"
+ public-encrypt "^4.0.0"
+ randombytes "^2.0.0"
+ randomfill "^1.0.3"
+
+css-color-function@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/css-color-function/-/css-color-function-1.3.3.tgz#8ed24c2c0205073339fafa004bc8c141fccb282e"
+ dependencies:
+ balanced-match "0.1.0"
+ color "^0.11.0"
+ debug "^3.1.0"
+ rgb "~0.1.0"
+
+css-color-names@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
+
+css-loader@^0.28.9:
+ version "0.28.9"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.9.tgz#68064b85f4e271d7ce4c48a58300928e535d1c95"
+ dependencies:
+ babel-code-frame "^6.26.0"
+ css-selector-tokenizer "^0.7.0"
+ cssnano "^3.10.0"
+ icss-utils "^2.1.0"
+ loader-utils "^1.0.2"
+ lodash.camelcase "^4.3.0"
+ object-assign "^4.1.1"
+ postcss "^5.0.6"
+ postcss-modules-extract-imports "^1.2.0"
+ postcss-modules-local-by-default "^1.2.0"
+ postcss-modules-scope "^1.1.0"
+ postcss-modules-values "^1.3.0"
+ postcss-value-parser "^3.3.0"
+ source-list-map "^2.0.0"
+
+css-selector-tokenizer@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86"
+ dependencies:
+ cssesc "^0.1.0"
+ fastparse "^1.1.1"
+ regexpu-core "^1.0.0"
+
+css-unit-converter@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996"
+
+cssesc@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
+
+cssnano@^3.10.0:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38"
+ dependencies:
+ autoprefixer "^6.3.1"
+ decamelize "^1.1.2"
+ defined "^1.0.0"
+ has "^1.0.1"
+ object-assign "^4.0.1"
+ postcss "^5.0.14"
+ postcss-calc "^5.2.0"
+ postcss-colormin "^2.1.8"
+ postcss-convert-values "^2.3.4"
+ postcss-discard-comments "^2.0.4"
+ postcss-discard-duplicates "^2.0.1"
+ postcss-discard-empty "^2.0.1"
+ postcss-discard-overridden "^0.1.1"
+ postcss-discard-unused "^2.2.1"
+ postcss-filter-plugins "^2.0.0"
+ postcss-merge-idents "^2.1.5"
+ postcss-merge-longhand "^2.0.1"
+ postcss-merge-rules "^2.0.3"
+ postcss-minify-font-values "^1.0.2"
+ postcss-minify-gradients "^1.0.1"
+ postcss-minify-params "^1.0.4"
+ postcss-minify-selectors "^2.0.4"
+ postcss-normalize-charset "^1.1.0"
+ postcss-normalize-url "^3.0.7"
+ postcss-ordered-values "^2.1.0"
+ postcss-reduce-idents "^2.2.2"
+ postcss-reduce-initial "^1.0.0"
+ postcss-reduce-transforms "^1.0.3"
+ postcss-svgo "^2.1.1"
+ postcss-unique-selectors "^2.0.2"
+ postcss-value-parser "^3.2.3"
+ postcss-zindex "^2.0.1"
+
+csso@~2.3.1:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85"
+ dependencies:
+ clap "^1.0.9"
+ source-map "^0.5.3"
+
+currently-unhandled@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
+ dependencies:
+ array-find-index "^1.0.1"
+
+cyclist@~0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
+
+d@1:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
+ dependencies:
+ es5-ext "^0.10.9"
+
+dashdash@^1.12.0:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
+ dependencies:
+ assert-plus "^1.0.0"
+
+date-now@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
+
+debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ dependencies:
+ ms "2.0.0"
+
+debug@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ dependencies:
+ ms "2.0.0"
+
+decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+
+decode-uri-component@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
+
+deep-equal@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
+
+deep-extend@~0.4.0:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
+
+define-properties@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
+ dependencies:
+ foreach "^2.0.5"
+ object-keys "^1.0.8"
+
+define-property@^0.2.5:
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
+ dependencies:
+ is-descriptor "^0.1.0"
+
+define-property@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6"
+ dependencies:
+ is-descriptor "^1.0.0"
+
+defined@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+
+del@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5"
+ dependencies:
+ globby "^6.1.0"
+ is-path-cwd "^1.0.0"
+ is-path-in-cwd "^1.0.0"
+ p-map "^1.1.1"
+ pify "^3.0.0"
+ rimraf "^2.2.8"
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+
+delegates@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+
+depd@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
+
+depd@~1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
+
+des.js@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
+ dependencies:
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+
+destroy@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+
+detect-indent@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
+ dependencies:
+ repeating "^2.0.0"
+
+detect-libc@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
+
+detect-node@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127"
+
+diffie-hellman@^5.0.0:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
+ dependencies:
+ bn.js "^4.1.0"
+ miller-rabin "^4.0.0"
+ randombytes "^2.0.0"
+
+dns-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
+
+dns-packet@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a"
+ dependencies:
+ ip "^1.1.0"
+ safe-buffer "^5.0.1"
+
+dns-txt@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6"
+ dependencies:
+ buffer-indexof "^1.0.0"
+
+domain-browser@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
+
+duplexify@^3.4.2, duplexify@^3.5.3:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.3.tgz#8b5818800df92fd0125b27ab896491912858243e"
+ dependencies:
+ end-of-stream "^1.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+ stream-shift "^1.0.0"
+
+ecc-jsbn@~0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
+ dependencies:
+ jsbn "~0.1.0"
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+
+electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.30:
+ version "1.3.33"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.33.tgz#bf00703d62a7c65238136578c352d6c5c042a545"
+
+elliptic@^6.0.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
+ dependencies:
+ bn.js "^4.4.0"
+ brorand "^1.0.1"
+ hash.js "^1.0.0"
+ hmac-drbg "^1.0.0"
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+ minimalistic-crypto-utils "^1.0.0"
+
+emojis-list@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
+
+encodeurl@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+
+end-of-stream@^1.0.0, end-of-stream@^1.1.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
+ dependencies:
+ once "^1.4.0"
+
+enhanced-resolve@^3.4.0:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e"
+ dependencies:
+ graceful-fs "^4.1.2"
+ memory-fs "^0.4.0"
+ object-assign "^4.0.1"
+ tapable "^0.2.7"
+
+errno@^0.1.3, errno@^0.1.4:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.6.tgz#c386ce8a6283f14fc09563b71560908c9bf53026"
+ dependencies:
+ prr "~1.0.1"
+
+error-ex@^1.2.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
+ dependencies:
+ is-arrayish "^0.2.1"
+
+es-abstract@^1.7.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864"
+ dependencies:
+ es-to-primitive "^1.1.1"
+ function-bind "^1.1.1"
+ has "^1.0.1"
+ is-callable "^1.1.3"
+ is-regex "^1.0.4"
+
+es-to-primitive@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
+ dependencies:
+ is-callable "^1.1.1"
+ is-date-object "^1.0.1"
+ is-symbol "^1.0.1"
+
+es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
+ version "0.10.38"
+ resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.38.tgz#fa7d40d65bbc9bb8a67e1d3f9cc656a00530eed3"
+ dependencies:
+ es6-iterator "~2.0.3"
+ es6-symbol "~3.1.1"
+
+es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
+ dependencies:
+ d "1"
+ es5-ext "^0.10.35"
+ es6-symbol "^3.1.1"
+
+es6-map@^0.1.3:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
+ dependencies:
+ d "1"
+ es5-ext "~0.10.14"
+ es6-iterator "~2.0.1"
+ es6-set "~0.1.5"
+ es6-symbol "~3.1.1"
+ event-emitter "~0.3.5"
+
+es6-set@~0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"
+ dependencies:
+ d "1"
+ es5-ext "~0.10.14"
+ es6-iterator "~2.0.1"
+ es6-symbol "3.1.1"
+ event-emitter "~0.3.5"
+
+es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
+ dependencies:
+ d "1"
+ es5-ext "~0.10.14"
+
+es6-weak-map@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f"
+ dependencies:
+ d "1"
+ es5-ext "^0.10.14"
+ es6-iterator "^2.0.1"
+ es6-symbol "^3.1.1"
+
+escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+
+escope@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3"
+ dependencies:
+ es6-map "^0.1.3"
+ es6-weak-map "^2.0.1"
+ esrecurse "^4.1.0"
+ estraverse "^4.1.1"
+
+esprima@^2.6.0:
+ version "2.7.3"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
+
+esprima@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
+
+esrecurse@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163"
+ dependencies:
+ estraverse "^4.1.0"
+ object-assign "^4.0.1"
+
+estraverse@^4.1.0, estraverse@^4.1.1:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
+
+esutils@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
+
+etag@~1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+
+event-emitter@~0.3.5:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
+ dependencies:
+ d "1"
+ es5-ext "~0.10.14"
+
+eventemitter3@1.x.x:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
+
+events@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
+
+eventsource@0.1.6:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232"
+ dependencies:
+ original ">=0.0.5"
+
+evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
+ dependencies:
+ md5.js "^1.3.4"
+ safe-buffer "^5.1.1"
+
+execa@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
+ dependencies:
+ cross-spawn "^5.0.1"
+ get-stream "^3.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
+expand-brackets@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
+ dependencies:
+ is-posix-bracket "^0.1.0"
+
+expand-brackets@^2.1.4:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
+ dependencies:
+ debug "^2.3.3"
+ define-property "^0.2.5"
+ extend-shallow "^2.0.1"
+ posix-character-classes "^0.1.0"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.1"
+
+expand-range@^1.8.1:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
+ dependencies:
+ fill-range "^2.1.0"
+
+express@^4.16.2:
+ version "4.16.2"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c"
+ dependencies:
+ accepts "~1.3.4"
+ array-flatten "1.1.1"
+ body-parser "1.18.2"
+ content-disposition "0.5.2"
+ content-type "~1.0.4"
+ cookie "0.3.1"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "~1.1.1"
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ finalhandler "1.1.0"
+ fresh "0.5.2"
+ merge-descriptors "1.0.1"
+ methods "~1.1.2"
+ on-finished "~2.3.0"
+ parseurl "~1.3.2"
+ path-to-regexp "0.1.7"
+ proxy-addr "~2.0.2"
+ qs "6.5.1"
+ range-parser "~1.2.0"
+ safe-buffer "5.1.1"
+ send "0.16.1"
+ serve-static "1.13.1"
+ setprototypeof "1.1.0"
+ statuses "~1.3.1"
+ type-is "~1.6.15"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
+
+extend-shallow@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
+ dependencies:
+ is-extendable "^0.1.0"
+
+extend-shallow@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8"
+ dependencies:
+ assign-symbols "^1.0.0"
+ is-extendable "^1.0.1"
+
+extend@~3.0.0, extend@~3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
+
+extglob@^0.3.1:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
+ dependencies:
+ is-extglob "^1.0.0"
+
+extglob@^2.0.2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
+ dependencies:
+ array-unique "^0.3.2"
+ define-property "^1.0.0"
+ expand-brackets "^2.1.4"
+ extend-shallow "^2.0.1"
+ fragment-cache "^0.2.1"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.1"
+
+extract-text-webpack-plugin@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz#5f043eaa02f9750a9258b78c0a6e0dc1408fb2f7"
+ dependencies:
+ async "^2.4.1"
+ loader-utils "^1.1.0"
+ schema-utils "^0.3.0"
+ webpack-sources "^1.0.1"
+
+extsprintf@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
+
+extsprintf@^1.2.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
+
+fast-deep-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
+
+fastparse@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
+
+faye-websocket@^0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+faye-websocket@~0.11.0:
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38"
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+file-loader@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.6.tgz#7b9a8f2c58f00a77fddf49e940f7ac978a3ea0e8"
+ dependencies:
+ loader-utils "^1.0.2"
+ schema-utils "^0.3.0"
+
+filename-regex@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
+
+fill-range@^2.1.0:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
+ dependencies:
+ is-number "^2.1.0"
+ isobject "^2.0.0"
+ randomatic "^1.1.3"
+ repeat-element "^1.1.2"
+ repeat-string "^1.5.2"
+
+fill-range@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
+ dependencies:
+ extend-shallow "^2.0.1"
+ is-number "^3.0.0"
+ repeat-string "^1.6.1"
+ to-regex-range "^2.1.0"
+
+finalhandler@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5"
+ dependencies:
+ debug "2.6.9"
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ on-finished "~2.3.0"
+ parseurl "~1.3.2"
+ statuses "~1.3.1"
+ unpipe "~1.0.0"
+
+find-cache-dir@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
+ dependencies:
+ commondir "^1.0.1"
+ make-dir "^1.0.0"
+ pkg-dir "^2.0.0"
+
+find-up@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
+ dependencies:
+ path-exists "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+find-up@^2.0.0, find-up@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
+ dependencies:
+ locate-path "^2.0.0"
+
+flatten@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
+
+flush-write-stream@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417"
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "^2.0.4"
+
+for-in@^0.1.3:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
+
+for-in@^1.0.1, for-in@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
+
+for-own@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
+ dependencies:
+ for-in "^1.0.1"
+
+for-own@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b"
+ dependencies:
+ for-in "^1.0.1"
+
+foreach@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
+
+forever-agent@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
+
+form-data@~2.1.1:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.5"
+ mime-types "^2.1.12"
+
+form-data@~2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.5"
+ mime-types "^2.1.12"
+
+forwarded@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
+
+fragment-cache@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
+ dependencies:
+ map-cache "^0.2.2"
+
+fresh@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+
+from2@^2.1.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+
+fs-extra@^0.30.0:
+ version "0.30.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0"
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^2.1.0"
+ klaw "^1.0.0"
+ path-is-absolute "^1.0.0"
+ rimraf "^2.2.8"
+
+fs-write-stream-atomic@^1.0.8:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
+ dependencies:
+ graceful-fs "^4.1.2"
+ iferr "^0.1.5"
+ imurmurhash "^0.1.4"
+ readable-stream "1 || 2"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+
+fsevents@^1.0.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8"
+ dependencies:
+ nan "^2.3.0"
+ node-pre-gyp "^0.6.39"
+
+fstream-ignore@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
+ dependencies:
+ fstream "^1.0.0"
+ inherits "2"
+ minimatch "^3.0.0"
+
+fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
+ dependencies:
+ graceful-fs "^4.1.2"
+ inherits "~2.0.0"
+ mkdirp ">=0.5 0"
+ rimraf "2"
+
+function-bind@^1.0.2, function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+
+gauge@~2.7.3:
+ version "2.7.4"
+ resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
+ dependencies:
+ aproba "^1.0.3"
+ console-control-strings "^1.0.0"
+ has-unicode "^2.0.0"
+ object-assign "^4.1.0"
+ signal-exit "^3.0.0"
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wide-align "^1.1.0"
+
+gaze@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.2.tgz#847224677adb8870d679257ed3388fdb61e40105"
+ dependencies:
+ globule "^1.0.0"
+
+generate-function@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
+
+generate-object-property@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
+ dependencies:
+ is-property "^1.0.0"
+
+get-caller-file@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
+
+get-stdin@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
+
+get-stream@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
+
+get-value@^2.0.3, get-value@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
+
+getpass@^0.1.1:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
+ dependencies:
+ assert-plus "^1.0.0"
+
+glob-base@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
+ dependencies:
+ glob-parent "^2.0.0"
+ is-glob "^2.0.0"
+
+glob-parent@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
+ dependencies:
+ is-glob "^2.0.0"
+
+glob-parent@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
+ dependencies:
+ is-glob "^3.1.0"
+ path-dirname "^1.0.0"
+
+glob@^6.0.4:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
+ dependencies:
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "2 || 3"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2, glob@~7.1.1:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
+ 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"
+
+globals@^9.18.0:
+ version "9.18.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
+
+globby@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
+ dependencies:
+ array-union "^1.0.1"
+ glob "^7.0.3"
+ object-assign "^4.0.1"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+globule@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09"
+ dependencies:
+ glob "~7.1.1"
+ lodash "~4.17.4"
+ minimatch "~3.0.2"
+
+graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
+ version "4.1.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
+
+handle-thing@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
+
+har-schema@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
+
+har-schema@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
+
+har-validator@~2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
+ dependencies:
+ chalk "^1.1.1"
+ commander "^2.9.0"
+ is-my-json-valid "^2.12.4"
+ pinkie-promise "^2.0.0"
+
+har-validator@~4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
+ dependencies:
+ ajv "^4.9.1"
+ har-schema "^1.0.5"
+
+har-validator@~5.0.3:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
+ dependencies:
+ ajv "^5.1.0"
+ har-schema "^2.0.0"
+
+has-ansi@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+has-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
+
+has-flag@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+
+has-unicode@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+
+has-value@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
+ dependencies:
+ get-value "^2.0.3"
+ has-values "^0.1.4"
+ isobject "^2.0.0"
+
+has-value@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177"
+ dependencies:
+ get-value "^2.0.6"
+ has-values "^1.0.0"
+ isobject "^3.0.0"
+
+has-values@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771"
+
+has-values@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f"
+ dependencies:
+ is-number "^3.0.0"
+ kind-of "^4.0.0"
+
+has@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
+ dependencies:
+ function-bind "^1.0.2"
+
+hash-base@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1"
+ dependencies:
+ inherits "^2.0.1"
+
+hash-base@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918"
+ dependencies:
+ inherits "^2.0.1"
+ safe-buffer "^5.0.1"
+
+hash.js@^1.0.0, hash.js@^1.0.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846"
+ dependencies:
+ inherits "^2.0.3"
+ minimalistic-assert "^1.0.0"
+
+hawk@3.1.3, hawk@~3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
+ dependencies:
+ boom "2.x.x"
+ cryptiles "2.x.x"
+ hoek "2.x.x"
+ sntp "1.x.x"
+
+hawk@~6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
+ dependencies:
+ boom "4.x.x"
+ cryptiles "3.x.x"
+ hoek "4.x.x"
+ sntp "2.x.x"
+
+hmac-drbg@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
+ dependencies:
+ hash.js "^1.0.3"
+ minimalistic-assert "^1.0.0"
+ minimalistic-crypto-utils "^1.0.1"
+
+hoek@2.x.x:
+ version "2.16.3"
+ resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
+
+hoek@4.x.x:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
+
+home-or-tmp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.1"
+
+hosted-git-info@^2.1.4:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
+
+hpack.js@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
+ dependencies:
+ inherits "^2.0.1"
+ obuf "^1.0.0"
+ readable-stream "^2.0.1"
+ wbuf "^1.1.0"
+
+html-comment-regex@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
+
+html-entities@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
+
+http-deceiver@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
+
+http-errors@1.6.2, http-errors@~1.6.2:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
+ dependencies:
+ depd "1.1.1"
+ inherits "2.0.3"
+ setprototypeof "1.0.3"
+ statuses ">= 1.3.1 < 2"
+
+http-parser-js@>=0.4.0:
+ version "0.4.10"
+ resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4"
+
+http-proxy-middleware@~0.17.4:
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833"
+ dependencies:
+ http-proxy "^1.16.2"
+ is-glob "^3.1.0"
+ lodash "^4.17.2"
+ micromatch "^2.3.11"
+
+http-proxy@^1.16.2:
+ version "1.16.2"
+ resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742"
+ dependencies:
+ eventemitter3 "1.x.x"
+ requires-port "1.x.x"
+
+http-signature@~1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
+ dependencies:
+ assert-plus "^0.2.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
+http-signature@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
+ dependencies:
+ assert-plus "^1.0.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
+https-browserify@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
+
+iconv-lite@0.4.19:
+ version "0.4.19"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
+
+icss-replace-symbols@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
+
+icss-utils@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962"
+ dependencies:
+ postcss "^6.0.1"
+
+ieee754@^1.1.4:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
+
+iferr@^0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
+
+import-local@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
+ dependencies:
+ pkg-dir "^2.0.0"
+ resolve-cwd "^2.0.0"
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+
+in-publish@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51"
+
+indent-string@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
+ dependencies:
+ repeating "^2.0.0"
+
+indexes-of@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
+
+indexof@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+
+inherits@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
+
+ini@~1.3.0:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
+
+internal-ip@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c"
+ dependencies:
+ meow "^3.3.0"
+
+interpret@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
+
+invariant@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
+ dependencies:
+ loose-envify "^1.0.0"
+
+invert-kv@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
+
+ip@^1.1.0, ip@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
+
+ipaddr.js@1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0"
+
+is-absolute-url@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
+
+is-accessor-descriptor@^0.1.6:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
+ dependencies:
+ kind-of "^3.0.2"
+
+is-accessor-descriptor@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656"
+ dependencies:
+ kind-of "^6.0.0"
+
+is-arrayish@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+
+is-arrayish@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.1.tgz#c2dfc386abaa0c3e33c48db3fe87059e69065efd"
+
+is-binary-path@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
+ dependencies:
+ binary-extensions "^1.0.0"
+
+is-buffer@^1.0.2, is-buffer@^1.1.5:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
+
+is-builtin-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
+ dependencies:
+ builtin-modules "^1.0.0"
+
+is-callable@^1.1.1, is-callable@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2"
+
+is-data-descriptor@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
+ dependencies:
+ kind-of "^3.0.2"
+
+is-data-descriptor@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7"
+ dependencies:
+ kind-of "^6.0.0"
+
+is-date-object@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
+
+is-descriptor@^0.1.0:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
+ dependencies:
+ is-accessor-descriptor "^0.1.6"
+ is-data-descriptor "^0.1.4"
+ kind-of "^5.0.0"
+
+is-descriptor@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec"
+ dependencies:
+ is-accessor-descriptor "^1.0.0"
+ is-data-descriptor "^1.0.0"
+ kind-of "^6.0.2"
+
+is-directory@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
+
+is-dotfile@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
+
+is-equal-shallow@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
+ dependencies:
+ is-primitive "^2.0.0"
+
+is-extendable@^0.1.0, is-extendable@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
+
+is-extendable@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4"
+ dependencies:
+ is-plain-object "^2.0.4"
+
+is-extglob@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
+
+is-extglob@^2.1.0, is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+
+is-finite@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+
+is-glob@^2.0.0, is-glob@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
+ dependencies:
+ is-extglob "^1.0.0"
+
+is-glob@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
+ dependencies:
+ is-extglob "^2.1.0"
+
+is-glob@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-my-json-valid@^2.12.4:
+ version "2.17.1"
+ resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz#3da98914a70a22f0a8563ef1511a246c6fc55471"
+ dependencies:
+ generate-function "^2.0.0"
+ generate-object-property "^1.1.0"
+ jsonpointer "^4.0.0"
+ xtend "^4.0.0"
+
+is-number@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
+ dependencies:
+ kind-of "^3.0.2"
+
+is-number@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
+ dependencies:
+ kind-of "^3.0.2"
+
+is-odd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-1.0.0.tgz#3b8a932eb028b3775c39bb09e91767accdb69088"
+ dependencies:
+ is-number "^3.0.0"
+
+is-path-cwd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
+
+is-path-in-cwd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc"
+ dependencies:
+ is-path-inside "^1.0.0"
+
+is-path-inside@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
+ dependencies:
+ path-is-inside "^1.0.1"
+
+is-plain-obj@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
+
+is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
+ dependencies:
+ isobject "^3.0.1"
+
+is-posix-bracket@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
+
+is-primitive@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
+
+is-property@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
+
+is-regex@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
+ dependencies:
+ has "^1.0.1"
+
+is-stream@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+
+is-svg@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9"
+ dependencies:
+ html-comment-regex "^1.1.0"
+
+is-symbol@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
+
+is-typedarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+
+is-utf8@^0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
+
+is-wsl@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
+
+isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+
+isnumeric@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/isnumeric/-/isnumeric-0.2.0.tgz#a2347ba360de19e33d0ffd590fddf7755cbf2e64"
+
+isobject@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
+ dependencies:
+ isarray "1.0.0"
+
+isobject@^3.0.0, isobject@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
+
+isstream@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
+
+js-base64@^2.1.8, js-base64@^2.1.9:
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582"
+
+js-tokens@^3.0.0, js-tokens@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
+
+js-yaml@^3.10.0, js-yaml@^3.4.3:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
+js-yaml@~3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^2.6.0"
+
+jsbn@~0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
+
+jsesc@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
+
+jsesc@~0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
+
+json-loader@^0.5.4:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
+
+json-schema-traverse@^0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
+
+json-schema@0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
+
+json-stable-stringify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
+ dependencies:
+ jsonify "~0.0.0"
+
+json-stringify-safe@~5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+
+json3@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
+
+json5@^0.5.0, json5@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+
+jsonfile@^2.1.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+jsonify@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
+
+jsonpointer@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
+
+jsprim@^1.2.2:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
+ dependencies:
+ assert-plus "1.0.0"
+ extsprintf "1.3.0"
+ json-schema "0.2.3"
+ verror "1.10.0"
+
+killable@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b"
+
+kind-of@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5"
+ dependencies:
+ is-buffer "^1.0.2"
+
+kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0, kind-of@^3.2.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
+ dependencies:
+ is-buffer "^1.1.5"
+
+kind-of@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
+ dependencies:
+ is-buffer "^1.1.5"
+
+kind-of@^5.0.0, kind-of@^5.0.2:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
+
+kind-of@^6.0.0, kind-of@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
+
+klaw@^1.0.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
+ optionalDependencies:
+ graceful-fs "^4.1.9"
+
+lazy-cache@^0.2.3:
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65"
+
+lazy-cache@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
+
+lazy-cache@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
+ dependencies:
+ set-getter "^0.1.0"
+
+lcid@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
+ dependencies:
+ invert-kv "^1.0.0"
+
+load-json-file@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^2.2.0"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+ strip-bom "^2.0.0"
+
+load-json-file@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^2.2.0"
+ pify "^2.0.0"
+ strip-bom "^3.0.0"
+
+loader-runner@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
+
+loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
+ dependencies:
+ big.js "^3.1.3"
+ emojis-list "^2.0.0"
+ json5 "^0.5.0"
+
+locate-path@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
+ dependencies:
+ p-locate "^2.0.0"
+ path-exists "^3.0.0"
+
+lodash._reinterpolate@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
+
+lodash.assign@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
+
+lodash.camelcase@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+
+lodash.clonedeep@^4.3.2:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+
+lodash.memoize@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+
+lodash.mergewith@^4.6.0:
+ version "4.6.1"
+ resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
+
+lodash.tail@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
+
+lodash.template@^4.2.4:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0"
+ dependencies:
+ lodash._reinterpolate "~3.0.0"
+ lodash.templatesettings "^4.0.0"
+
+lodash.templatesettings@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316"
+ dependencies:
+ lodash._reinterpolate "~3.0.0"
+
+lodash.uniq@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
+
+lodash@3.x:
+ version "3.10.1"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
+
+"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@~4.17.4:
+ version "4.17.5"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
+
+loglevel@^1.4.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa"
+
+longest@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
+
+loose-envify@^1.0.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
+ dependencies:
+ js-tokens "^3.0.0"
+
+loud-rejection@^1.0.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
+ dependencies:
+ currently-unhandled "^0.4.1"
+ signal-exit "^3.0.0"
+
+lru-cache@^4.0.1, lru-cache@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
+ dependencies:
+ pseudomap "^1.0.2"
+ yallist "^2.1.2"
+
+macaddress@^0.2.8:
+ version "0.2.8"
+ resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
+
+make-dir@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51"
+ dependencies:
+ pify "^3.0.0"
+
+map-cache@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
+
+map-obj@^1.0.0, map-obj@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
+
+map-visit@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
+ dependencies:
+ object-visit "^1.0.0"
+
+math-expression-evaluator@^1.2.14:
+ version "1.2.17"
+ resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
+
+md5.js@^1.3.4:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d"
+ dependencies:
+ hash-base "^3.0.0"
+ inherits "^2.0.1"
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+
+mem@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
+ dependencies:
+ mimic-fn "^1.0.0"
+
+memory-fs@^0.4.0, memory-fs@~0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
+ dependencies:
+ errno "^0.1.3"
+ readable-stream "^2.0.1"
+
+meow@^3.3.0, meow@^3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
+ dependencies:
+ camelcase-keys "^2.0.0"
+ decamelize "^1.1.2"
+ loud-rejection "^1.0.0"
+ map-obj "^1.0.1"
+ minimist "^1.1.3"
+ normalize-package-data "^2.3.4"
+ object-assign "^4.0.1"
+ read-pkg-up "^1.0.1"
+ redent "^1.0.0"
+ trim-newlines "^1.0.0"
+
+merge-descriptors@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+
+methods@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+
+micromatch@^2.1.5, micromatch@^2.3.11:
+ version "2.3.11"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
+ dependencies:
+ arr-diff "^2.0.0"
+ array-unique "^0.2.1"
+ braces "^1.8.2"
+ expand-brackets "^0.1.4"
+ extglob "^0.3.1"
+ filename-regex "^2.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.1"
+ kind-of "^3.0.2"
+ normalize-path "^2.0.1"
+ object.omit "^2.0.0"
+ parse-glob "^3.0.4"
+ regex-cache "^0.4.2"
+
+micromatch@^3.1.4:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.5.tgz#d05e168c206472dfbca985bfef4f57797b4cd4ba"
+ dependencies:
+ arr-diff "^4.0.0"
+ array-unique "^0.3.2"
+ braces "^2.3.0"
+ define-property "^1.0.0"
+ extend-shallow "^2.0.1"
+ extglob "^2.0.2"
+ fragment-cache "^0.2.1"
+ kind-of "^6.0.0"
+ nanomatch "^1.2.5"
+ object.pick "^1.3.0"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.1"
+
+miller-rabin@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
+ dependencies:
+ bn.js "^4.0.0"
+ brorand "^1.0.1"
+
+"mime-db@>= 1.30.0 < 2":
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.32.0.tgz#485b3848b01a3cda5f968b4882c0771e58e09414"
+
+mime-db@~1.30.0:
+ version "1.30.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
+
+mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7:
+ version "2.1.17"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
+ dependencies:
+ mime-db "~1.30.0"
+
+mime@1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
+
+mime@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+
+mimic-fn@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
+
+minimalistic-assert@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3"
+
+minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
+
+"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+
+minimist@^1.1.3, minimist@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
+
+mississippi@^1.3.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-1.3.1.tgz#2a8bb465e86550ac8b36a7b6f45599171d78671e"
+ dependencies:
+ concat-stream "^1.5.0"
+ duplexify "^3.4.2"
+ end-of-stream "^1.1.0"
+ flush-write-stream "^1.0.0"
+ from2 "^2.1.0"
+ parallel-transform "^1.1.0"
+ pump "^1.0.0"
+ pumpify "^1.3.3"
+ stream-each "^1.1.0"
+ through2 "^2.0.0"
+
+mixin-deep@^1.2.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
+ dependencies:
+ for-in "^1.0.2"
+ is-extendable "^1.0.1"
+
+mixin-object@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e"
+ dependencies:
+ for-in "^0.1.3"
+ is-extendable "^0.1.1"
+
+mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+ dependencies:
+ minimist "0.0.8"
+
+move-concurrently@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
+ dependencies:
+ aproba "^1.1.1"
+ copy-concurrently "^1.0.0"
+ fs-write-stream-atomic "^1.0.8"
+ mkdirp "^0.5.1"
+ rimraf "^2.5.4"
+ run-queue "^1.0.3"
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+
+multicast-dns-service-types@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
+
+multicast-dns@^6.0.1:
+ version "6.2.3"
+ resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229"
+ dependencies:
+ dns-packet "^1.3.1"
+ thunky "^1.0.2"
+
+nan@^2.3.0, nan@^2.3.2:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
+
+nanomatch@^1.2.5:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.7.tgz#53cd4aa109ff68b7f869591fdc9d10daeeea3e79"
+ dependencies:
+ arr-diff "^4.0.0"
+ array-unique "^0.3.2"
+ define-property "^1.0.0"
+ extend-shallow "^2.0.1"
+ fragment-cache "^0.2.1"
+ is-odd "^1.0.0"
+ kind-of "^5.0.2"
+ object.pick "^1.3.0"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.1"
+
+negotiator@0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
+
+node-forge@0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300"
+
+node-gyp@^3.3.1:
+ version "3.6.2"
+ resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60"
+ dependencies:
+ fstream "^1.0.0"
+ glob "^7.0.3"
+ graceful-fs "^4.1.2"
+ minimatch "^3.0.2"
+ mkdirp "^0.5.0"
+ nopt "2 || 3"
+ npmlog "0 || 1 || 2 || 3 || 4"
+ osenv "0"
+ request "2"
+ rimraf "2"
+ semver "~5.3.0"
+ tar "^2.0.0"
+ which "1"
+
+node-libs-browser@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df"
+ dependencies:
+ assert "^1.1.1"
+ browserify-zlib "^0.2.0"
+ buffer "^4.3.0"
+ console-browserify "^1.1.0"
+ constants-browserify "^1.0.0"
+ crypto-browserify "^3.11.0"
+ domain-browser "^1.1.1"
+ events "^1.0.0"
+ https-browserify "^1.0.0"
+ os-browserify "^0.3.0"
+ path-browserify "0.0.0"
+ process "^0.11.10"
+ punycode "^1.2.4"
+ querystring-es3 "^0.2.0"
+ readable-stream "^2.3.3"
+ stream-browserify "^2.0.1"
+ stream-http "^2.7.2"
+ string_decoder "^1.0.0"
+ timers-browserify "^2.0.4"
+ tty-browserify "0.0.0"
+ url "^0.11.0"
+ util "^0.10.3"
+ vm-browserify "0.0.4"
+
+node-pre-gyp@^0.6.39:
+ version "0.6.39"
+ resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649"
+ dependencies:
+ detect-libc "^1.0.2"
+ hawk "3.1.3"
+ mkdirp "^0.5.1"
+ nopt "^4.0.1"
+ npmlog "^4.0.2"
+ rc "^1.1.7"
+ request "2.81.0"
+ rimraf "^2.6.1"
+ semver "^5.3.0"
+ tar "^2.2.1"
+ tar-pack "^3.4.0"
+
+node-sass@^4.7.2:
+ version "4.7.2"
+ resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e"
+ dependencies:
+ async-foreach "^0.1.3"
+ chalk "^1.1.1"
+ cross-spawn "^3.0.0"
+ gaze "^1.0.0"
+ get-stdin "^4.0.1"
+ glob "^7.0.3"
+ in-publish "^2.0.0"
+ lodash.assign "^4.2.0"
+ lodash.clonedeep "^4.3.2"
+ lodash.mergewith "^4.6.0"
+ meow "^3.7.0"
+ mkdirp "^0.5.1"
+ nan "^2.3.2"
+ node-gyp "^3.3.1"
+ npmlog "^4.0.0"
+ request "~2.79.0"
+ sass-graph "^2.2.4"
+ stdout-stream "^1.4.0"
+ "true-case-path" "^1.0.2"
+
+"nopt@2 || 3":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
+ dependencies:
+ abbrev "1"
+
+nopt@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
+ dependencies:
+ abbrev "1"
+ osenv "^0.1.4"
+
+normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
+ dependencies:
+ hosted-git-info "^2.1.4"
+ is-builtin-module "^1.0.0"
+ semver "2 || 3 || 4 || 5"
+ validate-npm-package-license "^3.0.1"
+
+normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
+ dependencies:
+ remove-trailing-separator "^1.0.1"
+
+normalize-range@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
+
+normalize-url@^1.4.0:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
+ dependencies:
+ object-assign "^4.0.1"
+ prepend-http "^1.0.0"
+ query-string "^4.1.0"
+ sort-keys "^1.0.0"
+
+npm-run-path@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
+ dependencies:
+ path-key "^2.0.0"
+
+"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
+ dependencies:
+ are-we-there-yet "~1.1.2"
+ console-control-strings "~1.1.0"
+ gauge "~2.7.3"
+ set-blocking "~2.0.0"
+
+num2fraction@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
+
+number-is-nan@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+
+oauth-sign@~0.8.1, oauth-sign@~0.8.2:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
+
+object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+
+object-copy@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
+ dependencies:
+ copy-descriptor "^0.1.0"
+ define-property "^0.2.5"
+ kind-of "^3.0.3"
+
+object-keys@^1.0.8:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
+
+object-visit@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
+ dependencies:
+ isobject "^3.0.0"
+
+object.omit@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
+ dependencies:
+ for-own "^0.1.4"
+ is-extendable "^0.1.1"
+
+object.pick@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
+ dependencies:
+ isobject "^3.0.1"
+
+obuf@^1.0.0, obuf@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e"
+
+on-finished@~2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+ dependencies:
+ ee-first "1.1.1"
+
+on-headers@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
+
+once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ dependencies:
+ wrappy "1"
+
+onecolor@^3.0.4:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/onecolor/-/onecolor-3.0.5.tgz#36eff32201379efdf1180fb445e51a8e2425f9f6"
+
+opn@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225"
+ dependencies:
+ is-wsl "^1.1.0"
+
+original@>=0.0.5:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b"
+ dependencies:
+ url-parse "1.0.x"
+
+os-browserify@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
+
+os-homedir@^1.0.0, os-homedir@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
+
+os-locale@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
+ dependencies:
+ lcid "^1.0.0"
+
+os-locale@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
+ dependencies:
+ execa "^0.7.0"
+ lcid "^1.0.0"
+ mem "^1.1.0"
+
+os-tmpdir@^1.0.0, os-tmpdir@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+
+osenv@0, osenv@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.0"
+
+p-finally@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+
+p-limit@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
+ dependencies:
+ p-try "^1.0.0"
+
+p-locate@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
+ dependencies:
+ p-limit "^1.1.0"
+
+p-map@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
+
+p-try@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
+
+pako@~1.0.5:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
+
+parallel-transform@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
+ dependencies:
+ cyclist "~0.2.2"
+ inherits "^2.0.3"
+ readable-stream "^2.1.5"
+
+parse-asn1@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712"
+ dependencies:
+ asn1.js "^4.0.0"
+ browserify-aes "^1.0.0"
+ create-hash "^1.1.0"
+ evp_bytestokey "^1.0.0"
+ pbkdf2 "^3.0.3"
+
+parse-glob@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
+ dependencies:
+ glob-base "^0.3.0"
+ is-dotfile "^1.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.0"
+
+parse-json@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
+ dependencies:
+ error-ex "^1.2.0"
+
+parseurl@~1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
+
+pascalcase@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
+
+path-browserify@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
+
+path-complete-extname@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/path-complete-extname/-/path-complete-extname-0.1.0.tgz#c454702669f31452f8193aa6168915fa31692f4a"
+
+path-dirname@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
+
+path-exists@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
+ dependencies:
+ pinkie-promise "^2.0.0"
+
+path-exists@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+
+path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+
+path-is-inside@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
+
+path-key@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
+
+path-parse@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
+
+path-to-regexp@0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+
+path-type@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
+ dependencies:
+ graceful-fs "^4.1.2"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+path-type@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
+ dependencies:
+ pify "^2.0.0"
+
+pbkdf2@^3.0.3:
+ version "3.0.14"
+ resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade"
+ dependencies:
+ create-hash "^1.1.2"
+ create-hmac "^1.1.4"
+ ripemd160 "^2.0.1"
+ safe-buffer "^5.0.1"
+ sha.js "^2.4.8"
+
+performance-now@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
+
+performance-now@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+
+pify@^2.0.0, pify@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+
+pify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+
+pinkie-promise@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
+ dependencies:
+ pinkie "^2.0.0"
+
+pinkie@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
+
+pixrem@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/pixrem/-/pixrem-4.0.1.tgz#2da4a1de6ec4423c5fc3794e930b81d4490ec686"
+ dependencies:
+ browserslist "^2.0.0"
+ postcss "^6.0.0"
+ reduce-css-calc "^1.2.7"
+
+pkg-dir@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
+ dependencies:
+ find-up "^2.1.0"
+
+pleeease-filters@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/pleeease-filters/-/pleeease-filters-4.0.0.tgz#6632b2fb05648d2758d865384fbced79e1ccaec7"
+ dependencies:
+ onecolor "^3.0.4"
+ postcss "^6.0.1"
+
+portfinder@^1.0.9:
+ version "1.0.13"
+ resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
+ dependencies:
+ async "^1.5.2"
+ debug "^2.2.0"
+ mkdirp "0.5.x"
+
+posix-character-classes@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
+
+postcss-apply@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/postcss-apply/-/postcss-apply-0.8.0.tgz#14e544bbb5cb6f1c1e048857965d79ae066b1343"
+ dependencies:
+ babel-runtime "^6.23.0"
+ balanced-match "^0.4.2"
+ postcss "^6.0.0"
+
+postcss-attribute-case-insensitive@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-2.0.0.tgz#94dc422c8f90997f16bd33a3654bbbec084963b4"
+ dependencies:
+ postcss "^6.0.0"
+ postcss-selector-parser "^2.2.3"
+
+postcss-calc@^5.2.0:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e"
+ dependencies:
+ postcss "^5.0.2"
+ postcss-message-helpers "^2.0.0"
+ reduce-css-calc "^1.2.6"
+
+postcss-calc@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-6.0.1.tgz#3d24171bbf6e7629d422a436ebfe6dd9511f4330"
+ dependencies:
+ css-unit-converter "^1.1.1"
+ postcss "^6.0.0"
+ postcss-selector-parser "^2.2.2"
+ reduce-css-calc "^2.0.0"
+
+postcss-color-function@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-color-function/-/postcss-color-function-4.0.1.tgz#402b3f2cebc3f6947e618fb6be3654fbecef6444"
+ dependencies:
+ css-color-function "~1.3.3"
+ postcss "^6.0.1"
+ postcss-message-helpers "^2.0.0"
+ postcss-value-parser "^3.3.0"
+
+postcss-color-gray@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-4.1.0.tgz#e5581ed57eaa826fb652ca11b1e2b7b136a9f9df"
+ dependencies:
+ color "^2.0.1"
+ postcss "^6.0.14"
+ postcss-message-helpers "^2.0.0"
+ reduce-function-call "^1.0.2"
+
+postcss-color-hex-alpha@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-3.0.0.tgz#1e53e6c8acb237955e8fd08b7ecdb1b8b8309f95"
+ dependencies:
+ color "^1.0.3"
+ postcss "^6.0.1"
+ postcss-message-helpers "^2.0.0"
+
+postcss-color-hsl@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-hsl/-/postcss-color-hsl-2.0.0.tgz#12703666fa310430e3f30a454dac1386317d5844"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-value-parser "^3.3.0"
+ units-css "^0.4.0"
+
+postcss-color-hwb@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-hwb/-/postcss-color-hwb-3.0.0.tgz#3402b19ef4d8497540c1fb5072be9863ca95571e"
+ dependencies:
+ color "^1.0.3"
+ postcss "^6.0.1"
+ postcss-message-helpers "^2.0.0"
+ reduce-function-call "^1.0.2"
+
+postcss-color-rebeccapurple@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-3.0.0.tgz#eebaf03d363b4300b96792bd3081c19ed66513d3"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-value-parser "^3.3.0"
+
+postcss-color-rgb@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-rgb/-/postcss-color-rgb-2.0.0.tgz#14539c8a7131494b482e0dd1cc265ff6514b5263"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-value-parser "^3.3.0"
+
+postcss-color-rgba-fallback@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-rgba-fallback/-/postcss-color-rgba-fallback-3.0.0.tgz#37d5c9353a07a09270912a82606bb42a0d702c04"
+ dependencies:
+ postcss "^6.0.6"
+ postcss-value-parser "^3.3.0"
+ rgb-hex "^2.1.0"
+
+postcss-colormin@^2.1.8:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b"
+ dependencies:
+ colormin "^1.0.5"
+ postcss "^5.0.13"
+ postcss-value-parser "^3.2.3"
+
+postcss-convert-values@^2.3.4:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d"
+ dependencies:
+ postcss "^5.0.11"
+ postcss-value-parser "^3.1.2"
+
+postcss-cssnext@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-cssnext/-/postcss-cssnext-3.1.0.tgz#927dc29341a938254cde38ea60a923b9dfedead9"
+ dependencies:
+ autoprefixer "^7.1.1"
+ caniuse-api "^2.0.0"
+ chalk "^2.0.1"
+ pixrem "^4.0.0"
+ pleeease-filters "^4.0.0"
+ postcss "^6.0.5"
+ postcss-apply "^0.8.0"
+ postcss-attribute-case-insensitive "^2.0.0"
+ postcss-calc "^6.0.0"
+ postcss-color-function "^4.0.0"
+ postcss-color-gray "^4.0.0"
+ postcss-color-hex-alpha "^3.0.0"
+ postcss-color-hsl "^2.0.0"
+ postcss-color-hwb "^3.0.0"
+ postcss-color-rebeccapurple "^3.0.0"
+ postcss-color-rgb "^2.0.0"
+ postcss-color-rgba-fallback "^3.0.0"
+ postcss-custom-media "^6.0.0"
+ postcss-custom-properties "^6.1.0"
+ postcss-custom-selectors "^4.0.1"
+ postcss-font-family-system-ui "^3.0.0"
+ postcss-font-variant "^3.0.0"
+ postcss-image-set-polyfill "^0.3.5"
+ postcss-initial "^2.0.0"
+ postcss-media-minmax "^3.0.0"
+ postcss-nesting "^4.0.1"
+ postcss-pseudo-class-any-link "^4.0.0"
+ postcss-pseudoelements "^5.0.0"
+ postcss-replace-overflow-wrap "^2.0.0"
+ postcss-selector-matches "^3.0.1"
+ postcss-selector-not "^3.0.1"
+
+postcss-custom-media@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-6.0.0.tgz#be532784110ecb295044fb5395a18006eb21a737"
+ dependencies:
+ postcss "^6.0.1"
+
+postcss-custom-properties@^6.1.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-6.2.0.tgz#5d929a7f06e9b84e0f11334194c0ba9a30acfbe9"
+ dependencies:
+ balanced-match "^1.0.0"
+ postcss "^6.0.13"
+
+postcss-custom-selectors@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-4.0.1.tgz#781382f94c52e727ef5ca4776ea2adf49a611382"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-selector-matches "^3.0.0"
+
+postcss-discard-comments@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d"
+ dependencies:
+ postcss "^5.0.14"
+
+postcss-discard-duplicates@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932"
+ dependencies:
+ postcss "^5.0.4"
+
+postcss-discard-empty@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5"
+ dependencies:
+ postcss "^5.0.14"
+
+postcss-discard-overridden@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58"
+ dependencies:
+ postcss "^5.0.16"
+
+postcss-discard-unused@^2.2.1:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433"
+ dependencies:
+ postcss "^5.0.14"
+ uniqs "^2.0.0"
+
+postcss-filter-plugins@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c"
+ dependencies:
+ postcss "^5.0.4"
+ uniqid "^4.0.0"
+
+postcss-font-family-system-ui@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-font-family-system-ui/-/postcss-font-family-system-ui-3.0.0.tgz#675fe7a9e029669f05f8dba2e44c2225ede80623"
+ dependencies:
+ postcss "^6.0"
+
+postcss-font-variant@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-3.0.0.tgz#08ccc88f6050ba82ed8ef2cc76c0c6a6b41f183e"
+ dependencies:
+ postcss "^6.0.1"
+
+postcss-image-set-polyfill@^0.3.5:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/postcss-image-set-polyfill/-/postcss-image-set-polyfill-0.3.5.tgz#0f193413700cf1f82bd39066ef016d65a4a18181"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-media-query-parser "^0.2.3"
+
+postcss-import@^11.0.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-11.1.0.tgz#55c9362c9192994ec68865d224419df1db2981f0"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-value-parser "^3.2.3"
+ read-cache "^1.0.0"
+ resolve "^1.1.7"
+
+postcss-initial@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-2.0.0.tgz#72715f7336e0bb79351d99ee65c4a253a8441ba4"
+ dependencies:
+ lodash.template "^4.2.4"
+ postcss "^6.0.1"
+
+postcss-load-config@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-1.2.0.tgz#539e9afc9ddc8620121ebf9d8c3673e0ce50d28a"
+ dependencies:
+ cosmiconfig "^2.1.0"
+ object-assign "^4.1.0"
+ postcss-load-options "^1.2.0"
+ postcss-load-plugins "^2.3.0"
+
+postcss-load-options@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-load-options/-/postcss-load-options-1.2.0.tgz#b098b1559ddac2df04bc0bb375f99a5cfe2b6d8c"
+ dependencies:
+ cosmiconfig "^2.1.0"
+ object-assign "^4.1.0"
+
+postcss-load-plugins@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz#745768116599aca2f009fad426b00175049d8d92"
+ dependencies:
+ cosmiconfig "^2.1.1"
+ object-assign "^4.1.0"
+
+postcss-loader@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.0.tgz#038c2d6d59753fef4667827fd3ae03f5dc5e6a7a"
+ dependencies:
+ loader-utils "^1.1.0"
+ postcss "^6.0.0"
+ postcss-load-config "^1.2.0"
+ schema-utils "^0.4.0"
+
+postcss-media-minmax@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-3.0.0.tgz#675256037a43ef40bc4f0760bfd06d4dc69d48d2"
+ dependencies:
+ postcss "^6.0.1"
+
+postcss-media-query-parser@^0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244"
+
+postcss-merge-idents@^2.1.5:
+ version "2.1.7"
+ resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270"
+ dependencies:
+ has "^1.0.1"
+ postcss "^5.0.10"
+ postcss-value-parser "^3.1.1"
+
+postcss-merge-longhand@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658"
+ dependencies:
+ postcss "^5.0.4"
+
+postcss-merge-rules@^2.0.3:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721"
+ dependencies:
+ browserslist "^1.5.2"
+ caniuse-api "^1.5.2"
+ postcss "^5.0.4"
+ postcss-selector-parser "^2.2.2"
+ vendors "^1.0.0"
+
+postcss-message-helpers@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e"
+
+postcss-minify-font-values@^1.0.2:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69"
+ dependencies:
+ object-assign "^4.0.1"
+ postcss "^5.0.4"
+ postcss-value-parser "^3.0.2"
+
+postcss-minify-gradients@^1.0.1:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1"
+ dependencies:
+ postcss "^5.0.12"
+ postcss-value-parser "^3.3.0"
+
+postcss-minify-params@^1.0.4:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3"
+ dependencies:
+ alphanum-sort "^1.0.1"
+ postcss "^5.0.2"
+ postcss-value-parser "^3.0.2"
+ uniqs "^2.0.0"
+
+postcss-minify-selectors@^2.0.4:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf"
+ dependencies:
+ alphanum-sort "^1.0.2"
+ has "^1.0.1"
+ postcss "^5.0.14"
+ postcss-selector-parser "^2.0.0"
+
+postcss-modules-extract-imports@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85"
+ dependencies:
+ postcss "^6.0.1"
+
+postcss-modules-local-by-default@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069"
+ dependencies:
+ css-selector-tokenizer "^0.7.0"
+ postcss "^6.0.1"
+
+postcss-modules-scope@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90"
+ dependencies:
+ css-selector-tokenizer "^0.7.0"
+ postcss "^6.0.1"
+
+postcss-modules-values@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20"
+ dependencies:
+ icss-replace-symbols "^1.1.0"
+ postcss "^6.0.1"
+
+postcss-nesting@^4.0.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-4.2.1.tgz#0483bce338b3f0828ced90ff530b29b98b00300d"
+ dependencies:
+ postcss "^6.0.11"
+
+postcss-normalize-charset@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1"
+ dependencies:
+ postcss "^5.0.5"
+
+postcss-normalize-url@^3.0.7:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222"
+ dependencies:
+ is-absolute-url "^2.0.0"
+ normalize-url "^1.4.0"
+ postcss "^5.0.14"
+ postcss-value-parser "^3.2.3"
+
+postcss-ordered-values@^2.1.0:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d"
+ dependencies:
+ postcss "^5.0.4"
+ postcss-value-parser "^3.0.1"
+
+postcss-pseudo-class-any-link@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-4.0.0.tgz#9152a0613d3450720513e8892854bae42d0ee68e"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-selector-parser "^2.2.3"
+
+postcss-pseudoelements@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-pseudoelements/-/postcss-pseudoelements-5.0.0.tgz#eef194e8d524645ca520a949e95e518e812402cb"
+ dependencies:
+ postcss "^6.0.0"
+
+postcss-reduce-idents@^2.2.2:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3"
+ dependencies:
+ postcss "^5.0.4"
+ postcss-value-parser "^3.0.2"
+
+postcss-reduce-initial@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea"
+ dependencies:
+ postcss "^5.0.4"
+
+postcss-reduce-transforms@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1"
+ dependencies:
+ has "^1.0.1"
+ postcss "^5.0.8"
+ postcss-value-parser "^3.0.1"
+
+postcss-replace-overflow-wrap@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-2.0.0.tgz#794db6faa54f8db100854392a93af45768b4e25b"
+ dependencies:
+ postcss "^6.0.1"
+
+postcss-selector-matches@^3.0.0, postcss-selector-matches@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-3.0.1.tgz#e5634011e13950881861bbdd58c2d0111ffc96ab"
+ dependencies:
+ balanced-match "^0.4.2"
+ postcss "^6.0.1"
+
+postcss-selector-not@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-3.0.1.tgz#2e4db2f0965336c01e7cec7db6c60dff767335d9"
+ dependencies:
+ balanced-match "^0.4.2"
+ postcss "^6.0.1"
+
+postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2, postcss-selector-parser@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90"
+ dependencies:
+ flatten "^1.0.2"
+ indexes-of "^1.0.1"
+ uniq "^1.0.1"
+
+postcss-svgo@^2.1.1:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d"
+ dependencies:
+ is-svg "^2.0.0"
+ postcss "^5.0.14"
+ postcss-value-parser "^3.2.3"
+ svgo "^0.7.0"
+
+postcss-unique-selectors@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d"
+ dependencies:
+ alphanum-sort "^1.0.1"
+ postcss "^5.0.4"
+ uniqs "^2.0.0"
+
+postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15"
+
+postcss-zindex@^2.0.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22"
+ dependencies:
+ has "^1.0.1"
+ postcss "^5.0.4"
+ uniqs "^2.0.0"
+
+postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16:
+ version "5.2.18"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5"
+ dependencies:
+ chalk "^1.1.3"
+ js-base64 "^2.1.9"
+ source-map "^0.5.6"
+ supports-color "^3.2.3"
+
+postcss@^6.0, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.13, postcss@^6.0.14, postcss@^6.0.17, postcss@^6.0.5, postcss@^6.0.6:
+ version "6.0.17"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.17.tgz#e259a051ca513f81e9afd0c21f7f82eda50c65c5"
+ dependencies:
+ chalk "^2.3.0"
+ source-map "^0.6.1"
+ supports-color "^5.1.0"
+
+prepend-http@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
+
+preserve@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
+
+private@^0.1.6, private@^0.1.7:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
+
+process-nextick-args@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
+
+process@^0.11.10:
+ version "0.11.10"
+ resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
+
+promise-inflight@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
+
+proxy-addr@~2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
+ dependencies:
+ forwarded "~0.1.2"
+ ipaddr.js "1.5.2"
+
+prr@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
+
+pseudomap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+
+public-encrypt@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6"
+ dependencies:
+ bn.js "^4.1.0"
+ browserify-rsa "^4.0.0"
+ create-hash "^1.1.0"
+ parse-asn1 "^5.0.0"
+ randombytes "^2.0.1"
+
+pump@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954"
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+pump@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+pumpify@^1.3.3:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb"
+ dependencies:
+ duplexify "^3.5.3"
+ inherits "^2.0.3"
+ pump "^2.0.0"
+
+punycode@1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
+
+punycode@^1.2.4, punycode@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+
+q@^1.1.2:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
+
+qs@6.5.1, qs@~6.5.1:
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
+
+qs@~6.3.0:
+ version "6.3.2"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
+
+qs@~6.4.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
+
+query-string@^4.1.0:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
+ dependencies:
+ object-assign "^4.1.0"
+ strict-uri-encode "^1.0.0"
+
+querystring-es3@^0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
+
+querystring@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
+
+querystringify@0.0.x:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c"
+
+querystringify@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb"
+
+randomatic@^1.1.3:
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
+ dependencies:
+ is-number "^3.0.0"
+ kind-of "^4.0.0"
+
+randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80"
+ dependencies:
+ safe-buffer "^5.1.0"
+
+randomfill@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.3.tgz#b96b7df587f01dd91726c418f30553b1418e3d62"
+ dependencies:
+ randombytes "^2.0.5"
+ safe-buffer "^5.1.0"
+
+range-parser@^1.0.3, range-parser@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+
+raw-body@2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
+ dependencies:
+ bytes "3.0.0"
+ http-errors "1.6.2"
+ iconv-lite "0.4.19"
+ unpipe "1.0.0"
+
+rc@^1.1.7:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.5.tgz#275cd687f6e3b36cc756baa26dfee80a790301fd"
+ dependencies:
+ deep-extend "~0.4.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
+read-cache@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
+ dependencies:
+ pify "^2.3.0"
+
+read-pkg-up@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
+ dependencies:
+ find-up "^1.0.0"
+ read-pkg "^1.0.0"
+
+read-pkg-up@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
+ dependencies:
+ find-up "^2.0.0"
+ read-pkg "^2.0.0"
+
+read-pkg@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
+ dependencies:
+ load-json-file "^1.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^1.0.0"
+
+read-pkg@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
+ dependencies:
+ load-json-file "^2.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^2.0.0"
+
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3:
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.0.3"
+ util-deprecate "~1.0.1"
+
+readdirp@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
+ dependencies:
+ graceful-fs "^4.1.2"
+ minimatch "^3.0.2"
+ readable-stream "^2.0.2"
+ set-immediate-shim "^1.0.1"
+
+redent@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
+ dependencies:
+ indent-string "^2.1.0"
+ strip-indent "^1.0.1"
+
+reduce-css-calc@^1.2.6, reduce-css-calc@^1.2.7:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716"
+ dependencies:
+ balanced-match "^0.4.2"
+ math-expression-evaluator "^1.2.14"
+ reduce-function-call "^1.0.1"
+
+reduce-css-calc@^2.0.0:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.4.tgz#c20e9cda8445ad73d4ff4bea960c6f8353791708"
+ dependencies:
+ css-unit-converter "^1.1.1"
+ postcss-value-parser "^3.3.0"
+
+reduce-function-call@^1.0.1, reduce-function-call@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99"
+ dependencies:
+ balanced-match "^0.4.2"
+
+regenerate@^1.2.1:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
+
+regenerator-runtime@^0.10.5:
+ version "0.10.5"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
+
+regenerator-runtime@^0.11.0:
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
+
+regenerator-transform@^0.10.0:
+ version "0.10.1"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
+ dependencies:
+ babel-runtime "^6.18.0"
+ babel-types "^6.19.0"
+ private "^0.1.6"
+
+regex-cache@^0.4.2:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
+ dependencies:
+ is-equal-shallow "^0.1.3"
+
+regex-not@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.0.tgz#42f83e39771622df826b02af176525d6a5f157f9"
+ dependencies:
+ extend-shallow "^2.0.1"
+
+regexpu-core@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
+ dependencies:
+ regenerate "^1.2.1"
+ regjsgen "^0.2.0"
+ regjsparser "^0.1.4"
+
+regexpu-core@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240"
+ dependencies:
+ regenerate "^1.2.1"
+ regjsgen "^0.2.0"
+ regjsparser "^0.1.4"
+
+regjsgen@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
+
+regjsparser@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
+ dependencies:
+ jsesc "~0.5.0"
+
+remove-trailing-separator@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
+
+repeat-element@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
+
+repeat-string@^1.5.2, repeat-string@^1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+
+repeating@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
+ dependencies:
+ is-finite "^1.0.0"
+
+request@2:
+ version "2.83.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
+ dependencies:
+ aws-sign2 "~0.7.0"
+ aws4 "^1.6.0"
+ caseless "~0.12.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.1"
+ forever-agent "~0.6.1"
+ form-data "~2.3.1"
+ har-validator "~5.0.3"
+ hawk "~6.0.2"
+ http-signature "~1.2.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.17"
+ oauth-sign "~0.8.2"
+ performance-now "^2.1.0"
+ qs "~6.5.1"
+ safe-buffer "^5.1.1"
+ stringstream "~0.0.5"
+ tough-cookie "~2.3.3"
+ tunnel-agent "^0.6.0"
+ uuid "^3.1.0"
+
+request@2.81.0:
+ version "2.81.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
+ dependencies:
+ aws-sign2 "~0.6.0"
+ aws4 "^1.2.1"
+ caseless "~0.12.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.0"
+ forever-agent "~0.6.1"
+ form-data "~2.1.1"
+ har-validator "~4.2.1"
+ hawk "~3.1.3"
+ http-signature "~1.1.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.7"
+ oauth-sign "~0.8.1"
+ performance-now "^0.2.0"
+ qs "~6.4.0"
+ safe-buffer "^5.0.1"
+ stringstream "~0.0.4"
+ tough-cookie "~2.3.0"
+ tunnel-agent "^0.6.0"
+ uuid "^3.0.0"
+
+request@~2.79.0:
+ version "2.79.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
+ dependencies:
+ aws-sign2 "~0.6.0"
+ aws4 "^1.2.1"
+ caseless "~0.11.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.0"
+ forever-agent "~0.6.1"
+ form-data "~2.1.1"
+ har-validator "~2.0.6"
+ hawk "~3.1.3"
+ http-signature "~1.1.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.7"
+ oauth-sign "~0.8.1"
+ qs "~6.3.0"
+ stringstream "~0.0.4"
+ tough-cookie "~2.3.0"
+ tunnel-agent "~0.4.1"
+ uuid "^3.0.0"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+
+require-from-string@^1.1.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418"
+
+require-main-filename@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
+
+requires-port@1.0.x, requires-port@1.x.x, requires-port@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+
+resolve-cwd@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
+ dependencies:
+ resolve-from "^3.0.0"
+
+resolve-from@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
+
+resolve-url@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
+
+resolve@^1.1.7:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
+ dependencies:
+ path-parse "^1.0.5"
+
+rgb-hex@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/rgb-hex/-/rgb-hex-2.1.0.tgz#c773c5fe2268a25578d92539a82a7a5ce53beda6"
+
+rgb@~0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/rgb/-/rgb-0.1.0.tgz#be27b291e8feffeac1bd99729721bfa40fc037b5"
+
+right-align@^0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
+ dependencies:
+ align-text "^0.1.1"
+
+rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
+ dependencies:
+ glob "^7.0.5"
+
+ripemd160@^2.0.0, ripemd160@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7"
+ dependencies:
+ hash-base "^2.0.0"
+ inherits "^2.0.1"
+
+run-queue@^1.0.0, run-queue@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
+ dependencies:
+ aproba "^1.1.1"
+
+safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
+
+sass-graph@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
+ dependencies:
+ glob "^7.0.0"
+ lodash "^4.0.0"
+ scss-tokenizer "^0.2.3"
+ yargs "^7.0.0"
+
+sass-loader@^6.0.6:
+ version "6.0.6"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.6.tgz#e9d5e6c1f155faa32a4b26d7a9b7107c225e40f9"
+ dependencies:
+ async "^2.1.5"
+ clone-deep "^0.3.0"
+ loader-utils "^1.0.1"
+ lodash.tail "^4.1.1"
+ pify "^3.0.0"
+
+sax@~1.2.1:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+
+schema-utils@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"
+ dependencies:
+ ajv "^5.0.0"
+
+schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.3:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.3.tgz#e2a594d3395834d5e15da22b48be13517859458e"
+ dependencies:
+ ajv "^5.0.0"
+ ajv-keywords "^2.1.0"
+
+scss-tokenizer@^0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
+ dependencies:
+ js-base64 "^2.1.8"
+ source-map "^0.4.2"
+
+select-hose@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
+
+selfsigned@^1.9.1:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.2.tgz#b4449580d99929b65b10a48389301a6592088758"
+ dependencies:
+ node-forge "0.7.1"
+
+"semver@2 || 3 || 4 || 5", semver@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
+
+semver@~5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
+
+send@0.16.1:
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
+ dependencies:
+ debug "2.6.9"
+ depd "~1.1.1"
+ destroy "~1.0.4"
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ fresh "0.5.2"
+ http-errors "~1.6.2"
+ mime "1.4.1"
+ ms "2.0.0"
+ on-finished "~2.3.0"
+ range-parser "~1.2.0"
+ statuses "~1.3.1"
+
+serialize-javascript@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005"
+
+serve-index@^1.7.2:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
+ dependencies:
+ accepts "~1.3.4"
+ batch "0.6.1"
+ debug "2.6.9"
+ escape-html "~1.0.3"
+ http-errors "~1.6.2"
+ mime-types "~2.1.17"
+ parseurl "~1.3.2"
+
+serve-static@1.13.1:
+ version "1.13.1"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719"
+ dependencies:
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ parseurl "~1.3.2"
+ send "0.16.1"
+
+set-blocking@^2.0.0, set-blocking@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+
+set-getter@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376"
+ dependencies:
+ to-object-path "^0.3.0"
+
+set-immediate-shim@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
+
+set-value@^0.4.3:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1"
+ dependencies:
+ extend-shallow "^2.0.1"
+ is-extendable "^0.1.1"
+ is-plain-object "^2.0.1"
+ to-object-path "^0.3.0"
+
+set-value@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274"
+ dependencies:
+ extend-shallow "^2.0.1"
+ is-extendable "^0.1.1"
+ is-plain-object "^2.0.3"
+ split-string "^3.0.1"
+
+setimmediate@^1.0.4:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
+
+setprototypeof@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
+
+setprototypeof@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
+
+sha.js@^2.4.0, sha.js@^2.4.8:
+ version "2.4.10"
+ resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.10.tgz#b1fde5cd7d11a5626638a07c604ab909cfa31f9b"
+ dependencies:
+ inherits "^2.0.1"
+ safe-buffer "^5.0.1"
+
+shallow-clone@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060"
+ dependencies:
+ is-extendable "^0.1.1"
+ kind-of "^2.0.1"
+ lazy-cache "^0.2.3"
+ mixin-object "^2.0.1"
+
+shebang-command@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+ dependencies:
+ shebang-regex "^1.0.0"
+
+shebang-regex@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+
+signal-exit@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+
+simple-swizzle@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
+ dependencies:
+ is-arrayish "^0.3.1"
+
+slash@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
+
+snapdragon-node@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
+ dependencies:
+ define-property "^1.0.0"
+ isobject "^3.0.0"
+ snapdragon-util "^3.0.1"
+
+snapdragon-util@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2"
+ dependencies:
+ kind-of "^3.2.0"
+
+snapdragon@^0.8.1:
+ version "0.8.1"
+ resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370"
+ dependencies:
+ base "^0.11.1"
+ debug "^2.2.0"
+ define-property "^0.2.5"
+ extend-shallow "^2.0.1"
+ map-cache "^0.2.2"
+ source-map "^0.5.6"
+ source-map-resolve "^0.5.0"
+ use "^2.0.0"
+
+sntp@1.x.x:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
+ dependencies:
+ hoek "2.x.x"
+
+sntp@2.x.x:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
+ dependencies:
+ hoek "4.x.x"
+
+sockjs-client@1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12"
+ dependencies:
+ debug "^2.6.6"
+ eventsource "0.1.6"
+ faye-websocket "~0.11.0"
+ inherits "^2.0.1"
+ json3 "^3.3.2"
+ url-parse "^1.1.8"
+
+sockjs@0.3.19:
+ version "0.3.19"
+ resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d"
+ dependencies:
+ faye-websocket "^0.10.0"
+ uuid "^3.0.1"
+
+sort-keys@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
+ dependencies:
+ is-plain-obj "^1.0.0"
+
+source-list-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
+
+source-map-resolve@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a"
+ dependencies:
+ atob "^2.0.0"
+ decode-uri-component "^0.2.0"
+ resolve-url "^0.2.1"
+ source-map-url "^0.4.0"
+ urix "^0.1.0"
+
+source-map-support@^0.4.15:
+ version "0.4.18"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
+ dependencies:
+ source-map "^0.5.6"
+
+source-map-url@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
+
+source-map@^0.4.2:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
+ dependencies:
+ amdefine ">=0.0.4"
+
+source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+
+source-map@^0.6.1, source-map@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+
+spdx-correct@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
+ dependencies:
+ spdx-license-ids "^1.0.2"
+
+spdx-expression-parse@~1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c"
+
+spdx-license-ids@^1.0.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
+
+spdy-transport@^2.0.18:
+ version "2.0.20"
+ resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.20.tgz#735e72054c486b2354fe89e702256004a39ace4d"
+ dependencies:
+ debug "^2.6.8"
+ detect-node "^2.0.3"
+ hpack.js "^2.1.6"
+ obuf "^1.1.1"
+ readable-stream "^2.2.9"
+ safe-buffer "^5.0.1"
+ wbuf "^1.7.2"
+
+spdy@^3.4.1:
+ version "3.4.7"
+ resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc"
+ dependencies:
+ debug "^2.6.8"
+ handle-thing "^1.2.5"
+ http-deceiver "^1.2.7"
+ safe-buffer "^5.0.1"
+ select-hose "^2.0.0"
+ spdy-transport "^2.0.18"
+
+split-string@^3.0.1, split-string@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
+ dependencies:
+ extend-shallow "^3.0.0"
+
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+
+sshpk@^1.7.0:
+ version "1.13.1"
+ resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
+ dependencies:
+ asn1 "~0.2.3"
+ assert-plus "^1.0.0"
+ dashdash "^1.12.0"
+ getpass "^0.1.1"
+ optionalDependencies:
+ bcrypt-pbkdf "^1.0.0"
+ ecc-jsbn "~0.1.1"
+ jsbn "~0.1.0"
+ tweetnacl "~0.14.0"
+
+ssri@^5.0.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.2.1.tgz#8b6eb873688759bd3c75a88dee74593d179bb73c"
+ dependencies:
+ safe-buffer "^5.1.1"
+
+static-extend@^0.1.1:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
+ dependencies:
+ define-property "^0.2.5"
+ object-copy "^0.1.0"
+
+"statuses@>= 1.3.1 < 2":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
+
+statuses@~1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
+
+stdout-stream@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b"
+ dependencies:
+ readable-stream "^2.0.1"
+
+stream-browserify@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
+ dependencies:
+ inherits "~2.0.1"
+ readable-stream "^2.0.2"
+
+stream-each@^1.1.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd"
+ dependencies:
+ end-of-stream "^1.1.0"
+ stream-shift "^1.0.0"
+
+stream-http@^2.7.2:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.0.tgz#fd86546dac9b1c91aff8fc5d287b98fafb41bc10"
+ dependencies:
+ builtin-status-codes "^3.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.3.3"
+ to-arraybuffer "^1.0.0"
+ xtend "^4.0.0"
+
+stream-shift@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
+
+strict-uri-encode@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
+
+string-width@^1.0.1, string-width@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+ dependencies:
+ code-point-at "^1.0.0"
+ is-fullwidth-code-point "^1.0.0"
+ strip-ansi "^3.0.0"
+
+string-width@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+ dependencies:
+ is-fullwidth-code-point "^2.0.0"
+ strip-ansi "^4.0.0"
+
+string_decoder@^1.0.0, string_decoder@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
+ dependencies:
+ safe-buffer "~5.1.0"
+
+stringstream@~0.0.4, stringstream@~0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
+
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+strip-ansi@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+ dependencies:
+ ansi-regex "^3.0.0"
+
+strip-bom@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
+ dependencies:
+ is-utf8 "^0.2.0"
+
+strip-bom@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+
+strip-eof@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
+
+strip-indent@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
+ dependencies:
+ get-stdin "^4.0.1"
+
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+
+style-loader@^0.20.1:
+ version "0.20.1"
+ resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.20.1.tgz#33ac2bf4d5c65a8906bc586ad253334c246998d0"
+ dependencies:
+ loader-utils "^1.1.0"
+ schema-utils "^0.4.3"
+
+supports-color@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
+
+supports-color@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
+ dependencies:
+ has-flag "^1.0.0"
+
+supports-color@^4.2.1:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"
+ dependencies:
+ has-flag "^2.0.0"
+
+supports-color@^5.1.0, supports-color@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a"
+ dependencies:
+ has-flag "^3.0.0"
+
+svgo@^0.7.0:
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
+ dependencies:
+ coa "~1.0.1"
+ colors "~1.1.2"
+ csso "~2.3.1"
+ js-yaml "~3.7.0"
+ mkdirp "~0.5.1"
+ sax "~1.2.1"
+ whet.extend "~0.9.9"
+
+tapable@^0.2.7:
+ version "0.2.8"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"
+
+tar-pack@^3.4.0:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
+ dependencies:
+ debug "^2.2.0"
+ fstream "^1.0.10"
+ fstream-ignore "^1.0.5"
+ once "^1.3.3"
+ readable-stream "^2.1.4"
+ rimraf "^2.5.1"
+ tar "^2.2.1"
+ uid-number "^0.0.6"
+
+tar@^2.0.0, tar@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
+ dependencies:
+ block-stream "*"
+ fstream "^1.0.2"
+ inherits "2"
+
+through2@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
+ dependencies:
+ readable-stream "^2.1.5"
+ xtend "~4.0.1"
+
+thunky@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.2.tgz#a862e018e3fb1ea2ec3fce5d55605cf57f247371"
+
+time-stamp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357"
+
+timers-browserify@^2.0.4:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.6.tgz#241e76927d9ca05f4d959819022f5b3664b64bae"
+ dependencies:
+ setimmediate "^1.0.4"
+
+to-arraybuffer@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
+
+to-fast-properties@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
+
+to-object-path@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
+ dependencies:
+ kind-of "^3.0.2"
+
+to-regex-range@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38"
+ dependencies:
+ is-number "^3.0.0"
+ repeat-string "^1.6.1"
+
+to-regex@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.1.tgz#15358bee4a2c83bd76377ba1dc049d0f18837aae"
+ dependencies:
+ define-property "^0.2.5"
+ extend-shallow "^2.0.1"
+ regex-not "^1.0.0"
+
+tough-cookie@~2.3.0, tough-cookie@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
+ dependencies:
+ punycode "^1.4.1"
+
+trim-newlines@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
+
+trim-right@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
+
+trix@^0.11.1:
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/trix/-/trix-0.11.1.tgz#ffe54f2757c2c2385b8424fd5c5d2ab712a09acc"
+
+"true-case-path@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62"
+ dependencies:
+ glob "^6.0.4"
+
+tty-browserify@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
+
+tunnel-agent@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
+ dependencies:
+ safe-buffer "^5.0.1"
+
+tunnel-agent@~0.4.1:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
+
+tweetnacl@^0.14.3, tweetnacl@~0.14.0:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
+
+type-is@~1.6.15:
+ version "1.6.15"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.15"
+
+typedarray@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+
+uglify-es@^3.3.4:
+ version "3.3.10"
+ resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.10.tgz#8b0b7992cebe20edc26de1bf325cef797b8f3fa5"
+ dependencies:
+ commander "~2.14.1"
+ source-map "~0.6.1"
+
+uglify-js@^2.8.29:
+ version "2.8.29"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
+ dependencies:
+ source-map "~0.5.1"
+ yargs "~3.10.0"
+ optionalDependencies:
+ uglify-to-browserify "~1.0.0"
+
+uglify-to-browserify@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
+
+uglifyjs-webpack-plugin@^0.4.6:
+ version "0.4.6"
+ resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309"
+ dependencies:
+ source-map "^0.5.6"
+ uglify-js "^2.8.29"
+ webpack-sources "^1.0.1"
+
+uglifyjs-webpack-plugin@^1.1.8:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.1.8.tgz#1302fb9471a7daf3d0a5174da6d65f0f415e75ad"
+ dependencies:
+ cacache "^10.0.1"
+ find-cache-dir "^1.0.0"
+ schema-utils "^0.4.2"
+ serialize-javascript "^1.4.0"
+ source-map "^0.6.1"
+ uglify-es "^3.3.4"
+ webpack-sources "^1.1.0"
+ worker-farm "^1.5.2"
+
+uid-number@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
+
+underscore.string@2.3.x:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-2.3.3.tgz#71c08bf6b428b1133f37e78fa3a21c82f7329b0d"
+
+union-value@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
+ dependencies:
+ arr-union "^3.1.0"
+ get-value "^2.0.6"
+ is-extendable "^0.1.1"
+ set-value "^0.4.3"
+
+uniq@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
+
+uniqid@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1"
+ dependencies:
+ macaddress "^0.2.8"
+
+uniqs@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
+
+unique-filename@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3"
+ dependencies:
+ unique-slug "^2.0.0"
+
+unique-slug@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab"
+ dependencies:
+ imurmurhash "^0.1.4"
+
+units-css@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/units-css/-/units-css-0.4.0.tgz#d6228653a51983d7c16ff28f8b9dc3b1ffed3a07"
+ dependencies:
+ isnumeric "^0.2.0"
+ viewport-dimensions "^0.2.0"
+
+unpipe@1.0.0, unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+
+unset-value@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"
+ dependencies:
+ has-value "^0.3.1"
+ isobject "^3.0.0"
+
+upath@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.0.tgz#b4706b9461ca8473adf89133d235689ca17f3656"
+ dependencies:
+ lodash "3.x"
+ underscore.string "2.3.x"
+
+urix@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
+
+url-parse@1.0.x:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
+ dependencies:
+ querystringify "0.0.x"
+ requires-port "1.0.x"
+
+url-parse@^1.1.8:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986"
+ dependencies:
+ querystringify "~1.0.0"
+ requires-port "~1.0.0"
+
+url@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
+ dependencies:
+ punycode "1.3.2"
+ querystring "0.2.0"
+
+use@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8"
+ dependencies:
+ define-property "^0.2.5"
+ isobject "^3.0.0"
+ lazy-cache "^2.0.2"
+
+util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+
+util@0.10.3, util@^0.10.3:
+ version "0.10.3"
+ resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
+ dependencies:
+ inherits "2.0.1"
+
+utils-merge@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+
+uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
+
+validate-npm-package-license@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
+ dependencies:
+ spdx-correct "~1.0.0"
+ spdx-expression-parse "~1.0.0"
+
+vary@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+
+vendors@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
+
+verror@1.10.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
+ dependencies:
+ assert-plus "^1.0.0"
+ core-util-is "1.0.2"
+ extsprintf "^1.2.0"
+
+viewport-dimensions@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz#de740747db5387fd1725f5175e91bac76afdf36c"
+
+vm-browserify@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
+ dependencies:
+ indexof "0.0.1"
+
+watchpack@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac"
+ dependencies:
+ async "^2.1.2"
+ chokidar "^1.7.0"
+ graceful-fs "^4.1.2"
+
+wbuf@^1.1.0, wbuf@^1.7.2:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.2.tgz#d697b99f1f59512df2751be42769c1580b5801fe"
+ dependencies:
+ minimalistic-assert "^1.0.0"
+
+webpack-dev-middleware@1.12.2:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e"
+ dependencies:
+ memory-fs "~0.4.1"
+ mime "^1.5.0"
+ path-is-absolute "^1.0.0"
+ range-parser "^1.0.3"
+ time-stamp "^2.0.0"
+
+webpack-dev-server@^2.11.1:
+ version "2.11.1"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.11.1.tgz#6f9358a002db8403f016e336816f4485384e5ec0"
+ dependencies:
+ ansi-html "0.0.7"
+ array-includes "^3.0.3"
+ bonjour "^3.5.0"
+ chokidar "^2.0.0"
+ compression "^1.5.2"
+ connect-history-api-fallback "^1.3.0"
+ debug "^3.1.0"
+ del "^3.0.0"
+ express "^4.16.2"
+ html-entities "^1.2.0"
+ http-proxy-middleware "~0.17.4"
+ import-local "^1.0.0"
+ internal-ip "1.2.0"
+ ip "^1.1.5"
+ killable "^1.0.0"
+ loglevel "^1.4.1"
+ opn "^5.1.0"
+ portfinder "^1.0.9"
+ selfsigned "^1.9.1"
+ serve-index "^1.7.2"
+ sockjs "0.3.19"
+ sockjs-client "1.1.4"
+ spdy "^3.4.1"
+ strip-ansi "^3.0.0"
+ supports-color "^5.1.0"
+ webpack-dev-middleware "1.12.2"
+ yargs "6.6.0"
+
+webpack-manifest-plugin@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-1.3.2.tgz#5ea8ee5756359ddc1d98814324fe43496349a7d4"
+ dependencies:
+ fs-extra "^0.30.0"
+ lodash ">=3.5 <5"
+
+webpack-sources@^1.0.1, webpack-sources@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54"
+ dependencies:
+ source-list-map "^2.0.0"
+ source-map "~0.6.1"
+
+webpack@^3.10.0:
+ version "3.11.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.11.0.tgz#77da451b1d7b4b117adaf41a1a93b5742f24d894"
+ dependencies:
+ acorn "^5.0.0"
+ acorn-dynamic-import "^2.0.0"
+ ajv "^6.1.0"
+ ajv-keywords "^3.1.0"
+ async "^2.1.2"
+ enhanced-resolve "^3.4.0"
+ escope "^3.6.0"
+ interpret "^1.0.0"
+ json-loader "^0.5.4"
+ json5 "^0.5.1"
+ loader-runner "^2.3.0"
+ loader-utils "^1.1.0"
+ memory-fs "~0.4.1"
+ mkdirp "~0.5.0"
+ node-libs-browser "^2.0.0"
+ source-map "^0.5.3"
+ supports-color "^4.2.1"
+ tapable "^0.2.7"
+ uglifyjs-webpack-plugin "^0.4.6"
+ watchpack "^1.4.0"
+ webpack-sources "^1.0.1"
+ yargs "^8.0.2"
+
+websocket-driver@>=0.5.1:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb"
+ dependencies:
+ http-parser-js ">=0.4.0"
+ websocket-extensions ">=0.1.1"
+
+websocket-extensions@>=0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
+
+whet.extend@~0.9.9:
+ version "0.9.9"
+ resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
+
+which-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
+
+which-module@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+
+which@1, which@^1.2.9:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
+ dependencies:
+ isexe "^2.0.0"
+
+wide-align@^1.1.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
+ dependencies:
+ string-width "^1.0.2"
+
+window-size@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
+
+wordwrap@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
+
+worker-farm@^1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.2.tgz#32b312e5dc3d5d45d79ef44acc2587491cd729ae"
+ dependencies:
+ errno "^0.1.4"
+ xtend "^4.0.1"
+
+wrap-ansi@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+
+xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
+
+y18n@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
+
+yallist@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+
+yargs-parser@^4.2.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"
+ dependencies:
+ camelcase "^3.0.0"
+
+yargs-parser@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
+ dependencies:
+ camelcase "^3.0.0"
+
+yargs-parser@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9"
+ dependencies:
+ camelcase "^4.1.0"
+
+yargs@6.6.0:
+ version "6.6.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
+ dependencies:
+ camelcase "^3.0.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^1.4.0"
+ read-pkg-up "^1.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^1.0.2"
+ which-module "^1.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^4.2.0"
+
+yargs@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
+ dependencies:
+ camelcase "^3.0.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^1.4.0"
+ read-pkg-up "^1.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^1.0.2"
+ which-module "^1.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^5.0.0"
+
+yargs@^8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"
+ dependencies:
+ camelcase "^4.1.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^2.0.0"
+ read-pkg-up "^2.0.0"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^2.0.0"
+ which-module "^2.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^7.0.0"
+
+yargs@~3.10.0:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
+ dependencies:
+ camelcase "^1.0.2"
+ cliui "^2.1.0"
+ decamelize "^1.0.0"
+ window-size "0.1.0"
diff --git a/actionmailbox/test/fixtures/files/avatar1.jpeg b/actionmailbox/test/fixtures/files/avatar1.jpeg
new file mode 100644
index 0000000000..31111c3bc9
--- /dev/null
+++ b/actionmailbox/test/fixtures/files/avatar1.jpeg
Binary files differ
diff --git a/actionmailbox/test/fixtures/files/avatar2.jpeg b/actionmailbox/test/fixtures/files/avatar2.jpeg
new file mode 100644
index 0000000000..e844e7f3c6
--- /dev/null
+++ b/actionmailbox/test/fixtures/files/avatar2.jpeg
Binary files differ
diff --git a/actionmailbox/test/fixtures/files/welcome.eml b/actionmailbox/test/fixtures/files/welcome.eml
new file mode 100644
index 0000000000..5d6b3c1ea5
--- /dev/null
+++ b/actionmailbox/test/fixtures/files/welcome.eml
@@ -0,0 +1,631 @@
+From: Jason Fried <jason@37signals.com>
+Mime-Version: 1.0 (Apple Message framework v1244.3)
+Content-Type: multipart/alternative; boundary="Apple-Mail=_33A037C7-4BB3-4772-AE52-FCF2D7535F74"
+Subject: Discussion: Let's debate these attachments
+Date: Tue, 13 Sep 2011 15:19:37 -0400
+In-Reply-To: <4e6e35f5a38b4_479f13bb90078178@small-app-01.mail>
+To: "Replies" <replies@example.com>
+References: <4e6e35f5a38b4_479f13bb90078178@small-app-01.mail>
+Message-Id: <0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com>
+X-Mailer: Apple Mail (2.1244.3)
+
+--Apple-Mail=_33A037C7-4BB3-4772-AE52-FCF2D7535F74
+Content-Transfer-Encoding: quoted-printable
+Content-Type: text/plain;
+ charset=utf-8
+
+Let's talk about these images:
+
+
+--Apple-Mail=_33A037C7-4BB3-4772-AE52-FCF2D7535F74
+Content-Type: multipart/related;
+ type="text/html";
+ boundary="Apple-Mail=_83444AF4-343C-4F75-AF8F-14E1E7434FC1"
+
+
+--Apple-Mail=_83444AF4-343C-4F75-AF8F-14E1E7434FC1
+Content-Transfer-Encoding: base64
+Content-Disposition: inline;
+ filename=avatar1.jpeg
+Content-Type: image/jpg;
+ name="avatar1.jpeg"
+Content-Id: <7AAEB353-2341-4D46-A054-5CA5CB2363B7>
+
+/9j/4AAQSkZJRgABAQAAAQABAAD/4gxYSUNDX1BST0ZJTEUAAQEAAAxITGlubwIQAABtbnRyUkdC
+IFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAA
+AADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFj
+cHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAACGAAA
+ABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAAAIh2dWVkAAAD
+TAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJD
+AAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5
+OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEA
+AAAAAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAA
+AAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAA
+AA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAt
+IHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAt
+IHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcg
+Q29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENv
+bmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAA
+ABOk/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAA
+AAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAK
+AA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQAJUA
+mgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUBKwEy
+ATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMC
+DAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMh
+Ay0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4E
+jASaBKgEtgTEBNME4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3
+BkgGWQZqBnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDII
+RghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqY
+Cq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJECYQQxBh
+EH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT
+5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReu
+F9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9oc
+AhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCY
+IMQg8CEcIUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZcl
+xyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2
+K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIx
+SjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3YDec
+N9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1hPaE94D4gPmA+
+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXe
+RiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN
+3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYP
+VlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1f
+D19hX7NgBWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/
+aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfBy
+S3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuH
+n4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBukNaRP5GokhGSepLj
+k02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6f
+HZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1
+q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm4
+0blKucK6O7q1uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZG
+xsPHQce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnU
+y9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj
+4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/8ozz
+GfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t////2wBDAAICAgIC
+AQICAgICAgIDAwYEAwMDAwcFBQQGCAcICAgHCAgJCg0LCQkMCggICw8LDA0ODg4OCQsQEQ8OEQ0O
+Dg7/2wBDAQICAgMDAwYEBAYOCQgJDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O
+Dg4ODg4ODg4ODg4ODg7/wAARCADwAPADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAEC
+AwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0Kx
+wRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1
+dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ
+2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QA
+tREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYk
+NOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaH
+iImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq
+8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9v1Wob5cWEh5q4v3qhvlzp0gz2oA+XvEwiTXbtWwTuJ59
+6/Mn4tCGP9p+OabLR5UEKeB81fo345uPK8Y3lvnkj86/M341XaW3xuSfjYeWz3IPFAHv+r6mINN0
+LdLt3na+Bnj6nmvtn4ISiT4eaeN2VVSAfXrX583Eiah4L8PrCgeVmGZT2yucV90fAZnTwLbiQ/vQ
+SKAPrjTseWMVsL0rhdU8UaF4R8E3/iLxJqUGkaLYw+Zd3UxwqD8OSScDA55r4n8Yftla1r0l/bfC
+rwxcQaWG2w6zrVs0YkyR8wQ4IVgTtJGTQB+iEl1awRk3FxFEoGSXcKB78kV5n4m+MHwu0W5TStY8
+c+GbS/mDlbd75C20dWO0kjFfiD8Y/ib8V9e1Kzmu/Ef9owLcx3U9pZ6o8TSIzlGgAyAQvXH+1XkX
+iP4ca0uq3ev6fJHo2nXOnTCeVSX8iKVlQfvDncVYqrjrhgegoA+xfi9J+zR4ul8RfEZ/iO/2G5u2
+imtYLDfdCYHaDEpwQrY9ORXhej/DT4A61q2oahonxIkdbexa7vYdV0do4lj3KkgV93Ubs4+pr4ck
+8F+LLmaz068WKwuWl2hJnIFwFTd8vcDaAQehwa5PWfE15HomnaQt1NpMls6xsshDCRxv2yAqM4wS
+Dj1oA/c/9mz4O2Pwt+OGpeJ/D9/4e17wvqaobHU9OvUm2wkE4bPKnPav1XsJ1n0xWVw+ByQfbP8A
+Wv4/tB+N/jHwHbKPDGuXaRXOyS5tIZCsSspPzIeBj2PNfU3w2/4KK+NfC3jjT73UL3UbnThax2dw
+s0rSb23sTcFehbaQuP8AYoA/psHRfxplwhe1IHPB4r81Pgf+394d8ZR6g3ia7tJ7CC4X/TreJ4sR
+FQNxRhuyGznAxzX6SaNq+l6/osGpaVeQX1lOgaOWI8MD0oA+SfjX4S8Rz3yX9pcB9PLbJY8cqCOu
+fxr4J8ZeAr6y1YbJ237+cnOa/YfxtZRT+E7oMoI8tsHNfnX8Q7i2ttfjMwjVdvJPrmgDrf2UPhdb
+vfap4q1PM96lwILTP8AwCx/Wv0UtoPs8CoDkAcV8q/sx6jb3nw4uli+8l64fjHOBX1sBx1BoAjC8
+Uu3PHpUoXj/69L0oAwNZ02C50e4WVN4dcN7jHSvgPxR4b03S/FGrQrbeUBOzqM4wCOBjvX6H3p/0
+F8gkbT0FfnH8d9e/sLx1fGSC5hjkAw7IQrHDd6ALPw9ttNj+K2jzMka4uNqZ+9nFfonZL/oEfPav
+x7+Gev8AiDxP8YtLXw9ps+pXVvcrLNGH2oiZxuJ+nbrX6+6OZjo0AnULJs+bFAGnTgMU6igA7V81
+fHG8srDRLWa5kiTbNxu6/dNfSh5U151488D6R4t8OT22p2a3MRXj1U460AX1B80U26QnTpR3xVlV
+4x39ajuQTZyYGaAPjT4nosXjGeXG1sYQj1r8u/jq+34rQzNgAHDhugz6V+qXxYg/4qhwOAw4NfmB
+8e7Zk8dWqlR8/V2XIHpQB6b4Uxd/DPSIRMWLMHDKPbpX1T4U+JPg74VfCsav441eDRY2ceTE0bGS
+VicAIO5JwK+LLDxloPgfwHo+i6xLNJ4mmKyWljaL5hmQ92C9Fr6j+DX7OF/8afHlp8TPiLHfw+Hh
+KH0zSrp9yKi9BtI+XnmgDfu/D/jT9pXWLHUNVguLLwjHcpcaVpkTsINqnlrg/wAbnghegr2vw9+z
+TpHhnTpILxm1ppJzcKbvDorduP7o6AV9g6Zoum6Lo8NjplnDa2sSbEjjQKBirMkAkYbwuPpmgD8a
+/jx+zvf6ppUy3dxp+kWVjKj6febREFk3Z2lgOFI6+4r5q0eP4p+HPi3caBDp9t4u0a5sJ7q50CZU
+P2YpGFMgJ/5ZnCsMZzX9A2t+GNK1rSJrK/topoJVZGBQelfF/j/9mG1l+IGn+LtKudSi1exiaG2k
+trgoAmDjev8AEP4SO4JoA/Jn4m22kReCfDWtabpuqaNf6jDHca4uopi12qCQ0DDn5gSMDtmvhXUd
+Klt/FkkxZ4nW28+1Mluf3SqcBVz13Zzk1+wvxq/Z08QWvgm0t9Age0tjeiSexsZz5NsrqRIsCvzs
+blmX+E4xxXx/rHwB8ceIYtXWSwvnksdtnYwYO+FCOPlxkDHPNAH58apJfzeIDbugaNQ3mb1wVz06
+VNo+hXd9enT0i8+Vx9yEZI9zX3p4J/Y18Za7dPealGYAB5ayyRk9OpAxzn+lfaHw6/Yw8K+GjDdX
+0dxM+P3m9fnYkc8HpQB+afhT4KeO9Z8E2S6Ok1rLMrRho8KyRHHJwfUV9X/D74+fEv8AZ/8AEtho
+vju/8V6Jp9nai2XU4ZfOjuGByMoflBxxn0r9JtC+HGl6Do8dtaW8duqR+UmE/h+vrWb41+E/hXxx
+4PudI1zSYL61mG12ZRvPGMhuxFAHtXgj9pbwN8bPg8jeFNds5tYe1HmxHLFHK4G8fwkn8K/O34ze
+IvEGh/GG80HW5Qt7AqsHAIRgScYzx1r558cfDH4n/ssfE+08ffC+8vb7w+rGG5tjllkhbOYpFB7A
+5DV2Wl/ETSP2lfhdL9tk0jS/ijo48i3+1XbRrLGTkELn5iP6UAfr5+zJ8OLvwr8MotVvdQuL251S
+NLqRWOFjyoIAr61UYQD0FeQ/BN5z8APDEV3JHJdQ6bDFMydCyoAT+lexYB96AGUYNPwKWgCNkDQl
+W5Br5T/ah8I6Zf8A7Out3clsslzbAXETAYYEH1/GvrCvD/j9bNdfs2+KkQAuLByM98YOKAPiL9iq
+K3HxT8cW5jVnUQvGx6gZI4NfqTGirEFUYGK/KX9ja6Fv+0p4rtndf3umo+M9w4/xr9XEIMS4I6UA
+OoozSblHVh+dAC1VuhnT3HXipvMT+8KqXdwiWL8joe9AGAoPPFOkX/RXzTgCDTn/ANQwoA+R/izF
+/wAVKGPKlDgetfmh+0E+nLrFn54dL5lH2TYMtI5ONuO4r9Jfj/qFrotmdTuXRI44mLEngcGvz9+H
+XhO6+Inx2/4Tnxf5ptYbh10TTwMpGRwHP6cUAdp+zF+zJP4z+IFv4g8a28xWGOKYL5eCADuC8/qK
+/aDStMs9J0W206whjt7aGMJHGiYAArifhp4dj0XwHAxiQXUyhpXUY5x0+mK9LVcUAMKcVWlyGGOl
+Xz05qpcDEZNAFB3A61k3Gx3J6n0x1q5cPgEd6xJJMTHNAFC/0rT7tClzbQSIPVBmvMLn4e6Rb67c
+XdlZwq07hpiRncR06V6dNOPM64+tQF8HcckY7c0AcFb+FNNhgYC3i8wdWCbRUdzpdlGuFjHHcCuq
+1CYpFlQ2Se9c1PORuXkkigDmL62jWJAqfxelYEtuU4UZ56V092ZPJL/dweprJkQtuc79w6YoA4vV
+dJtNS0u6s721guEkRhtdQRyMd6/Ff9qf4KS/DL4uDxZoEVxaaPeEOPs+Va1kGfm46jJz+dfuVNGs
+8GSxLA8jvXzp+0B4Eg8afA/VrN4/KuooWaGYLlhwTQBf/YR/aLl8QaBp3grxRqNtc3gsYza3sT5F
+zhcEsD0bjkV+pIuo/LBDhhjrmv4+PAfjrVvhN+1FpHm3t1pWmremGa6tXKzW7HjfjoRX9J3wU+L9
+j8Qfhdavp2rx6rqdhGq3gA2uRtGHI/2uDn3oA+uDexA4J5+tMN/ED3x9a88S7upow2GTIyQDmmlr
+lpMGQ+2TQB3ralEGPP615D8ZdTiPwM8RrlfnsnABPtXSfZrhxuJevOfifo0138LdVjILZtmwPXjN
+AH5xfs++Jf8AhF/2v4HYOUvYWgfnjqCP5V+v9tr0b6bGyvu+TjFfiz8PbQXX7WXh2EKoR7g/pX7H
+abpoGiwnoAlAGu+vjPy5P0qBtclz0asXUL3T9NtmeRkXb97ccYrjJPiT4Ztrry5NUsVc/wAJmWgD
+0Y6pdO3y7h+FVLy8v205znsccVFouvaXqtukkE0UqseGVwRXVfY4pdPZlwwPSgBPtCD+IVFJeIIH
+5HtXnD6+4J5HHvULavcSRcNgepoA8J+P+iSeMrvTPD6TbYJnMt4yx5PlqckZ9T0qD4VeA9Ni8TaW
+tvFi1tmJIcY3MeRx9Bmuj8W6xHpxS5uZYxc3Eqwwr3bJ5/CvSvh5aK0cV2xiUMRsjVcHPTJoA+g7
+RFjsokUAKB0FW6rW/wDqUHtVmgBrMAue9U5zuXOasP8AKMkZ9qpyAtGTnA9KAMm7GMk9MVzUzYbk
+nJPFdNcgDJOTx6Vz158iltvXrx0oAzWAOfm/IVI6qUXLP+Aqtna2BuJJx0rXS3DRZPpjA9aAOS1A
+b3cIWYD1rl5jiZvvbSOSD0rvbmyzIQCVbGc/zrlru2xFKwQhc/dFAHPSAtjIOzoCeap3EZcGJsq2
+3IYKeaufP5qD5goPAxSF90rHf83TINAHNtbqB8uVnJwc1zfi23YeDbxRF5p8k7sLmu/8tTMemeu4
+jNYniC3WbRXH3d0TKcHAORxQB/O/+0X4TGnfHDV7kwAWtwxLxhcEdwR719n/APBN/wCM1hpHxE1H
+wVr4cXs8I+zXTHLSRDpG/rjqPbFeVftRaOV8a3lvL+7uoyT5eM7k/vZrwf8AZ28Rx+A/2vdC1yec
+WtlHGxedVDHGRxg98n8qAP6MvFHxg8MeHb2W2uNVtRMibvKjILYxkfoRXlQ/aT0l74Rw+Y6g5zuA
+r5TtfgP8VPjF+2r4k1SDV4dF+G8kUElvejLyT70VnCqOnJIHtivraz/Yl8FWunfLrPiFrwIMyNKv
+X1xigD0nwd8dvC+tahDaz3y2t052rHK+M/TtXr/iXUNMvPAN5+8WRXtmIIYYIINfnR8S/wBnbxf4
+BsH1jw/e3Wv6VCSWiCDz4sd8jtXI+Hfin4rm8ItoralLJFHF5WyWP94oHGDnnigDiPCur2ui/tfe
+H724Kx20OqOoI7gsRX6x6z8QtE0P4cyatdXESQRR5GJBuNfj9eaHv8epeyNIiRyiQtjGD611PjLx
+l4n8QX2keCdDmu7/AFC72xwxLJuGT/EfQD3oA6b4k/H3xZ418bvo3h23vnDyYgsbI73YerEVgwfB
+74/arp66uPDDxqfuwz3KiQ/m1foN8Av2evD3w58K2t9c2seoeKriPdfahIoLFz1VM9AK+pU022SM
+KI0X6DkUAfiRo/jz4k/DLxoIdRg1nQLuFvmtrk5hkHoCTg56cGv0n+BvxusfiT4ZuYZ1S01i1Rft
+NvnoOPm/Wuw+Lfwp8PeOfAF/a32n273Rib7PPj54mAOCD9a/LPwh4lvvhH8eZrmTcFj821vR03gE
+YPv0oA+wdT+JOmWCmeXVokY9FZhWt4O+KmieINdOnw6lZXE687N3NfCei/sifHfWvD0V14m8e26X
+fa2SIkD8a4bWvhr8WPgd4ph8QXEyX9tZzh/tUbZ3A8EP/k0Aff3j3UDd/GvT9Osx5zgRFEYZX5jy
+R9BX1Z4LiA0a0d1EcxGWGMdsV8QfDjVLnXviPc6zf4897G2KW5XhGZclgTX3/wCErMy6fFKuPLRQ
+oHtjP86APQbc4jUe1WjytQDCqPYU+WaOGAvIdqjqaAGzEbTyOlUTINmM8is+41yyDOvmqFH8R6Hm
+sefXrMT7FuIM9wW6f0oA1rmQFWAZRxXJ6nchoGUOcj0ps3iHTPtBj+22zT4zsVwTzWNdXkM29VYE
+hucUAWbb57sFi5Xp1rrbMp5Wzg/hXHxPHGZGEinH88ZqzpmpCe9aLzASvJxQBq6tIsKk8Z71wt7d
+K1wI/wC8Ogre1qVpZv3WXyOOeK4p3mMiO6qnXnPH60AMmh+csMHH+1VWe1PlZBNOa4T5klnVJeoD
+EAH8azn1yzRGhN5ayFW+crKp2/rQBYVGR8E/LjFEkMc0Dps8xAhDJ7mnFormz3wzblOfmXnBHak8
+mS3tFcgNuOD3P40Afkb+11p0ln8QEfyE+0NG2GI5MeT8v86+FdOt7P8A4S/Ss23mWksiM5Xg7DuW
+T/vng/hX7J/tYfDNvEXw+/tvTYQ8tsv71UHzNX463dmdE1WeHMqtFd9BztznP/6qAP6Mf2MbhtW/
+Ye8KXd5GXvbMS2byt/y2WORljf3+QLX2AqgghcY+lfAX/BPnxRbah+wRplq0redaatcwN5h5ILBx
++jCv0Et9phDD5uOwxQBmXmkQ31q0UsaFXUhsjOR6V+f/AMd/gMnhjVpfHvhmN47czF9SskXK8nJd
+cD3zX6Odq5/xLpMGteDNT064UFLi2ePpnqp7UAfij491a2ttGa4twhDruyD1Fe+/sYfDo65far8T
+NZt/MneU22m704SMdWGe5r5R+KOkXeia5rGhXqTI9lcPCMjhlDcEfhiv13/Z68NQeG/2b/C1jArA
+LZISSO5GSaAPdoohDbqiAcdKmzmkZgqkk8Cub1bxLY6VCXupo4VHdjigC7rlxFBocrSHACE5r8TP
+ifa/8JZ+0JqGnaQHmlv9UkjhCdTzzX3P8bvj5YWHhe/0zRLuOS6ljKtcI3CDHOD0zivGv2VfhjqP
+iv4nT/EvxDaulhb7otLjljIMjnhpORyOeDQB+gMccSLsVVAHQcV5r8SPC2m+IPAGo2d5AJUmi+Yd
+jg969IxtbJzXHeONUt9M8FXs88ipEsRLMegHqaAPjn4b6dLZ/F7UQd8sjgMGEoIjijyAAv04r9KP
+DlukPhi3YIwZ1DYPB6V+cHgvxRpD+O7S+02CNreOUW81ymPl3PkCTngZ716n8VP+ChX7MPwN8S3P
+hHxh4t1S+8UafDG13p2jaa9w8bOoIXcSq5wc9aAPuaV/LhLBCzcgDsa8u8R+IL5JTH87IeCqDBHN
+fOPgv9qH4sfG/wCHY8S/BT9m/wAQR+GLpd2m674+1uDR7W9TODJHHH5spXjqVGe1a2sP+1rLpLTz
+j9mnwyQMiSS+1O8KDv1gUUAcl8TPH/jDw8zvY+Gtf1lQ/wA6WKDESdiMn5j6ivknxP8AHT4k6pLP
+YaZ4A8QafbSykW9xJK6SSN0OVwdvT8a3PiR8Xvjf4X1aTTda+L/wLudRmQywWml+E7uYEDqCxcYP
+1r5ktv2j/jPca1DDc3XwpmBnKLnTrm3Zz6/KzYoA9Eh+IPxIu9YjkvfDWoadqUISIXKxMzEbuWJ4
+6fSvpnRfix4h0uG0tdVmlluDKUQgE+cOCD+Wa8Y8M/FD4rX2mC8b4c+EPGzRDDJoPiTZdfMM8xXM
+anHXvXRaR+0B8Kz4ks9A+JPhTxd8INWL7EfxXpgjtAxz9y6jLR4PqSBxQB92eHfET6ppqzApIzxA
+8HjJrQh1uPR9f3zMiIRhgetWPB3g6K68GWWp6K1td6dLGr29xayCVJlPRlZcgqRz1rn/AIheGb59
+MutsUlvc+WRHIVOM/hQB5F8Wf2l/C3geymhkik1O6jO5bZDjdnjqK/Orx9+2T8QNR1NhoerT6RZw
+zOu6KNeehA+YHpz+dYnxzbR9C8feX418U2sd+rsfs8MvmSYHT5Rz36V5nZ3GhT6TDcaT8KvG2r2d
+wQYrq9gjtYpj3I8xt2D64oA6Wx/bB+KGoavDa39zc6lbBSZHiiAd/TOAK7VfiF4x1uddRsbTxLbS
+mPDfZ43ZexYkY54xXEWPxO1zwzrzR6Z8EfCNo1uVMi6nqgLIp6E7UP6E19D+B/2lviRf+Ko9Mt/B
+nwb0uW5IEEOo63NbRMMfMRIIiOlAHT+BPF/xOQrNFd3UIkkU2ofiM9jvBPfHpX234E8bXXiOxey1
+zR7rRNVjj+ZiQ6SEfxKQeh9K8X0bWPjfq+lpqy/Ab4WaxpUsfE2g+OUcyYJyQssKjP41har8edR+
+HYWbxz8APjd4aslc4vNM0uDVYkA65+zyFto65K9KAPqnXtNg1TwvPaXG0FlwrEZGcelfz/8Axn0t
+9G/aB8T6e0oSVbyRQDHtzzhSB9K/XXw9+2T+zZ4xttkHxU0bSL1JhG9nrkT2UyOMcMrjj0r84f2r
+H0iT9rqDWdOvbG/0TV7Tzbee0ZZI5M9JEYcHoR7HigDa/Z0+PHin4YfD3S7fT72M2E2tTxypOpEb
+nZGcA9mr9d/g3+1Jpvi3xrp3h3WLM6beXqYt3WTekjDGRwetfN/7CHwe8LePP2ANXufE+g2Wrwze
+Mbs2b3MKkgJHGmVI6DKnv1zX2r4U/Zw+HnhTxZHq+kaDDb3cZzE+4nyz3Kg9KAPpKORZIwynIIzm
+ormZILOWWQgIqk5J6cVSghktbVETJOcc9Kp+IrG41DwxdW0TbXeMqCvagD80P2jdD0298WanrkKW
+y+bOAz7hg4719Ofs5fE7TPFngCHTUljXUdOjSK4iB6DHB+lfD/7QX9taJruoaBqAkXyJfMR8fK6c
+4P1r339iv4e3dj4Nv/G9/Jum1kr5Ean/AFcanA/WgD7/ALzzG09vL5Jr4B+NHgL41eIfiIp0OCHU
+tFf5IlFwYxCfVh3r9DQuItpwaryWkUsgZ1DEHJJHWgD4D8AfsmtLfWeqfEW9Or3Snc2nRDFshz39
+a+6tD0Kz0bSYLSygitreFdkUUabQorZSKONNqIq+mBVjjbz1oA+XvFfxF0nw7EzXN4iEKTtB5OK+
+BfjF8Y/G/wAUdI13wX8IfC+veJ9R2hb+ewt2dbVSQAXIGAD/ACr6O+MHwBuviFOHh1rUdJlAIL2s
+hGc19E/s+fCjw/8ABX9nqx8O2Amnury4kuNR1CfBmuJGOAWYegGAO1AH5p+D/BPj/wAMyQ2nikRa
+JDPpyz61ehCymBEIkG0DJcdABzmvCP2tf2O7Dx18B/Ev7Tnh+bxfoWvJDHe3vhvXIVEcunxhEyvA
+aJ/LBkwxJGce9fu14x0XRPL0uQ6fay313qEUcbsD8gzuYj/vmrvxK8Gad46/Z38a+D9St47iy1rQ
+rqyljK8kSwlev4/WgDA8B3+g6P8ABnwh4e0wC107TdCs7e0j9I0gQL9eMc9818c/tQ/GDVbLTV0n
+wxBc3l3NP5dvaxNtkuJMckgZJQf0r6I+CenQeLf2BPg7qkgY6u3gzTrbUW6EXUFskNwrZ/iWWN1P
+uprc0v4baZ4d1afU2tYLnU3yPtdzCsjop6qpPQfSgD8qvjP8IbL4cfsC3HxK8V+E9R8Y/EnxD5Vs
+ki3726aCZlyJSF4cL0wcZJHNflh8Ix4k8U/H5tNsYtdvvJMkxiRAjEA4AmJBABweByQeor+pTxjo
+un+I/BOpeHdat7PVNGu4TFNa3AwpHb6Edj2r5MtPgr8N/h3Ndy6FZXuixTNulSDVZZBIPT5jmgD5
+C1Pw/p3w2+KulaUuqXdrBeW6SQNE7+ZYyPjfGxA5jB7E19C+G7G713Vrzw547tNM1bwRJYsNSS7U
+SW7W7Kd8nIOAF+YntRd+AND8R+N47tNEdLBAwmuJ3O+XJ7nOTXR/Gn4d+JdP/YWtbzwpd22n6jHf
+2VlpOlSs63GtXEsywWlkrKwxvmdC+cr5YcngUAfGf7IH7JHjz4taz8X9Z0j4+/Ev4W/AbRvF19ov
+hy38O6i6XGpmCU/OvmZVIkRkXhTuJI42nPo/x1/YZ+IHhb4banrPw/8A2qvjLqGpRRM4tfEV+Xiu
+DtJ274ymzOMZINfqv+zt8J7H4Tfsd+Dfh5a3BvU0OzaK4vCpH2+9aRpLy6YEk5kneVgMnAIAJxmu
+u8X6dZXPh+5tbmKOWCRSrKy/ez26GgD+YT9mzwx/anhjU9cv0XxH8So9ceC9+3sZ57dUO0Ehs/KT
+u59q+1Y4NQ8R/Fa18Ixam8d+I9mpXgcFIU7xRA8AkZGe2OnFeJf8Kq8W/Cj/AILBvovhTWrHT7TV
+rifUIxODtvbVsG4hAx80mzayDsd1fTllpPh7SfGP2m20bzNQhuGaaOclnQMxzznnrnNAHwF+07p4
+8O/FLxDp9l/bemT6TqAt7Kz8t3gS2aNT5rOGyzMT9PpX1Z+yP4A8PfHD4a6x4Z1HRr+WXT9Jjnm1
+LVblW+z3TMw/dKACkZXB2kk5zzX1BrXwM8EfFSW31G8SW11Z4ET7RDc+UxxgAOCGzwK9o+HnwCm8
+A+Fp9E8LS2WmWFzk3twDumuTggFnAHAAHGKAPjT4aa/47+Dvx1l8Im4v49Fiumt4lnB+y3MYPRc8
+KT/eFfpA3jjTpvBUOoGb7HOQP3DOQ68cjj/JryM/Ae61W626tqF1qQChYy5ztGc4Bz2z1r1Pwl8J
+I9Itxb6jqEt3GhwiyYYgdsk0AfhV+3l8OIPHn7efhq58A+HILK+1vQpJdUaO08uOV4JGDXDgDrtI
+BOOcCvcvhH8GvDPxQ/4JBaN4Og1nTrP49/DbxkbeWOdtsws7+72qjK5BeB1lVlYZwy4r9FW8B+Hr
+z/gpBq2rf2dFLHoHw+t7BUl5Ec95dzyv14OY44/w+ted/HvQ/C/hn4t/C/xbqejaV/Yt3fLo+pq8
+KgbWYywPlRkMkq5U9iaAPrL9jLwU/wAPv+Cavw10i8Ahubq1l1K5DJ5e1riV5QCD0IVlFfU8TB4l
+ZSjIehVsiviXWNNubvSDd+ONa1PXtB0uAtZ6X5ax20MajCqIYwokOAOZNx54wMCu4+B/j8ap4hGi
+xWk+naTNAWs7WVt5jIG4Ef3QRn5e1AH1VwR2NI5+RvoaUfdFUdRuUtdJmmdtoVGP5DNAH5k/tXQr
+qnxIv44UDLFbAOQM8n1r139izxXHqv7PEekOcXWk3L20qkYOM5XNcN9mj+IXxJ8bM4+0Ib1xGSM7
+VAwB+lcx8FHl+E37buq+Db92gsNfgWa0APymRf64oA/UCiobV1ms1ZWyCM5qzx7UAJt5p1GR60ZH
+rQB5oFBYE5OPXnNdLIobQ7FAdqMyjA+tYIGOxroLMCa1tQ/PlzDP06igCj4jgjl8UeF45BuAvmeP
+nphD/jXYj/V+uBXBa1cl/Heil8lYZ2x6DIxXcr93k9s8UAfLOnaT8Zfgv418VW3hrwpa/Fv4Vajq
+k+p6NpthqEFlrOhSXEjz3UJ89liuIGmeSRNriRd+3aQAak1f9prwZpemLN408EfGTwLCx2tPq/gm
+7ECt3HmorKfqODX1HkZPSqUzSeWyoxXjggkYoA+Gte/aL+BWoWgvI/iXaWMDtjbcWU8b4z3UpkV5
+PqXxz/ZyFw0svxH0zU5hu2KqTysfYKEr9D9R077W379VmbGCWUHP6Uy28OWULCUWkEZA/gjC/wAh
+QB+f/h/46fDjVr54fCnhT4p/EK4i+ePTvDng27k8zjJUyOioPxPevpTwP4V8feOPHWj/ABC+K/hq
+x8D6RoTyy+CPA63S3U1pLLGYvt+oOvyNciJpEjjQlYllkBJYgr9BxSNEwjMj+X0xuPNacWLm8RcH
+YvJPqaAL9lapa6XFbxLtRVwAa8+8a7k06QxqTg9MV6TI6RozOcCvP/E9wJbCYLjBFAH53fHb4Y3X
+i7WdJ8TeE7m30H4i6O63PhvW5Uylrdx8eXIO8UsZaNvQEHtivID8WvC8lzbWHxc8M658GPiKF23M
+txpslxpV2y8GSC6iVl8ts5AbBGelffWoWMFxJcQTACOTBBB5B9RWVFa+TcfY50MJPPnFidwH6ZoA
++cfC/wAVvg7dygJ8SPB2+JwhY6ksbDA7bsV9A6f8Tvh9DYxsPil4OWA9PM1eEDHv81WJ/B2kaoJJ
+L3SNC1IN90XenRSH8SVyafB8LfALMDL4D8Fyucbn/seHI/8AHaAHTftCfATQrQR6l8Zfhxbyj+/r
+MZP5AmsHVP2sfguNKkg8LazqHxH1mVCtvpfhXSZr2e5bB+VcKFGf7zEAcEmvSLLwd4S01Qtj4U8M
+WpXo0WkwKfzC5roBI0dm0EREUWMBIwFAH0FAHiPwo0fxRHpnijxr4901tD8VeMNXGoy6ObgXD6Ta
+JCkNraO44LpGm5tvAZyMnGa8Z/bVvIbb9ky1iZGMp1eAxvHj5DuJLc9OlfYshYq2CDkc1+d37eGq
+xxfBXTtOLMBLqiRsVHAUAnn60AfdOif8Tn4U2+p+YJ0utLjdeRtY+WDn8a4r4KpJP8Z9LCblEM0m
+Qp4AAP515d+yt4qutb+DGlaZdXD3It7IQhT/AAqFwB+VfSnwQ0dLG+u9SlUAxvOIiepBkIH6UAfV
+W84POB2rxv4yeJx4f+EuqT+ZtkaMxx4P8TcYr0s3a+QSzEfjXx9+0JrP9oTaPoolyjTGSVVPp0zQ
+ByfwJsp5tV1ed1LISvmHH8Z61yf7UGj3mg634W+IOmq63uj3auTGMEr3BPpXuHwKt47XwnOzgB55
+y+K9I+Inhmy8VeBL/TbiFJ4ZIiNuAeaAOw+Gviu28VfCrRtXgkDR3Nsj4J6ZHI/OvQ/MFfn1+zn4
+rk8F6trnwy1u5ZJrC8drIynG6JjkAfSvtM65B5akODlc/e4oA7EzKD1FMNwgNcI/iK3BO6WPHfD9
+KzpvFtlGfnuY1696ANlQW2kdDWtprkNNGccgEfhWJZyCWwjcNkHpitmy+XVYm/2se3IoA5LxvdjT
+tSgmLFVDeYTnHCjcf5V6Jp9yt1o8E6n/AFkSkV5x8XdP+0fCm/1CGMNewrtjBOOvek+FviWLXfBA
+XcBLEem7PH/6xQB6pTTtbgioy3ynHWkDYGc80AQmKMyHIxj0rPupdsfyk4qzNMVjcnGa5y9vMAAD
+B5oAhmuQsuWLYBya7PTgselRytw0nNeVNPLcXgjHzMeta/jKXUJvhrJb6bczWN3JaNHHPFwY2IwG
+HoQeaAOm8QaqkFiMH5jxgHp6V5zrOqwiwhikLnzCdxHavnX9nLRv2lbfwr4z8M/G7V7bxLo1hOh8
+Ka5cbFvrhDu3JKVADAALg4B46nNdPrN1qi6w9lcKsUsTfL5zbRj19xQB18dul9NIYgvykAkZ/X0r
+Onl2aqLCeGMSIMlycg185eGNK/aGt/2ztf8AE/i7xRFB8K7a3aHRdEsI4xbzqwAEr4yxb6n8K+gd
+RlF9NBLHuVo4/vEfeP8AkUAdHbqrwrIg6HoOlasKDzBgsuRzzXOWk5eNNhZcD5gPWuhiYpGrj5z3
+BNAF/agTaS2fWqUx2nJOFPGR1zQb2KRiELZBw2V6VBIwklGG3KOgI70ANLBQS21UXhjux9TX5P8A
+7cXiO0vfEXh3w6/mSs9z9okRTw2GAHP0Jr9RNcv/ALF4fubjjcqMB7nFfhd8e/FNv4r/AGx3Essl
+5b2t9FCkZ5AU7QcY/GgD9Fv2U4WHh/xZPaxmLyVZbJTIPkXbx296+m7/AOJvh34domjXd5bRXgiW
+SZTJlskZ5A9+awPgj4J0Twj8MrS30aORjqMQOZCGZQcEknHoa+R/jJ4P8S+KP2i/F2pR3E0NibwQ
+xiMZIVVAGP1oA+2bX46aHeaC0ttcmX5SQea8fv8AUJPFfjVdRuI3eLqpXpjtXiOheDda0vw3bx3F
+2zKflGVwSp9R619BeGNOMFgo+YYTbgd6AJ9M8cJ4PYwSLweVABB/nXuXw88bw+MtDklAKujlXUnu
+a8C8QeCP7cu43MbqB1bPNegfC/w/J4W8RzRR/NbTDOD60AeD/tEaTfeCvjVpfjTTA1ss8YhmdO5F
+VbP43a9eaPAIJnZlXDlVzX1P8e/CSeJvgnfCOISXMEZmiJ5ww/pXw78ItPi1fU47W6jVQsjRyAdd
+woA6+b4oeK2DYeVgx6KORWRceOvFl4JCDcljxjJr6Tg+H+lrhvs6cDnK9ad/wg2moxbyUAPt0oA9
+y+G3iODxL8MdF1a1ZZLa7tEuImBzlWXNekxkpMjDqrAivz//AGK/F1wfhXqHgPWZWGs+E9Rl02dT
+/cVyEI9sAV+gEeSgYtxQAeNbBdW+FWrRkneLRpUwcbioJx+lfKPwQ10WPxIvLFbiGO0m5WEtyAcn
++dfTuvambHwfdrI3yPC6IxGQuQea+DPBkk/h79oi0t7qS3kzuRWCcuoCuG+g3GgD9IlcNHu7VBI+
+AeaxdD1L7Tp5Z+jn5SfTtitWcjqDxQBk3cxAZielcndtJI4+fac4HvXQXAaW48teSetT6do6Taj9
+ouMCGL7qnuaAH6LobQwiecAu3PPpXVNaWz2QjmjSRAehqGW7VQVyqjGMVk3eqxxxgLJlu/vQBmeJ
+ruPT/D83kLsiij3bQeuBXxd4l8d2mqeNZo9sCXUWQBKeCB24r3vx9rc03gu/SOOV3MT7lA5AFfCW
+g28moa1qs9zAIriK93QkOcquOQSetAHsGk+Lnkmis53jks5hveSInauD9017NpTWE1sjRliNgbbt
+xg88c18369p9jpi6bcyLkTtiMJIQu4jjOPerXh/xRqekSzTXRnnRo95CNvLkHG0enWgD6aitxHdN
+Mny55welaMDliWOGUjGK8dt/iEspjR4HgZQBJuHKk9M12uk66l/cI0MgRGcocj7rYGDzQB10jhYy
+Q7AdCoFQmQmLaoGCOAPX1oM6l5AQpOOF/rWNc6tb2FrJcSsmVUgJ6nFAHjXxz8XL4c+H+qwRTJDc
+R2jujHkbsHrX4W6Pcz6h+0D9v1BozcSXSSJK0m1Qd4AJz25Nffn7S/xVs1tfEVib5BeMoEdu5wGA
+5P5Zr82fB13Pe/F6zinRpruW4iW0VCCOWAXPbGaAP6aPh9FDY/C6xYTGeOG2AEqsCDhQDgjsSK42
+40OKe7mufKV5biVpHyOpJqD4YyX0XwVsLC/uYLi/EYS4+zsWSIqACBjg/wCNeg21t8hkYEKoxz7U
+AeJeLLWOzkihVBkEZA7VveGZIWC26bCwAPJyayPEkjT+MPJTEmSQSQTiul8I6LLFqr3Mm1lzgcUA
+d/Dbj7OuEUH1x1q0irHdo4UKc54qZuE2jjFNwSBu7UAd3IsWq+E5IpFBV4yGB7ivzbFtJ8Pf2vNR
+0xgYbCe6MkBx1zX6K6BdKY/IbpjGDXy/+0x4JIgs/F1jC32qylzI6ngr3oA9osZvtWkW8y/xLkgf
+Snyjgj73qK86+F3iFdc8A2ZLbpljwxByOK9MnA8rjg45oA+Fy8/we/4KzQOSYvDfji32njCrdRk8
+H3Oa/UXTLkXOlRvjhlzwa/PX9t3wrej4RWfjrRoHfWPDGpRajblRyFU5cce2a+t/gr41tPG/wX8P
+eILOZJIL6xSYAHJBI5B9waAO88Ypu8C3eACwU4z06GvhdZ7OHxJC8kdvHcacSkcu7LsG4fd+lffH
+iGAXPg+8hHDGM8+nBr84fEyz23jzUYtPhDPGyuzY+8GfBzQB9l+GfFcK6NGzXILsqeVG2OV2jkV6
+/balFcWO1yiOQAvzdTX5/wDh3xVFe6jLYsXihkt1jgBPzxOh5Of7uK+ltE8R+dYCNJDJNakLPnsw
+H9aAPahDh5JASMDr6GrWo3/9nWixrt2hNzMe3HJrM06+GoaArk4fHIrjvHurvFo6x20ck88oUKB6
+5xigDattQm1GFpVJdNxwcEB/oaR3s4pB9puVY5+4vJX2+teQ6f4c+Md9qdoLOXw9b+GXB+1wSXTp
+dKexTClcevOa9CufA3jRrURabrOg6MeMO8T3EmcDOTgdeaAK+r31mbCSEaZcSrKfndmCkj6V59Jb
++FNKL3EumD7RKxMpYIM+nTis/wAU/Df41zazHNB4u8G6nZRkma2ms5YHPPADKT/KvNfGnhn4z3cN
+vp9l4X8LPCp3yzR60wIOOmCm40AbvifxFotxLHbLo7XtuMDEJH7r0OK81nvPD5tZbYSjT33kKr/u
+y34j0/rXlV38LvjRfeK7q/uvFsPhywcKBYaYokIx1ZmfBq2fhNr8unSwan8QNTvA0ZCbbdGYMeOO
+M0Ad+1tdRadcPDi8jbDYS4Jyv+93ru/BkE1rpdws3meaAssblicgHGK8e8G/BnX/AA14Unkl8e+I
+NQnL/uYbjAjiXPQjFe/ac/m6fFAAGukdYiVXaCCRk0Ad54p1ZNC8H3GpMIUl8jKZPGcV8E+Lvi3d
+Jpup6tJeCODHlRqkhI8w54x+Br6F/aN1y7svhbJa2ilpSmxUH8bHI/TGa/LzXry7bwtdWOozNBZW
+YeZWjGfMcDnr6ZoA+fvHPiLVfGXiy+v9TmWVoGkCK6YEkZruv2cfA8HjL4sq1/Oum2VraNcI6AF3
+bdhVGa8t1xoF1O0061DS7o/MXcPu5Hc19ffsq6ZE3imGeeGNLgwsjxbcAgdCPwoA/XrwVYpYfD7T
+LK3thBHFEFwrZz6k+pPWu+v/APRfDjSDG4qc5rnPCke7RLIKP3eOlWvG18tp4VfB7dB9KAPKrErd
+eJrlmJyrZznPfpXrekWogsQVHykZryTwjBJI/nMpyzZJPWvdbWHbp8SgfMR+lAFZlO4EHGac+DFx
+96pMAscimBeelAFywlMGqJzgN39K1vGehWfiX4b3unzIksc0JU7RXOKxznHI613mkSi60jY3U9c0
+AfBvweu5PDXj/VvCt3K8Ztrlkj3emTivrJk327OGBGBj3r5W+K2nS+C/2pbLXBGwstQfbIU4APAr
+6U0O/j1DwxbXCKwXyxgE8mgDpfid4Zg8SfDfVtNnQSR3Nq8ThhkEMMV8V/sS+KJdA1Xxf8IdVd4r
+3w3qMi20Uh+Y2zsSp96/RbUoBcWUkbKXyMH6V+X3jK1l+D//AAVU8GeM4kNtoviqJtM1F+ieaAfL
+J9ycUAfqpcos2jzgdWjOPyr84viPfQ6D8VtWhNu11cXtwLdsEI0S/MwYDoRkV+iOk3X2zw/HMGD5
+HGPpX5i/tXRzaL8evD9yqv8A2ddBmu+dpkCg4QHqCT6UASaVdWcmvPLDbra2N4gSBwdxDA4c+xr3
+LQdWKeMriDzpHiuYVdiOnHAb68V8Tx+JNUvtVsNTsrYQW1w/krHPJt278geWPUGvqDRIJbSW0S1k
+23SRmOWRyZBhTkqSeh68YoA+xPBmpi4t5rfuhZSexA71uPp6ahrMUMm8LG2c+o9K8k+Huos/iWFY
+GMttIm4+gPfivfLWPbfh8+tAHR2sMNvbBEUKo6cVBO8Y5LHHSmtOFBBOfrWDqV0PIfnbQBzHinxT
+p2kWDtdyTfdJ3BfSvnfXvi3oUa/ahcTrHK4QOy/catj4ntez6NPFDPHCJU+QnqOa+NvEOmz/ANlx
+6fJfzqEuPNmcKQHHYZoA+hh4v0HU5PObzpYnyBKHxkg9h+NdLBLpk1ruto4Wdjxg5Ixivm/wt4fh
+1CSFXkn8xQfLKyZGD65719AaJottp/lli0kkYG07uOev8qAOyhjQ2o/d8bTuB5oiiX+1o59mY/L5
+Zudp+lRK7iUgSgemBx+NWlDfaFLs+4rhNnAxQB8sftEazGusWcbySSQrEwKK2CWPRv6V+YfjySWG
+Yx+aQiR5njL/AHC3O0+ua+9v2ntRT+1biAxOzW6eYHj6luyfng1+YviJdV1DUHnurhpzfDz7nDgi
+NVzt/KgDkbaaDU/FEUlxujkW62xgNzIu3v7V99fsytZXPjVbR5Zlmh3Kh2fJJ7cfXFfnxClt/aUl
+ufOEKSrslXh2YnkfSv0a/ZC04z/Gv7OZAbGCAyMBgruYDCj6UAfrpoFr9m020jCgGJACo6HgZrjP
+iBOJoorZCWZ5MBRXo1viNlYHgj9MV5jqpS88bIOGQMePSgC54asDawwxSZJUDJr0xF2RAljkdvSu
+a0eDcWwOAcDPtXUffkfKHOKAKLYy2PWndQMdutSeWAOBn2NI20oduAe+KAISACQOAeprd0O4MN0s
+TO23HTNYgXMb5GafBKsF5G43HHWgDhf2hPCA134Q3V9bRhr2zAmhYL8wxycflXnPwV8TNqnguG2n
+fM0Y2sS3PBxjFfWt/bRav4JmhZVKvGVOR2Ir8+vDjS+Af2kNY8PzqyRSzGW2LHqCelAH6SEFgcV8
+EftreErq+/ZzvfEOnRsdU0G6h1O2KDDAxyKWwRz0zX33GteY/E3w9Br3w01XT54/NhngeNwehzxQ
+Bgfs/eOLXx3+zp4b161mE32uwRmcHkOFwwP418kftz6Y39k+GdYgVfOsNSDDPAIIzj6VS/Yl1u68
+H+OPiP8ABzUXKz6DqrT2Ubn/AJd5ckY9gRXoP7Z1otx8GrK52sdl6h+T64oA/NDwf4iiuvHOiWd/
+dCRv7UX552YeWGYnCc4HNforZGax1f8AtC1aE6XbttmgY7hIzcBwevU81+feg+EtQ8VfErw/aaHo
+V9rlzBdrNc2ttFvZQrY3SEcR/U1+5Or/AAg0O+8DxQ6bbJYzTQx70HbjJ6dT2oA8U+Ft/bx6rZwi
+Q+fkh0ycqSx9e3NfWNugDbuvH9a+WbH4ceKvC/xMfUGC3OjtKJC4OGjKjA+ua+mtMvI7iyjlJA7M
+O+aANOaMueCRWNeWEsqsvJBrpUZWRivrVaZwEOaAPLdY8F2F/EZL6JnEa7cZ9a+b/iH8PNEh065N
+v9raAYMgSZsDHP8A9avsye6jaFy2CFGCT2rwD4kavbwWrxR4lkZxny03GgD5d0fQpNOliubZJljL
+gMj/AMJPTp2xXrdm8n2eMNPbICcEEHJPtXCazr0UUgtonEUk0m3KgqQ3b6mtjRmuDJFJczuwiOJN
+2NzH2xQB6bbDYrb9wRVznGQa0lkDEfO3loQSwHQVjfaolsdyA4bAwWxj61C+oQxRsjMAmM/I2c+3
+1oA+Ff2ndatNK1y8mm8uNnm+Qq3zOW+X9OtfnLqek6tqF2iaetxcTXbm3SCIbyEH9T1r9M/jD8C9
+b+Lv7Ruj3iT21r4RjieTUYizedIwxsAA4r6p+FP7P3gvwZLBeR6RZCcbSH27juA9TQB+O+s/syeN
+/Bvwo0bxprVheQW9/M0sVqkDSuAo4LYGQD6V7r+xhL/Z/wAZLuzuo3gl80MPtEbRkg/w/N3r6w/b
+2+JfjD4d+FvhpB4U8RXHhu0u7qZL8wTKrOileNpHIr4F8EftvfFrTPGeq2Gm3ui+INOswsrw6vYx
+zNKc4wGxuUe+RQB+5Wr3X9naA15kLtTOPbFeXeGp11HX7i68wMHJC5PSvMvDf7Q/hb4n/seat42u
+3svBN3oUiw+I7S9uh5Npv4WZWPWNjx04OR2qT4X+KfC3iK2kXwr4q0bXZly5SyvVkYepCg5xQB9X
+aVGiwFxgnNbBAy3OOK5bSzNb6fHmQSnp93pWtDdgHZN8o9fU0ASgHBNRhFEJZvu9zVtWUythuoqF
+bm0kumtkuLSSZOWjDgt+QoAqsdm3ByT37UhVgDtKlvSrLR7pNzfIPSm8hj/Cv8NAHVaDdebZ+RJ0
+Awa+Nf2oNBfw/r+h+OLJTHIk3lzY/iBHQ19WaVcNBqAz909Ky/jJ4Uh8X/AnXNO2Bp2tW8okdHA4
+/WgD1xO9ZfiKXTbPwxdXWq39pplise6a4up0jjjHqzNivz4/bP8A26R+z9q9r8PPhxpVh4o+KN2m
+6f7XL/o2lK33GlA5LEdE7V+Efxk/aa+LnxU8Xxad8QPFmr+OS9xldONx5GnxyMeEEKYVgP8AbzQB
++sPxP+NPws+GX7dmifEbwb4x0Pxg01tJZa5pXh66S6nlXIKE7SVHPrxXG/Ev9rHxf8fvGfh/4R+A
+PB9no2oa3qMcFp9snF3dn5gXkZEysYQfNz6V+a2lL/whngWSbybG31K6XNy1rGIyncJkdcV9f/8A
+BMeyh8W/8FPPEHiS9jDtoHg+4uLIHpFJLJHDn67WNAH6K/E+20j9nv8AZLuvD/hy6FrfR2IfWdeE
+Q8+6cj523dgWzxX6UeFbpdQ+Ffhq/SQyLcaVbyBv7waNTn61+O3/AAUhu7iw/ZambzWhtLm8SC5I
+HUHmv1i+D0zXX7J3w1ncfM/hmyPPb9yv/wBagDq763WSJwy7lPauFlhk02+M0Ct5ROWA616XMmSR
+7Vz9xaB1IYAj36UAUbTVIZYMq6pxnHenXeoRrACDnIznOK4fWbC9sbuSa0L4IyQvSvNtZ8X3sFg6
+ywtvXjaxwGoA7TXPFFrb3HlzMQRGSFEn3iOcV4f441q3vLCK4sZYhIcb1Zs5JHSvO/EfiW6k1Z7s
+28ylFyux84Pt+Ga87k8S3F5fBLuXZZQtuQLF3PYmgDtY4yZDd3M1u7AkeQ65Ue496ty6sselxiAM
+v7z95KVwVXuBXDNr+nC1inDl3CEhc8nnjisr+2tR1TUUt7YM8W87jjqcDigD1r/hIrh4ooUAK9v7
+23sasRanPPNJHCTPgde34VwukaJqsl0skjh18vGxh69q9k0DQswmNrIKFQYB9fWgDpfDGlCGJp5s
+jIBI7Zr1iyEvmKCsSQBflb3NcvZW7R2cMTDyyi9u/tXV2bEWwfGMHpQB+YX/AAU/8H3Wt/DD4W6x
+BftbLb3N1azhT8hUhXBIr8gvh/oaWPjnVz9oaYG3QOSdoZs9a/eP9v8A0aPxD+wlqSrcfZrvTrtL
+yJgOW4KsoP0Nfhh8JUc3OoXs9uDc2swt5A5yGwaAPur4C29pqg8d+CNSMR0fxN4M1Gx1FZB5kJxA
+0sRbjgq6Aj61+Tnw68f+Lvhr8SdO8ReEtavdK1eykBBgnKI+1uUYdwcV+lFj4kg8EfCrxbqYlRLm
+fTbiC3SEYyXUgkn6HH4V+UEEYS4ZZGIDMSxznJzQB/SP4A/4KE/DPVfgR4d1TxXpus2viGa12X6W
+UO6MzqBu2+2c0/X/APgot8J4tah0vwz4Z8T+JNVliB+zIqIVOcbee/rX4h/BrS7nxrp+raDY6tY2
+eqWIF9apeviGTJ2spPbjmsvW/FOn+Cp9T0nwrfLqHiu53RaprafdhGSPKtvT0Ld6AP04/aQ/4KMa
+xYeGo/Bfw605dD8RTRg6tqYdZVsgRzEuOsg6H0Ir44+HP7WHxH8KfEi18SjUrjU9QglEs26VsXce
+fmDZPWviv7TKnmlndwc+ZHIc5J/iPqau6XevbzQE/vEWTK/U9aAP67vAvj218f8Awa8K+NdMGLHW
+bBLtR/cLDJU16AsjeWn8dfIv7G82m3n/AATX+GT6Xem/ijs5EnJ/5YyCRtyfhX1lZfPbbd/3aAJ0
+fbcBuhU7jXokSpfeG5YmAcMnOfpXnz4EeW78HFdl4buM2rwvywPP07UAfyzfHrXr3xH+3d4v8Ta2
+7SXc3im7juNy8hElaKNefRVFfLuiaeH/AGsxBPHGoiunlRW4XoSDX1l8fLS21r45+NtZ0dQ+n3mv
+3dxZSKeiNMzKSRXzBr1tdad4z0jxorK8BYQagy9YHPGT7GgD1T4jqf8AhDHuol3yfOrBex7mvuH/
+AIJBukn7QPxjuCokaDQrWFJG7K07HH0+Svh/XbpdY8IRfZwPKIw0q/MrDA55r6n/AOCX3i618Dft
+++L/AAdqUyQDxXonlWRyADNC29V57ld2KAP0u/by+HV18QP2CfG1jpVnLc6xYW6ahZFOSWiYMRjv
+xmv0D+DOsafr37JPw31fS2RrG48OWnlbTwAIlUj8wa8x120ttY8P3CXiK1u9u0c6OMkgggivIf2L
+vEl54RtfF/7PXieaUan4WvnuPD0sp+W70yZi8ZUnrsyVIHSgD7ycbhis6eMZJrUPSs646Hgn6UAY
+09usyuGAYY6E15H4v8KQX9nMPKBOMjBr2GRhlsgjisHVIhJZvgA/LQB8W614LaO9mjKOseNoIPIA
+7+/WuCuPAdw2IbeZpYi2XA44/wAa+pvENnt1AOMAbeSa4janm/u0VcNyaAPG4fhpDHcCT7wABWRm
+yy+oNdXBodjbTwrHbRKoH3k+UMR613JVSXLjCE4wPWgWyO4XylZO/qtAFG202FEaOVQUIGwBs4zX
+Z6RAIoEXDu2OAwxVCGFjGoUg7Tg/LW/apsjVmZcZ5KtmgDai+VSAqJKRkMTwKd9ujtUd5SUXaOpI
+rFu9S+yxSMyL5XZ8818b/tCftFWPgTw/cQWd2kmqyLtgj80ZGcjO3+VAHgH7dnxmcs/gC0d5EaNZ
+LlY29WIVcevtXw/4Z0X+yvC6WYEUdzK/n3DgfxZztP4cVBql3qnifxbc+LfFDyXl9M7GCOT7yqef
+MI/QCpbjXodP0J3uZPKIjyVbG4fWgDlPit4vjtvAE9ujqpkiaCMFcDOMn/Cvie3VmkWOEMXkPyv2
+X3NezfEPXE127t7O2aN7VGLu7OBgkV5za28EaNEkn7z+GQj7w/pQBt2WqXOheGr7SdFjSG4vlVdS
+1DPzumeY0/uj1rn4U3bdxL5JABU/KetWktSS6EED72d2SfpS7UKOY3Ykp8qHhlIODmgCoqSbvMy7
+v/GMDFRu5S4RQNzHk7R0rdVYvMCLtQKP3jA5zWRfaVd395tsSXK9Qo60Afsr/wAEtfjZAw8T/AzX
+Ls/a55W1bQGcj958oE0S/QLux7V+y0DtFcbQm2P1Nfy8/sk+FviP4d/b1+Eviyx0PUXsLbxFDFez
+oQUjt5PkkDeg2sc59K/qLuUQyyrGxaMv8hHT86ANLKlBtxir+iXBg1rBcAP2FcebiaJtpJbFP0q8
+mk8VWW5WRGJGAOaAP//Z
+
+--Apple-Mail=_83444AF4-343C-4F75-AF8F-14E1E7434FC1
+Content-Transfer-Encoding: base64
+Content-Disposition: inline;
+ filename=avatar2.jpg
+Content-Type: image/jpg;
+ x-unix-mode=0700;
+ name="avatar2.jpg"
+Content-Id: <4594E827-6E69-4329-8691-6BC35E3E73A0>
+
+/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAZAAA/+4ADkFkb2JlAGTAAAAAAf/b
+AIQAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQICAgICAgICAgIC
+AwMDAwMDAwMDAwEBAQEBAQECAQECAgIBAgIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMD
+AwMDAwMDAwMDAwMDAwMDAwMD/8AAEQgAwwDDAwERAAIRAQMRAf/EAJ8AAQABBAMBAQEAAAAAAAAA
+AAAKBAUJCwYHCAMBAgEBAAAAAAAAAAAAAAAAAAAAABAAAAUDAgMDBgcIDQkDDQAAAQIEBQYAAwcR
+CCESCTEVCkFRIhMUFvBhcYGhJRfRMiMkNEQ1RZGxweFCUpIzZGV1JhhyslRVNkZWZhliooXSQ3SE
+tJWltbYnN0c4EQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCZBQKBQKBQKBQKBQKBQKBQ
+KBQKBQKBQKBQKBQKBQKBQKBQKBQKBQWx5emeOta98f3VuZGVrS3lrk7O61M3NrejTkNdvqlq5Xcs
+pkqezbKJjHOYpSgGojQYANyviVenfgaW3oTEHKb7gHhArXoXlxxc0JvdZsUodbY27cikSppSvZb6
+gOUl1AVTYEAE3rNNOYMEm4TxW26yTqpg2bdMRYvx3GHATJonJZOgdJXPmVPy8ntt21edrcRurrg+
+kBbqG/btiOmhu2g8CSnxE3VNmDYytxM6oowtaAL65dFoZF2pY8GAnKJnQxGw1u+U33wgBShrQdPM
+vW+6qLGtfVybeFkxZdflHtN609Xmt8RN1wAAoEY0DsgWImVPoH80mt27Yjx5deNB3rFvEh9WCOub
+WsX5yi8sQN9m2nUMsgxLjT2N0t2xKInXLGqMtjv7ScpdBuW1JDDqIjxoM1u1HxW7M+SWMRbd7g5F
+D2NcYqF9yhixwXuqdpumJpad1sJcvWrrqAL2nryJFNy6Qgie3bOIBbEJRe27ebtc3dxoZXtyzXCc
+otdq4FlZYZXC4kfG2+Jeb1DtGXiw3SJru6dgX0tvXThrQenKBQKBQKBQKBQKBQKBQKBQKBQYSeph
+1ytsXT0FTA0xbmac93UN28mxxE3JIRujl09u57JencgAb5Ga3cuFD8XtW7yoxe0pO2gg0b9+rZvP
+6i6qylyO5+7GLmFVfVt2OscJHZsiSQ94BtlUPd4VKpS9LCWB5fWKrpihx5Sl10oMSIqR1HUR1146
+9uvl19HtoK781Q/2n+5QUH5z8PPQV3/tnw/71BXJvxT6fi8/Z2UHJ4tFJZkSTs8Lg0ed5XK5AttN
+zKwMSG+4ujktvmALSZGkTW7l67cMPmCg9o5B2cdQzY9djuRJlhzPWCbqv1LixS1K1SNi5DEL623c
+7ybAKWwchR15bhg7eIUEifpY+JVkTE+x/BHUDXGd4/fBIzR/PlhKPfTSpADWbBMhI7YFBajOYCBc
+Xk1ukH0jEENaCbFFpVG5vHmiWRF7bZHG35Cncmd6aFVpa3uCFVaLesKE6i0YxTFPbOHAdDFHgIAI
+CFBf6BQKBQKBQKBQKBQKBQKCMv11OtNY2gs7ptZ24OFh23JylmEsjfkVwiqzixmcieqLqWyJ+aVr
+rVz8DZH0rIDzCADpoGPboreHMkW9Qj5u96liTIzRAZUrtOcAx+rdlDJN8sqV4FWLZpLHS7656aIo
+BTltprRQsqlxhMYp7dq2UboTacK9NvY9t+xPewpjHbfjVqx8sSqEjo2OjEnka55tqiGIoO7vT8Dg
+7OF64U2nMe8IgHZppQQQfEJ+H6S7L7DtvK2nWHl329v8l5cjY6BECxVhlwergijcm5SjtgZRAlq8
+RslNdIUyC7ct2zmOU5TAESX8z+HnoKJL2j8v7g0H5w/JPj+P4a0BUq7ePw+nz0E9jwj/AE2Udxsm
+fUVyrFrV4664rx7t6tvCIpwt2kl4PfOeN1tQQQAwqLZG5IoL2CRTyj5aCdEub0DolvIXNCjcUSm1
+csKEa5NZVpb9m6USXbN5OoJctXbVwg6GKYBAQ4DQRr+rr4dDbZvVgshyTtfhUK2/bqmlOpdWVfGW
+61GoBkdXbIa8ZjmDA02SNTcvcTl5bTmmTkulum1vhcLxKEYDpCdRrOnTO3XOWwPeXdeI5i1NNluP
+npvl9+6f7IJYF8bCBzQq7pzFtRZUpOT1glEbHs90t4no+kAT+kaxI4pEy9AqTrUK2xaVI1iS9bUJ
+lSa+QtyyoT37RjW71m7bMBimKIgIDqFBU0CgUCgUCgUCgUCgUGKzq79Q9i6eO1aQTpCpRKswTYiu
+KYejd2+T2pZIr6fS++GSAB791BHbN4t+5oXQTCUuvGgjLeHG6cl/qPbu8m75N2CZVkPHWGpOkfzp
+5OQ65Bk/OUgv3XZsSuYKRPacGKHI7IrlScdSGunR2TlNaOctBsj7Fiyms2k6azaTp7FolmxYsWyW
+rNmzaKBLVq1atgUlu1bIUAKUAAAANAoPrQWeQR5hljI6xmUMzXIo6+oFLW9MT2gTObQ7Nqy0awrQ
+OLestXkqxIpsnEp7dwpimAdBCghudSfwl2NcuSCUZd2Fzdpw9InY6h6XYKmdpXexyudLXOptJYZI
+khVLjEbKm9qS2lUWVKW2JgALlq2GgBHxhPhiOpE/7as/Zwk0eYsbTnFze9OuOtvjqrTOmSM0I4+7
+LrMqWoAQqLzbErZGtEYGg1s4g/Dy+QxRMGFHG+2LPGYXlHHscYPyVM3pfbC8ibmaGP70pv2DGKUL
+5CIUN7WwJhD0/vfjoJaPTU8JZknIl6N5R6g0hT46xw4NKd3SYThjlfPlRdfUjbuWEcwcrjeLNEk/
+sxhNct2rqtYU/oHt2h1Ggnu4exFjvAmL4LhrE0ZQQ7HOOI22xSIxxtJyJm1na7BbFggnHW4pVXhA
+bl+9cE12/eOa4cROYREOyaBQa+XxgGxhDCMr4i34wtuOmTZbtWsW5XOmKIWPfOKNhbkQfLnIGltS
+7RhMZKc3DmFvKP3wjqF48Md1K5PMweNjebphfdL7SzLJHgd1kLlcV3lSBtU2SOsGa3NUIivsJm+9
+7Witc4mLas3ilDQtBMqoFAoFAoFAoFAoFB8VCiwksXlSq/ZTJk9s96+oUXCWbFizbKJ7l29duGLb
+t27ZQETGMIAABxoNX/1p98jjvl3mzt7aXE9/E2LVazHmLURD2DprjWzq7tpzkIDZExDqH9eUTibm
+H8Fbth5KDYR+HYwy14e6SO1m4lb7KN6ykySDLUpv27RCXXF1l0lde71ag5fSunLGkKG2UREdCkAA
+4aUGbugCADwENQ8w8aBQfO7cC1auXRLcOFsh7gktWzXbpgIUTCW3bIBj3DmANAKACIjwCgpCAS/e
+IIDp6kx7ptPKY2gAA/JQfidpa0hiHStremNaKYtsydGnsmtlOYTnKQbdsokKc5hEQDtEdaC4UCgU
+Cgj5+J3xEqyp0kM0OSBDaXK8US7HGTDgYgGvWG5FI7MZdFCc2giQydJJxunEP/NkNrQau7EGQ5Ji
+nJkVn8QdlrFJog+tr+yu7ddMnWIVzcqtqLF+xdIICUwDb0EOwQHQeA0G2w2gbi4dus254tzhCXhK
+8t8vjLed1OmuWznb5OjTWk0jaVZLf8wqQuhLgCQQAeUSm00EKD0rQKBQKBQKBQKBQYlut1ubXbWO
+nTnCZMTiLZLJkhR4tiiq2cbaiw6zk9xsNfTXwOT2W/aRet5Lo6gS4YvDUQoNW46PrQivpk61ztGW
+rroXVgCcTnIa+fUx1BilMW2c4mEePy0G622Iw+O4/wBku0OFRK0isxuM7aMHtLOVuuWryE6NNjaN
+lKoTX7BSWb9tUYRu+sKABcE/MAcaD1dQKBQBDUBAfLw830hxoLQf1SC6UeNmxasHualtmPZspLHK
+a9bEpREbYgI6lEA8/wAlBdSmKcpTFEDFMAGKIdggYAEB+cBoP4vXSWLZrlwwFKXzjpqI9gB8YjQW
+2y4GG4BLhP54/wCD5jgQxfSIF21y6CTnT+tKUQ5uY5y3ADiUAMF3oFB4Z6m8KbMh9O/exEni0N9v
+cdsmY1N4gEC4cDssId3xNdtkEBAbthU2kOT/ALRQoNL4nD2NYIeYdO3X0QEQ7aDYIeFmyndlW0TM
++MxMa8kxpldsdm9SJtS+zz6NlOdKUv8AB9Qoi57g+f11BKBoFAoFAoFAoFAoIpfi152WMbI8KR0i
+w1hVKc33r9pKUwgC20xxFyC6BygIc9uxdd7RhDjoIgPkoNcua4e5cG4cTHOY/MYREREwiP7NBvBe
+l0uXOXTd2IrHJEsbV9zaXgQipA4Wrllakup8ax1ONhTZugW5bu2wtAAgIBpQe76AIgACIjoAcREe
+AAAdoiNB8Qv2xONvX0y66lABHQOYQKOoAIemAagHaNB9dQ05tQ5dNddQ008+vZpQWNUrTXb3qzcp
+xMnulKU9/wBnT3Ut/kC8e5d04h+D4AHk184UHztPyG1rbOJilAR5TAUTfMIAGutBa3dxBbb5bHMU
+SCPIUeHMP8YQ837lB/aVVoOqvUBDyhpw8w0HJUd8bpClObU/IFwoiUSH9UcTerLeIIiJVFsoAFzy
+c3Zp2AFbQeVN9hil2R7wzHHlKG13PomNygflKGKpXqPIPA+geTy0GlBWffD8o/51BNM8JS9ONtZv
+Djw3rFtoVo8av1lvIP4a2tTukyRmv3g17RtLhL2UE0WgUCgUCgUCgUCgh/8AjCGlMp2p7Wng6stt
+W2ZolSROiHTnVWnOKoTqb5fLokFvIA+T8JQQcNnWIVGfd2G2/CiYnObKObcaQq9+CG+W2ifpc1IX
+G+eyACNy2nQXbhzBpxKUaDelxliQReOMEaa09lI2x9mbGVAmT2rdiwnRtiOyjT2bNm0Utu1bt2rI
+ABSgAAHZQVaxWFg4FtiQ14SGONsboENyWiX7xdAMUwaXj2BII9oBqIdlBVCYR7f2KC3UFOqt3QKJ
+AuH5f4uo6fHwAaC2+y+16ir7POH08KCt7rS+cP2A+7QPZE3mH6fuUH77P/Qfh/JoKyg/U14SXA5j
+Dym4DqIjp5vpoOJ5ax/H8uYryTiiUnvkjWUIHLsdSAyQRBWVmmzEvi7mKbTiCgErocCeY+lBpZd2
+m3SZbUdy2cds02Xd9SHBeUJjClr4Hpd7g0iItEv11/3gYKCVR4R9vtlf95DlYDntCz4vT+u/j21S
+6Sr0oecdCGPQTXaBQKBQKBQKBQKCLb4rKIR6QbNMHurtaLfcmrOV9sarImEDntP8JfAXmt6CHpWr
+jYnNrx0oInPRCxoz3erHsAI1K7qhzJuIgkgMRSNs9oGZg1dH0pScgBqJPRAR1EAoNxGN843Qt8AD
+yiAcR+5QR1Ooz4jzZhsNnMrwcwMUv3K5+h7TpJofj9zZWSC49lP8GLZJys73rgRyQX7YahbbrT3d
+IA6HKU4CABHpk3jHd6QKwVx3aztNjbWIAX2GSzLKTtr8Yuhfcoga/EAUFc2+Mf3frA0DaXtNeOPa
+15IyeI/94pgoMqnTj8UhjfdHlCC4J3UYSLt0nWTJaywqE5CiEyPK8SvElebxGyMM0q942Zhfcf3X
+125gtc4GDm5dRAOYaCWpQUiVL8PN+/QQ3OoH1jOo9uN3DZS2X9IzA2ZFkh27zyXQTN+VIdjxld3P
+vVnezNjQDPKZ3rAYBH9CmMIupROIecAAKDBpmRq8QgjUShFuN3vTrESOLNQOMp+0bffhSJtLQDqB
+XMAeAxfNRLHwAA/RDpp+1QYiWzN+6FVOu8nffbJ2SQoBB0993Leq8urZ3v8A1QeBzKZn0+IWvT4q
+DKTsK8R1vk2kZPi7TmfODvu/2/23Vojk0hE1d7kqyCMYKYmrphSXL7sfkJn7Ti1kdtQMJQAQ0HUA
+m+QPxCfSAnDWmWWN5kVjKhU294Wm/IePcowJ3brQhprdK9QaxbtmAB4gBjfL5KCAP4gPJ22rNfU5
+y7mnajlOMZpxrleB4hkT7MIZcF5izbPysbxE3NquDykL6wxWJrMbQA9Iwhx0AaDNN4S1M8e3b0FC
+VOnCKWLWJk15UY/40L5dPMBR2LVvTUycECS+Y4j96blAO0aCZ5QKBQKBQKBQKBQYM/EO4xJPem5P
+pARImvrcZS2GS21fvCBbyZKpd7UeUilEe25cUO6fmAOIlKPmoINnTK3UxjZJvuwFuum2OpTkyE4Z
+c5iL3F4OBe9yu+QoS9RaIGZuYpyiIP8A5wEKCQ7uS8X5uElTXKGDbltAjWGUrzGnlCyTbNUvenOU
+NBjFO1HdmdmjAM7H3+xHuCYCmOblEA0HQKDExs26DnUV3vxlFml3RxnB2Pp45XZmhylufeXwMhT8
+XYAuuuQmqJtZTzx8HW+U/enATAIiHlEAzhYu8LvDMZKyyM3UIycabg26vr1C9t+MAaGrQdRKP2mm
+KYAHTyCA0F5y34bN/kCZY7RLf02SjITO26RVFnzaviyVxN0DlE3MaXQUzyLAAiGnDUdRDyaiAYM8
+oYFyj0otyeL5Vvw6dm3XPuHXuSs7jB5diPvnHsAljtFHwrmBsdZExmDQVhyOQR1BolRTdnENNaDK
+Blfxg+7G/OnxZhHadt4iGNwM19yseXnibSzIAl9EXQXV3gL2xx0TDqIaAQvKABxHUdA63HxW/Unl
+qGQSpJi3Z5F4/ieLhkWVxVwieVQNkZnGaw6BGibPec34t23dD31B0AS+lp5Q5QoOhJh1A+rX1386
+W9rGHHuNbfIRJYy7yLJGOsKvr3j7EzTEBFgJKcsbh8i3CGkE+j4mAQ7qETcwiHDt5gza4A6B3Tn2
+0MXvbuDWOG7KQRdqNJJRlLca7PjRiaHtEVZjGdgHHZjCxmjhdeAu4mNoABrwoPW2y/ed0ud0ORXv
+CWzxZhsJXAGszj7jt2BwxQ1S2Ksxu6TyvEoOeoz2PFHiJR0EAEB7BARD31kba/t9yvGFsUyvh3Dm
+QIo8/Vy1km2N2R2Hhpr8dBCt6znSwwl0+ZPineNgfHLXNNs8kyc1QbKe1rI7w+OsAiUodWIHNnJE
+5e1iL8aNT0veoB/qF8HTsAAAI++e5/D8jZJnT1ivCLbtZxVkV2iLrjzCceenx4izXFoqyvbaY55i
+7CMhyCY0gHi7jxHWgl5eEmWorsa3jWb7mnB7vLsNqSs6e4AEO3Eb5oN1yJa11OFpUrJaEwcC84AP
+bQTIKBQKBQKBQKBQKCPZ4jPcPCsebPl2AHO69qJtmpU33GFmYjmtOQoYxIGle4qVRSiIHTequksc
+nZ68dfIFBBh2vwfI0/3Asm3LHolheeMzzvEeN8WSuTOs0x/KsRZta8nsznE3geYBf8fyMH7s8oDx
+DQaDPb0pYvuT6j3VaSX+pI+SjLci6ceL3ZtW4yy0I92s+WMTTU8DaIhLCRnWNv0jYJ4dzdXh2MU/
+fpifWg6agITplStUrVe1q13tqseK5d8PIOlBgx8QPA92k32Crmnaf7+LUiKeM7ln6LYl78+0KW4o
+Bk/VHuu9C/yCOd//AKXaKDzx4dSD7x4ZtVyGl3HtmS4phN5nTQ2bWoTlYH4ssi7Ya4c+RXpqZnYw
+zqOY7K/HAjQDi5kIBSunDXURDNNvK2kId4exbcxiFZF41JLE2xbMEETROds4OLXm6KAcuO8gM98x
+PUsV2APxRN3mBuflDXTloI6fhK4Dt9zHC90kTybtWw/KMxYancPkqLN8ziLLPpd3TkFmMIY7A85Z
+3oWD3AFhAPqsSB6Qh5tQkwdUeMQzGfTr3gzKL7b8Y5YdYdg+YSRBjBbDYKaLu7mRsOhNKXW3dh9w
+twIFZdjyU33hh5DCAlHQShHa8LDi9hadjOfshNK7+8OXNxgRt9XdndETx8ycGj/43QSBdxuCEm4P
+A+Y9uKxc5oUm4HF0wxMK9t7Gd2lgatP66ZuHf7L81BHe6ZHh+M17EN48WzfuXzHA3oMSRl7DFcVx
+uZ5M7OztKmUYAMseBcw/2eIwvLqAtPn8gaUEqBL3okV+1pVzWALWvu1chcmgHZp7p83yUGHjr5R9
+ieOlFveUqW9sSJkbZjaaMKAoCLU1O8Vm7KDQDOUA9IwmenTh5aCHV1KWmxHdg/QbhC1LzP6TZ5uO
+kysugfWTRkLcFbcojb119LU5TG4/xvMFBx7p17jcydOp4xbvMxzOMQzmBZOl0txHljDzbKl16bRd
+AjXWHYbmTIkoaSPbNdlFhLaVMbmhJdsGOTQ5DFEQoNlHivKLbleIMM2jxrfccla2x0QqSmE4GbHp
+suX+cdQDQwAfT5qDtSgUCgUCgUCgUETPxDcXfse7jNj+5ppXNa3uWe43bEKGSfWzS0SyJ5RhcoaO
+9v8AlygjZ573hbp5tv1w7lzdchbmXeDti3EQ6P5Sm7bEGSAyt5+z3JzG5RVryC0NJWMDSKBmJ3WG
+gCPcGgajprQSfMJRMuyfxRW7HEKoXEsP6guK5dluDLiCUbLs65B0ycJ7RiiJDWTZbLKWwpg4CXQa
+CT5QUaVVp5/xIfN+/wAaAr/G/wAr/HVfk4UFvkGdf8PuG8pziQssZVQnHkXyTlqUrnF4eOEUijG9
+Sd2NoZjHUQBlHTycezsoMLPhNNvnuVsGnO5h2bzJJDu/zJLZShAAKBWzH+OxNBIeQphEdBLctuY6
+/J5xoJQMij7FL2B7ikhRNrvHpK3Okek7IvKHdjq1OjQLW7tPEDehctjoYO3lEeIDxoIa3RwiA7AN
+5HUU6W+QFzgilLPlQu4/AhrjV6tpyHiY7G9ldOQvKbnOeDPZXICiJf0F26gBRCSeKoEqpC7Jfypl
+dGdyQh5wag00HXgOtBV5NnJslCyilhp2l1ZnQoA8md7YgRpddSnEvJbIb0x4jrrx7NOyg4y1pXT8
+7XfDs17aDAx4g7Ib/LdueEenzicBes8dQDO0Qx4yxZsIU7qGPYm+A4yt3MU4lAxDSQxSj26APYPZ
+QYZPFRY5YsK7gOnxgloAyOI4J6fzXG0S5uDTkaGWde6PfHZwKJmQP2aBlLpCRjaJ4f1futywxlR7
+t8zZOwRkhWdxKbvfE+KHV91iWJWsOTlKHu+Yrq66mExtRDQALxCWZ0q/alfT72rq1n5WtxhD/wD5
+Hp5aDIZQKBQKBQKBQKCL14ophVq9r+K5Ck/U07efYfi/uS9a0EZPrfZFxTlffxK8m4VXtL28TTBW
+3SS5hfGsNGj/ABAGxkzDKXaI+XmJa0B48mtBN73lbC5b1I9suwrfFgmWtUY3z7ZIdCMhY/kYWrdt
+jmjw3JmxzmmJ5RbIIFtxpLPWZRbACh9+U/EObloPXm1LdXjvdjDn52iqxpZMrwt1LG8+YQcTC15D
+xLlYSlGXNRmgSF79jwHEQaHYBHgADrx0APTir8U8vk83k+bWg/fzVc6+w/VKP8vfHL6paWj/AMX+
+UaCNv1I92D71DsksXRz6c8rLkCZZalLa2b3dw8F0v4v287fGF4tnmMQGSWjA1Pr8b1gFdTaemIGa
+LfMZzMABLBwfhuD7ccJYtwTjdEDPAMSwaKY4iiINeDNFGfuu2A83pCY5SgJhH+FrpQdsJvJ8P41B
+gz6wnTbypukJivdxsukjfj/f/tMdTyHELm6EZgi2Vo0S6VydcR5FtvJu47idQBvQM4hyl5R1HlNq
+UPCeFOuBt5azDjPf/GZ506dzzO5dwZAx9mqGzUMUXJMICYrziXIIGfikjwELqIOhjAHaBjF0MIZF
+Eu/rZGri77LEu9LaWtjzK1+8b4ubcwMjt3Q0/tUHhHcZ1uNpcIi/dO3CVf4tdx8zax+x3B+JWd6l
+nvdLHbQIi0O/uv31Qc66WHS63GuO4OTdVPqj3W163sTNqux/C+IG7u0IBtYx4RoBsZSILbWpOity
+K3HjgQtv9TEMfm5nQR5Qjz+LmB0V7+MXNuhraJm2kwFGhMJSgCh/c5llq6a3buCHMPs9sSmOUo6f
+hCCID6IgHpjrR9SC5vF6cnSowLAUDiimu8xtxvnrIMXazEud2M+PALBWuLFOXQOSRZfHlAB/ia8d
+QoJSu0zF/wBiO2nCGJ/y33LgTPG/bv7JZBoPR9AoFAoFAoFAoMOvXMw39rGwXIyRIhBarhbozzVA
+P9k/pfj8TBQa62eJVUTS+1yFja1qTJzV/cicOXfbT3O7RN7/AL3C0eaR6/pag2anh3Mypsx9LXEF
+62495OGN5ZkrH0qvCcTnM7BML2QiWzBza2/ZYvPENkA/iWwHy0HPN43R7xtuDzIi3XbfcqSvZjvX
+ZGwW8mfMTs4OrZLWs+pjNWWcdg+MdvIFsSgUoauZD6E1ETcKDyFHtiXXwg6ebMEX6i+05+b5MYDM
+02mOCZrbl0UtgICcrLFzEfrdoxihwEXMwAPHQQDSg4M2+H73R7glKr/qNdXTc1uBijwcRf8AF2F2
+pmwjFXkumoC7uTWF23eAohwL3SXXyjQZttm2w/arsDxiGLNrmH43jOK8omkrwQDuUolbm29jrMZW
+5mF7kdz0dNTjoAhwAvHUPWXtSpWq/onZ5h+AUFd+SUFD7Uk9q9l0/wDUfJ+z2UHBMo4bxLm2PjE8
+xYrgmWY7x+pMiw5klzUHER5iklBDgIjrx4caDwE49EjpMOrkDou2CbdRXAGpu7Yd3UA8R491tTtb
+IbT4gD5KD1pgfZ/tX2rpXNLt027YcweR8H2F5W4pxvH4k6ufLobldXdqt235+AptBATGMOvHXhQe
+kknsn5pr8+v33x6+Wg14HiKMUZW3j9YiFbb9vURXZAzI9xHHUNjsUI+WmollRaht2bLpA9PGhAj0
+dZmY6ozsJjcvIQBAQ5h1DovpMbQVmY+pt7v+/Jct4n6frX9nEWnBQegiTs7Y+EYv3vEQcQAAjcgf
+w710oJ/qVKlSJUKRJr+JfRQf1QKBQKBQKBQKDic8i7XNobKom7IfbWmTtbxG1yH+1vmANaCGht+w
+Oyv8c6pHRrzBFYo4T52juYNyWyZ2dmcXV0bc/wAUgx3N3LjsSctz3kf2TldR5TlHl10HXSg9U+Df
+3NN7rBd3W0tZfIkfrMoie6WItvrhMdQzZBisfx3kO1bsj/NhFFsRiwnEOBu+Sa6CHEJwPs4fF9H/
+AJNBQ+zAl0EBAddfPpQflB1JnCKTycYkyhEcUTdBjPJcjgkvjeOcid0keCwGfO7DcTxKXXmo2oHL
+H3w/rBLrzCIB2jwoMT3To2z9YzblPTx3eDvOwTuxwCtI5OIuLnH5wXNrQ6uQga2ETl4gQl6NW9Ox
+zMJigI8ogI60GUjcUz5vkGEpy1bcJ1BcfZsXNJhx3NskRI8uikTdzCTleHWKtgiZ8KQAN6Iej6VB
+h56c2xfqt7eNwE2yrvi6kJd0uMZLFXhrQ4SaQmYM5JU5PRQtSlqLKGhlDH5I6Ajyg0l4ajxDhQZ4
+Ff5KOn5X5PP2cPh5qDivtSpIPzfP+6HkoKH2lV/pv+dQcqa1X417Jp9zt+Sg12uaN+cIxR1yOpzv
+QTPI2JRt4wtm3Fe3VtsN9u9ZmO5M7dCttcJLaMUD2jJ2YJY4vbsbTmuWkYDqACUaDM54c/bSrw5s
+tXZMkKD+9mdZR7ye3f1T+qOFBIWoFAoFAoFAoFAoFBg86r3Txyfm5+xzuw2nvjnC90uF3RncmNdG
+/ql1d+6v0R3R3pr/AHjoIJER3X7sOkf1DU+VouwXMZZvhzurWziAPaNtSwuYQ7IjgeVSeASdgZrV
+n2mPTNoUtdwTW1FtQiUprSlOa2otWrpA2nPTb6lO3/qc7a2XPOD3MELml9mZMq4rdVqe/MsRTsUv
+r1cZkVqyFoVbcrAh77S627RErqjD1hAt3SKE9gMgntQ+Yfo+5QWSg/j2oPMH0/coHeof6CP0UFEq
+fvZPzHTt+L46Cu70/Ffa/L5+Oumnm81BQqn5KrS+bT4aUHFFXYHyfuhQf1/ROPtn7vw+agwfdb3r
+UYw6V2KbUWgp2LIW9DIzABsZYzu3QWIYPZCwRMiyjkpKmuFvJo+hV3Td3N5jW770rtchNLBL922E
+SDoi9MJJ1EJBLs95hlrveh8QyevvPOtklh2yu7Op1zyZx7xt3rpbR1p043Dl5h0OYeI0GwTjEXYY
+QwsUTiSHuWPMrV3axoW39UNLT+3QX6gUCgUCgUCgUCgUCgggeLB2cEQS3H27KNtyowuyW7FJOqTN
+g2U4g3ktODStv3S8SlOkV3LIa66GTaeegjZ7BOoTuk6duXEud9rs7CJShU1Gjstj7y323+CZBjJr
+9tRdjs4i6k9pO7toqbRbtm7bOnXIrwesSqLFz06DYRdJvxQGIN/uScdbX874cfcH7msgX77PFXKF
+HUTPC08fG9pXvCuykUKDFmOPVitG3XTWEi+26oyFtiB3TnEhBCUd7UHmD6fuUFf7N8Xw/lUD2f8A
+oPw/k0D2b4vh/KoOPt8qhrs5rmhqlMZeXVIURXMra8szk6l0Lz6GaymA9vgHboFB9FTCl0D5P3v2
+qDrOYzGJ48iclnk8kjHDoTDWJ1k8tlkmc0bLHo1HGNFecXh9e3dwvWELY1NiBPcvX7945Ldq2QTG
+EACgiM7/ALxbO3fF9p3g2wyBr9w88spVaFNmGdJHaD4WYHG4nEidxZ48vSoshZHM3rAEt1PdsR1F
+c0LcsLVFsdBCBBmvNWV9y2WpvmjNczfMk5ZyW9LX2RyZ7VjddXd9XiFhtto7VoLdhGgbFF5PbSIk
+xLaZKjsBZtWyWyAUA2cnQy2vG2sdP/E7WuQmRSWesFibP6A46mC48lAxwMPZqAjpQZlaBQKBQKBQ
+KBQKBQKBQYsespt8btxHT6zzHFDf3gvYYkeYM63s1c4sUXIfP56DVAL2w8eenRgumC4REoOZFdEB
+AL6K5qe0YupQEwkKPKbT+EAhQZeOgffBN1hNhtwbg2+bMd+wBg14+1QqWJQt8A1/Cje5R8mg8aDb
+v8PyT4/j+GtBi46n217qN7oo1jCP7Cd4LbtLTNTs7DmVwFtfWyUytvLbIDQdolUbtnfLWlspw5CC
+TUTAbmERMUQwoj4YveRkFT78Zu6w2eXrLIOQuSB6a2iauzU1iGoho7yqbA/aj2cRAKDlBfDK7qZw
+l93s89YjcZkHH3YvYu6Xp2M6tenMBTe802ewA4APkoL6/eEc2wtLWiV4T3abn8Y5MZTd4sU4cRhL
+w1d6ejoJmhsZWLQPOACPD46DN507tr+47Zlt1JiHcJuklO7ycDOHmQo8nzkXj1jRFHUS9yxExpFe
+f3seQebXiOgmHQQ8ocU6tS49vpj7/hUGKU5toWf7IGNygGqjGcis6cTaamC7oHl1Gg04lB696emH
+Q3Bb4du+LbgWRbXeftl13u3ya2EaBuLec1Ks5/4iRMlPdMGnH1Q8aDb6MLWljzCxx9o/EkbM1s7a
+h1/qny+Sgv8AQW/89+HmoLhQKBQKBQKBQKC30FwoOjd0DD7w7c84NOv5bi/JH0sj15vjoNU5k3aJ
+Pp1tryZvAgdkr/HdvOV4nizNLClIAusEj8/tOqXGE+dA1KYI5K5AwrWk94Q5bS6xbAeBxEA7j6EC
+4P8Aq6bBFVsto2ueWe2e2oAwlKFxme7N4pigICFwhDmEvEQ5g7PJQbe50Sqkirj9zjx/yaCva3T8
+04/D4fJQeC99rV1K3VPClHT2k+CWVQhK7BkSLZt74aXB3KHMVoc2SVkhs0EvqgLx1KGuocR0HQMS
+/wBiHidcnKwapDuZ2lYXj638tXRx3+tw1D9Ti17fOHCgzzbOcXZ3wlt9hWPtx+cTbjctMguwSnKg
+s/uuDwVze/WDpbA4gAMRTCUR0Awh8gUHc6qUflyTT5fpHtGgxndXQ43elz1AFZC857e1DMxFZQNy
+cpr8Mc05T66Dwtjd10/haacNaDTzakJbu3ro8tmwUDHADlJcumMPLZT2NSXBMoU3TAUgAQ/LxOYO
+QpjAGbjoqYpkkT6jG0lmnDK5w97m7uyTi2nemS9bc7sOl8WI5wJ8aS3RG4ZskLW5kV2DDxPbtgOn
+Gg2i1AoLH+c/Dz0F8oFAoFAoFAoFAoFB1vlpL7Xi/IyVV+SLYHMGzs/1syfPQRaPDWQ6HzfMvVo2
+25AijdNcZTeNw5slcKkIFFolTQWZ5RjfdVzm1AbZ2F3AnDjqYNNO0AxIbn+n9IugH1WtsW5NZFJr
+kXZW3bho3PMXy5uS2zya3HbLtzu+G38pQ5XDL8KZLt0ycDjyyZNbtqbYAJ74gGyjwxmfGO43FOP8
+u4ombZPMbZIjDTIobNmIxitjw2udpPoa2Y1rmZnzQ4lM2jxKcvKIagIFDsNKl7pVf+m9v7vH56Dk
+FAoOByhzVpPxRIuHT975NKCxtbCrVqtfzT976KDGn11Z3FcX9I7fCofndG0kkuGHTH7SdUptIlb3
+Mp8rRxlrj6BKe8Fxe8uV9yC6CK2BjWEFu9d0ApDCAQEuhP0dXrqL5oTZJyzHVSbZ1geStTlld4vn
+IlsZWlgJiObVhGN63LalcpfbdwUkhUCNv3bRhzfg1CgxRDMVkVqSKvFErStCBqQtEWc8DRhiQtjM
+DQ0M7Q0bXcLgDOz/APLYUEy+gUFv9l/Gu3h9H7etBcKBQKBQKBQKBQKBQcGyh/sHOPa/+F3j5P0J
++xQRlvC/6O+9LqaStJqtSf3PbES7savrbKOTnPUB4Dw7loJYm6va9hLehhqbbdNwULb5njCftpkT
+83gTV3bHQSn7plUTdTEPdYJDHTH5gdS6GAwAHZqUQhPMkh3o+GO3RLIVMGnIm5vppZslZHNvfm+7
+daBZ3RfbuJrcpx/a0MwwTcKyWiAWUNBvQnRQAQ0ECmKEx3b3uuwzu6xgxZp2+ZTa8sQB5KXleo2G
+jlE3YSmHubIEU5SvsDkhfVjqDpoYoBqIAAgIh3MlfnT/AE7234fL8dB+9+qvZfy7h5/39aCg70+V
+YHw+fQKDxPv86tG07pr48XSDPE2aHDJXdxl8MwDDXdmc825CcfVczTyRAxDhB4+c5ih3s6CUoAI8
+dS8pgiewXDfUP8TZmtiz1uDcpZtm6c+LnYjlB2WPkdjtN0AtnLcaMUA6Ax28v5FPw74yA5k9WyAY
+e6gAAABCZriLDWJ9vOI4NgfCmO2qA4rxi1BGoVGGsnIDUbiI8roJji/SGQDq6urqJjCAeUR40ETb
+cSdFjbxMKF4fkhWhnmZsFSFkFwMBwdmtywvDYpcddQEQ5iXISICHkEBCgl7JPyX9j/NoP6oFAoFA
+oFAoFAoFAoLC/PzDE2BdIZC+NjK0srWLkuXOf6KaGnXWgih9XLrwRdpYXzA2zmVNcndlrW8Nk3yo
+2fW0TaGnT/dH/iCR8aDIn4VrbE54n2MS7cJNWRwZ5buzyh76My50OUHN1xRFCg3Y8drg8RAX57fH
+QwgIBqA/PQSQ350VpHT2v804fPrxoOKT2LYlz1A3rGWWIvGZ/CZM1d2vsVkbODs0PID/AABAQEPn
+4DQRoc0+HPyFhLIS3O/Sn3TzfbJOu8gcUUJty98aGY/okAzULmYX1inZhuCbQkpbbgAUOJhoPLjp
+uq8UVtPfl0eybtJgm6pp/wCKW3D3vZ3v5vrfDc0839V0HE3XrHeIHWJQSR/o8NqFWHYucdrO4qVB
+2eXlFk7dPLQcWM+eLE3kMKtqaWBq2rwiSD3e9OBmfF230QbHcpiCYoyiZZNy8QRIYQ+qmvXQR40H
+vPYd4XXDcKfkOd+oZlVw3l5sXCDk5RxxdHp8xaDoJbnrAlMqlRiTrLpyjyCQ7qDOGuoDbEONBKwY
+o+xRppQsEcZ29lZmRtam5nZG5rI2NLO0tfFqbGtqRDpbJaEB0AvAPNpwoPkqa0ventfsP0cOzhxo
+I9XXg6X8o3SwKK7mtuCF0/xS7cNO42KNh9b5cxP3370O+Omnj/tHH38O9Gmg6r6afVyxhm3HSHGW
+4+VNeMs8wv8Au2+e+31S0y52aP8A6fkf+t2igzgNbqldkqF2aVwrUi38hXNvyfTQXCgUCgUCgUCg
+UHW+UMtY5w5F104ybOIvC48i1/HpI8A0j5P3KCMtvc8SxA8eql0T2nwgZo7fmE4m31TE+9v7ID6/
+1oIqW6rqg7vt3ar/AO7OYZQtj3/A8b/unE//AHQ10HafRdw5gHdH1MdtOEt0DN74YsyCSZlVQkXV
+9bWaWSyKsBpNjxpeiEuWz3I0Pc3EoCAmARDUKDbVsLC0xlpRR6PIm5naGduaW5mZm5pBpamdqaQH
+upra2rUOQpNNOH3vAAANAAAuipKlV8OPk+X5x40FjVMLX5PJ9z7oUFclSpUg/ivy60Fd7T8fw/k0
+D8W+GlB+eyJvMP0/coPrQfL2RN5h+n7lB9aC2pfaUn9NS/F8n7VBhV6kPRe26b2SPOWIosvbc9yO
+neAZhhTIV2bZgctsxjfaRj0nLYnoiJBDXUr1zcAHiFBFVxb1JNxfSM3ZZg2eZhfGzM8dxPJix6Uo
+G92uW2sAEAOzy3HhJYBTkMAD+hxABDy0Er7aD1DttO8aLd7YyyM1+8I/l0GcnfumWcP6oCg94UCg
+UCgUCgsEl9r7qXd3d5e2/wDLHc3evze9X1DQQfeun72e8yHvD/qA+26//tb7Ffs67P8AdD3Z+oaC
+I7KO9u8lv+3f336z9y+zh837v0UHFU/evtHD3k1+L3N1+fm9HWg9pdPb3g/xx7SO7vte9u/xN459
+g+zn7M/tD9Z37w91vej+73vlp2d5fgvPxoNz83e1d12vaO9Pbe7Q5u9+5u9P5sf073X9Qa/5Hk1o
+L7QU9AoKigp6BQKC2pfa9f1n83cvm8nxUD8b9r/Wfb/UnN2UFyoFBqTOvl3p/wBW/ePr78a+/bRp
+3f7na69yk/S3L6PNprprw018ulB0lsj94vtjg/8A/Umnegf/AIk+y37Qu3/dHy0Gyl2Xe932ZJ/e
+r/Fxp3Y0d2/4tvsR95f/AAH7Gvwmn9rUHsOgUCg//9k=
+
+--Apple-Mail=_83444AF4-343C-4F75-AF8F-14E1E7434FC1--
+
+--Apple-Mail=_33A037C7-4BB3-4772-AE52-FCF2D7535F74--
diff --git a/actionmailbox/test/generators/mailbox_generator_test.rb b/actionmailbox/test/generators/mailbox_generator_test.rb
new file mode 100644
index 0000000000..9ffa73321b
--- /dev/null
+++ b/actionmailbox/test/generators/mailbox_generator_test.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "rails/generators/mailbox/mailbox_generator"
+
+class MailboxGeneratorTest < Rails::Generators::TestCase
+ destination File.expand_path("../../tmp", __dir__)
+ setup :prepare_destination
+ tests Rails::Generators::MailboxGenerator
+
+ arguments ["inbox"]
+
+ def test_mailbox_skeleton_is_created
+ run_generator
+
+ assert_file "app/mailboxes/inbox_mailbox.rb" do |mailbox|
+ assert_match(/class InboxMailbox < ApplicationMailbox/, mailbox)
+ assert_match(/def process/, mailbox)
+ assert_no_match(%r{# routing /something/i => :somewhere}, mailbox)
+ end
+
+ assert_file "app/mailboxes/application_mailbox.rb" do |mailbox|
+ assert_match(/class ApplicationMailbox < ActionMailbox::Base/, mailbox)
+ assert_match(%r{# routing /something/i => :somewhere}, mailbox)
+ assert_no_match(/def process/, mailbox)
+ end
+ end
+
+ def test_mailbox_skeleton_is_created_with_namespace
+ run_generator %w(inceptions/inbox -t=test_unit)
+
+ assert_file "app/mailboxes/inceptions/inbox_mailbox.rb" do |mailbox|
+ assert_match(/class Inceptions::InboxMailbox < ApplicationMailbox/, mailbox)
+ assert_match(/def process/, mailbox)
+ assert_no_match(%r{# routing /something/i => :somewhere}, mailbox)
+ end
+
+ assert_file "test/mailboxes/inceptions/inbox_mailbox_test.rb" do |mailbox|
+ assert_match(/class Inceptions::InboxMailboxTest < ActionMailbox::TestCase/, mailbox)
+ assert_match(/# test "receive mail" do/, mailbox)
+ assert_match(/# to: '"someone" <someone@example.com>',/, mailbox)
+ end
+
+ assert_file "app/mailboxes/application_mailbox.rb" do |mailbox|
+ assert_match(/class ApplicationMailbox < ActionMailbox::Base/, mailbox)
+ assert_match(%r{# routing /something/i => :somewhere}, mailbox)
+ assert_no_match(/def process/, mailbox)
+ end
+ end
+
+ def test_check_class_collision
+ Object.send :const_set, :InboxMailbox, Class.new
+ content = capture(:stderr) { run_generator }
+ assert_match(/The name 'InboxMailbox' is either already used in your application or reserved/, content)
+ ensure
+ Object.send :remove_const, :InboxMailbox
+ end
+
+ def test_invokes_default_test_framework
+ run_generator %w(inbox -t=test_unit)
+
+ assert_file "test/mailboxes/inbox_mailbox_test.rb" do |test|
+ assert_match(/class InboxMailboxTest < ActionMailbox::TestCase/, test)
+ assert_match(/# test "receive mail" do/, test)
+ assert_match(/# to: '"someone" <someone@example.com>',/, test)
+ end
+ end
+
+ def test_mailbox_on_revoke
+ run_generator
+ run_generator ["inbox"], behavior: :revoke
+
+ assert_no_file "app/mailboxes/inbox_mailbox.rb"
+ end
+
+ def test_mailbox_suffix_is_not_duplicated
+ run_generator %w(inbox_mailbox -t=test_unit)
+
+ assert_no_file "app/mailboxes/inbox_mailbox_mailbox.rb"
+ assert_file "app/mailboxes/inbox_mailbox.rb"
+
+ assert_no_file "test/mailboxes/inbox_mailbox_mailbox_test.rb"
+ assert_file "test/mailboxes/inbox_mailbox_test.rb"
+ end
+end
diff --git a/actionmailbox/test/jobs/incineration_job_test.rb b/actionmailbox/test/jobs/incineration_job_test.rb
new file mode 100644
index 0000000000..a3907898ab
--- /dev/null
+++ b/actionmailbox/test/jobs/incineration_job_test.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionMailbox::IncinerationJobTest < ActiveJob::TestCase
+ setup { @inbound_email = receive_inbound_email_from_fixture("welcome.eml") }
+
+ test "ignoring a missing inbound email" do
+ @inbound_email.destroy!
+
+ perform_enqueued_jobs do
+ assert_nothing_raised do
+ ActionMailbox::IncinerationJob.perform_later @inbound_email
+ end
+ end
+ end
+end
diff --git a/actionmailbox/test/test_helper.rb b/actionmailbox/test/test_helper.rb
new file mode 100644
index 0000000000..bf93df1029
--- /dev/null
+++ b/actionmailbox/test/test_helper.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+ENV["RAILS_ENV"] = "test"
+ENV["RAILS_INBOUND_EMAIL_PASSWORD"] = "tbsy84uSV1Kt3ZJZELY2TmShPRs91E3yL4tzf96297vBCkDWgL"
+
+require_relative "../test/dummy/config/environment"
+ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)]
+require "rails/test_help"
+
+require "byebug"
+require "webmock/minitest"
+
+Minitest.backtrace_filter = Minitest::BacktraceFilter.new
+
+require "rails/test_unit/reporter"
+Rails::TestUnitReporter.executable = "bin/test"
+
+if ActiveSupport::TestCase.respond_to?(:fixture_path=)
+ ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
+ ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
+ ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
+ ActiveSupport::TestCase.fixtures :all
+end
+
+require "action_mailbox/test_helper"
+
+class ActiveSupport::TestCase
+ include ActionMailbox::TestHelper, ActiveJob::TestHelper
+end
+
+class ActionDispatch::IntegrationTest
+ private
+ def credentials
+ ActionController::HttpAuthentication::Basic.encode_credentials "actionmailbox", ENV["RAILS_INBOUND_EMAIL_PASSWORD"]
+ end
+
+ def switch_password_to(new_password)
+ previous_password, ENV["RAILS_INBOUND_EMAIL_PASSWORD"] = ENV["RAILS_INBOUND_EMAIL_PASSWORD"], new_password
+ yield
+ ensure
+ ENV["RAILS_INBOUND_EMAIL_PASSWORD"] = previous_password
+ end
+end
+
+if ARGV.include?("-v")
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
+ ActiveJob::Base.logger = Logger.new(STDOUT)
+end
+
+class BounceMailer < ActionMailer::Base
+ def bounce(to:)
+ mail from: "receiver@example.com", to: to, subject: "Your email was not delivered" do |format|
+ format.html { render plain: "Sorry!" }
+ end
+ end
+end
diff --git a/actionmailbox/test/unit/inbound_email/incineration_test.rb b/actionmailbox/test/unit/inbound_email/incineration_test.rb
new file mode 100644
index 0000000000..21c01a9cea
--- /dev/null
+++ b/actionmailbox/test/unit/inbound_email/incineration_test.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require_relative "../../test_helper"
+
+class ActionMailbox::InboundEmail::IncinerationTest < ActiveSupport::TestCase
+ test "incinerating 30 days after delivery" do
+ freeze_time
+
+ assert_enqueued_with job: ActionMailbox::IncinerationJob, at: 30.days.from_now do
+ create_inbound_email_from_fixture("welcome.eml").delivered!
+ end
+
+ travel 30.days
+
+ assert_difference -> { ActionMailbox::InboundEmail.count }, -1 do
+ perform_enqueued_jobs only: ActionMailbox::IncinerationJob
+ end
+ end
+
+ test "incinerating 30 days after bounce" do
+ freeze_time
+
+ assert_enqueued_with job: ActionMailbox::IncinerationJob, at: 30.days.from_now do
+ create_inbound_email_from_fixture("welcome.eml").bounced!
+ end
+
+ travel 30.days
+
+ assert_difference -> { ActionMailbox::InboundEmail.count }, -1 do
+ perform_enqueued_jobs only: ActionMailbox::IncinerationJob
+ end
+ end
+
+ test "incinerating 30 days after failure" do
+ freeze_time
+
+ assert_enqueued_with job: ActionMailbox::IncinerationJob, at: 30.days.from_now do
+ create_inbound_email_from_fixture("welcome.eml").failed!
+ end
+
+ travel 30.days
+
+ assert_difference -> { ActionMailbox::InboundEmail.count }, -1 do
+ perform_enqueued_jobs only: ActionMailbox::IncinerationJob
+ end
+ end
+end
diff --git a/actionmailbox/test/unit/inbound_email/message_id_test.rb b/actionmailbox/test/unit/inbound_email/message_id_test.rb
new file mode 100644
index 0000000000..af467a8d45
--- /dev/null
+++ b/actionmailbox/test/unit/inbound_email/message_id_test.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require_relative "../../test_helper"
+
+class ActionMailbox::InboundEmail::MessageIdTest < ActiveSupport::TestCase
+ test "message id is extracted from raw email" do
+ inbound_email = create_inbound_email_from_fixture("welcome.eml")
+ assert_equal "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com", inbound_email.message_id
+ end
+
+ test "message id is generated if its missing" do
+ inbound_email = create_inbound_email_from_source "Date: Fri, 28 Sep 2018 11:08:55 -0700\r\nTo: a@example.com\r\nMime-Version: 1.0\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: 7bit\r\n\r\nHello!"
+ assert_not_nil inbound_email.message_id
+ end
+end
diff --git a/actionmailbox/test/unit/inbound_email_test.rb b/actionmailbox/test/unit/inbound_email_test.rb
new file mode 100644
index 0000000000..76f047eb61
--- /dev/null
+++ b/actionmailbox/test/unit/inbound_email_test.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module ActionMailbox
+ class InboundEmailTest < ActiveSupport::TestCase
+ test "mail provides the parsed source" do
+ assert_equal "Discussion: Let's debate these attachments", create_inbound_email_from_fixture("welcome.eml").mail.subject
+ end
+
+ test "source returns the contents of the raw email" do
+ assert_equal file_fixture("welcome.eml").read, create_inbound_email_from_fixture("welcome.eml").source
+ end
+
+ test "email with message id is processed only once when received multiple times" do
+ mail = Mail.from_source(file_fixture("welcome.eml").read)
+ assert mail.message_id
+
+ inbound_email_1 = create_inbound_email_from_source(mail.to_s)
+ assert inbound_email_1
+
+ inbound_email_2 = create_inbound_email_from_source(mail.to_s)
+ assert_nil inbound_email_2
+ end
+
+ test "email with missing message id is processed only once when received multiple times" do
+ mail = Mail.from_source("Date: Fri, 28 Sep 2018 11:08:55 -0700\r\nTo: a@example.com\r\nMime-Version: 1.0\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: 7bit\r\n\r\nHello!")
+ assert_nil mail.message_id
+
+ inbound_email_1 = create_inbound_email_from_source(mail.to_s)
+ assert inbound_email_1
+
+ inbound_email_2 = create_inbound_email_from_source(mail.to_s)
+ assert_nil inbound_email_2
+ end
+ end
+end
diff --git a/actionmailbox/test/unit/mail_ext/address_equality_test.rb b/actionmailbox/test/unit/mail_ext/address_equality_test.rb
new file mode 100644
index 0000000000..e4426aeae9
--- /dev/null
+++ b/actionmailbox/test/unit/mail_ext/address_equality_test.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require_relative "../../test_helper"
+
+module MailExt
+ class AddressEqualityTest < ActiveSupport::TestCase
+ test "two addresses with the same address are equal" do
+ assert_equal Mail::Address.new("david@basecamp.com"), Mail::Address.new("david@basecamp.com")
+ end
+ end
+end
diff --git a/actionmailbox/test/unit/mail_ext/address_wrapping_test.rb b/actionmailbox/test/unit/mail_ext/address_wrapping_test.rb
new file mode 100644
index 0000000000..c4eb1328ef
--- /dev/null
+++ b/actionmailbox/test/unit/mail_ext/address_wrapping_test.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require_relative "../../test_helper"
+
+module MailExt
+ class AddressWrappingTest < ActiveSupport::TestCase
+ test "wrap" do
+ needing_wrapping = Mail::Address.wrap("david@basecamp.com")
+ wrapping_not_needed = Mail::Address.wrap(Mail::Address.new("david@basecamp.com"))
+ assert_equal needing_wrapping.address, wrapping_not_needed.address
+ end
+ end
+end
diff --git a/actionmailbox/test/unit/mail_ext/recipients_test.rb b/actionmailbox/test/unit/mail_ext/recipients_test.rb
new file mode 100644
index 0000000000..fdcad440e2
--- /dev/null
+++ b/actionmailbox/test/unit/mail_ext/recipients_test.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require_relative "../../test_helper"
+
+module MailExt
+ class RecipientsTest < ActiveSupport::TestCase
+ setup do
+ @mail = Mail.new \
+ to: "david@basecamp.com",
+ cc: "jason@basecamp.com",
+ bcc: "andrea@basecamp.com",
+ x_original_to: "ryan@basecamp.com"
+ end
+
+ test "recipients include everyone from to, cc, bcc, and x-original-to" do
+ assert_equal %w[ david@basecamp.com jason@basecamp.com andrea@basecamp.com ryan@basecamp.com ], @mail.recipients
+ end
+
+ test "recipients addresses use address objects" do
+ assert_equal "basecamp.com", @mail.recipients_addresses.first.domain
+ end
+
+ test "to addresses use address objects" do
+ assert_equal "basecamp.com", @mail.to_addresses.first.domain
+ end
+
+ test "cc addresses use address objects" do
+ assert_equal "basecamp.com", @mail.cc_addresses.first.domain
+ end
+
+ test "bcc addresses use address objects" do
+ assert_equal "basecamp.com", @mail.bcc_addresses.first.domain
+ end
+ end
+end
diff --git a/actionmailbox/test/unit/mailbox/bouncing_test.rb b/actionmailbox/test/unit/mailbox/bouncing_test.rb
new file mode 100644
index 0000000000..d4bd6ea6db
--- /dev/null
+++ b/actionmailbox/test/unit/mailbox/bouncing_test.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require_relative "../../test_helper"
+
+class BouncingWithReplyMailbox < ActionMailbox::Base
+ def process
+ bounce_with BounceMailer.bounce(to: mail.from)
+ end
+end
+
+class ActionMailbox::Base::BouncingTest < ActiveSupport::TestCase
+ include ActionMailer::TestHelper
+
+ setup do
+ @inbound_email = create_inbound_email_from_mail \
+ from: "sender@example.com", to: "replies@example.com", subject: "Bounce me"
+ end
+
+ test "bouncing with a reply" do
+ perform_enqueued_jobs only: ActionMailer::MailDeliveryJob do
+ BouncingWithReplyMailbox.receive @inbound_email
+ end
+
+ assert @inbound_email.bounced?
+ assert_emails 1
+
+ mail = ActionMailer::Base.deliveries.last
+ assert_equal %w[ sender@example.com ], mail.to
+ assert_equal "Your email was not delivered", mail.subject
+ end
+end
diff --git a/actionmailbox/test/unit/mailbox/callbacks_test.rb b/actionmailbox/test/unit/mailbox/callbacks_test.rb
new file mode 100644
index 0000000000..8d98a3f3ac
--- /dev/null
+++ b/actionmailbox/test/unit/mailbox/callbacks_test.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require_relative "../../test_helper"
+
+class CallbackMailbox < ActionMailbox::Base
+ before_processing { $before_processing = "Ran that!" }
+ after_processing { $after_processing = "Ran that too!" }
+ around_processing ->(r, block) { block.call; $around_processing = "Ran that as well!" }
+
+ def process
+ $processed = mail.subject
+ end
+end
+
+class BouncingCallbackMailbox < ActionMailbox::Base
+ before_processing { $before_processing = [ "Pre-bounce" ] }
+
+ before_processing do
+ bounce_with BounceMailer.bounce(to: mail.from)
+ $before_processing << "Bounce"
+ end
+
+ before_processing { $before_processing << "Post-bounce" }
+
+ after_processing { $after_processing = true }
+
+ def process
+ $processed = true
+ end
+end
+
+class DiscardingCallbackMailbox < ActionMailbox::Base
+ before_processing { $before_processing = [ "Pre-discard" ] }
+
+ before_processing do
+ delivered!
+ $before_processing << "Discard"
+ end
+
+ before_processing { $before_processing << "Post-discard" }
+
+ after_processing { $after_processing = true }
+
+ def process
+ $processed = true
+ end
+end
+
+class ActionMailbox::Base::CallbacksTest < ActiveSupport::TestCase
+ setup do
+ $before_processing = $after_processing = $around_processing = $processed = false
+ @inbound_email = create_inbound_email_from_fixture("welcome.eml")
+ end
+
+ test "all callback types" do
+ CallbackMailbox.receive @inbound_email
+ assert_equal "Ran that!", $before_processing
+ assert_equal "Ran that too!", $after_processing
+ assert_equal "Ran that as well!", $around_processing
+ end
+
+ test "bouncing in a callback terminates processing" do
+ BouncingCallbackMailbox.receive @inbound_email
+ assert @inbound_email.bounced?
+ assert_equal [ "Pre-bounce", "Bounce" ], $before_processing
+ assert_not $processed
+ assert_not $after_processing
+ end
+
+ test "marking the inbound email as delivered in a callback terminates processing" do
+ DiscardingCallbackMailbox.receive @inbound_email
+ assert @inbound_email.delivered?
+ assert_equal [ "Pre-discard", "Discard" ], $before_processing
+ assert_not $processed
+ assert_not $after_processing
+ end
+end
diff --git a/actionmailbox/test/unit/mailbox/routing_test.rb b/actionmailbox/test/unit/mailbox/routing_test.rb
new file mode 100644
index 0000000000..d4ba702ac5
--- /dev/null
+++ b/actionmailbox/test/unit/mailbox/routing_test.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require_relative "../../test_helper"
+
+class ApplicationMailbox < ActionMailbox::Base
+ routing "replies@example.com" => :replies
+end
+
+class RepliesMailbox < ActionMailbox::Base
+ def process
+ $processed = mail.subject
+ end
+end
+
+class ActionMailbox::Base::RoutingTest < ActiveSupport::TestCase
+ setup do
+ $processed = false
+ end
+
+ test "string routing" do
+ ApplicationMailbox.route create_inbound_email_from_fixture("welcome.eml")
+ assert_equal "Discussion: Let's debate these attachments", $processed
+ end
+
+ test "delayed routing" do
+ perform_enqueued_jobs only: ActionMailbox::RoutingJob do
+ create_inbound_email_from_fixture "welcome.eml", status: :pending
+ assert_equal "Discussion: Let's debate these attachments", $processed
+ end
+ end
+end
diff --git a/actionmailbox/test/unit/mailbox/state_test.rb b/actionmailbox/test/unit/mailbox/state_test.rb
new file mode 100644
index 0000000000..b3a58ad667
--- /dev/null
+++ b/actionmailbox/test/unit/mailbox/state_test.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require_relative "../../test_helper"
+
+class SuccessfulMailbox < ActionMailbox::Base
+ def process
+ $processed = mail.subject
+ end
+end
+
+class UnsuccessfulMailbox < ActionMailbox::Base
+ rescue_from(RuntimeError) { $processed = :failure }
+
+ def process
+ raise "No way!"
+ end
+end
+
+class BouncingMailbox < ActionMailbox::Base
+ def process
+ $processed = :bounced
+ bounced!
+ end
+end
+
+
+class ActionMailbox::Base::StateTest < ActiveSupport::TestCase
+ setup do
+ $processed = false
+ @inbound_email = create_inbound_email_from_mail \
+ to: "replies@example.com", subject: "I was processed"
+ end
+
+ test "successful mailbox processing leaves inbound email in delivered state" do
+ SuccessfulMailbox.receive @inbound_email
+ assert @inbound_email.delivered?
+ assert_equal "I was processed", $processed
+ end
+
+ test "unsuccessful mailbox processing leaves inbound email in failed state" do
+ UnsuccessfulMailbox.receive @inbound_email
+ assert @inbound_email.failed?
+ assert_equal :failure, $processed
+ end
+
+ test "bounced inbound emails are not delivered" do
+ BouncingMailbox.receive @inbound_email
+ assert @inbound_email.bounced?
+ assert_equal :bounced, $processed
+ end
+end
diff --git a/actionmailbox/test/unit/relayer_test.rb b/actionmailbox/test/unit/relayer_test.rb
new file mode 100644
index 0000000000..fb2b48ea16
--- /dev/null
+++ b/actionmailbox/test/unit/relayer_test.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+require "action_mailbox/relayer"
+
+module ActionMailbox
+ class RelayerTest < ActiveSupport::TestCase
+ URL = "https://example.com/rails/action_mailbox/relay/inbound_emails"
+ INGRESS_PASSWORD = "secret"
+
+ setup do
+ @relayer = ActionMailbox::Relayer.new(url: URL, password: INGRESS_PASSWORD)
+ end
+
+ test "successfully relaying an email" do
+ stub_request(:post, URL).to_return status: 204
+
+ result = @relayer.relay(file_fixture("welcome.eml").read)
+ assert_equal "2.0.0", result.status_code
+ assert_equal "Successfully relayed message to ingress", result.message
+ assert result.success?
+ assert_not result.failure?
+
+ assert_requested :post, URL, body: file_fixture("welcome.eml").read,
+ basic_auth: [ "actionmailbox", INGRESS_PASSWORD ],
+ headers: { "Content-Type" => "message/rfc822", "User-Agent" => /\AAction Mailbox relayer v\d+\./ }
+ end
+
+ test "unsuccessfully relaying with invalid credentials" do
+ stub_request(:post, URL).to_return status: 401
+
+ result = @relayer.relay(file_fixture("welcome.eml").read)
+ assert_equal "4.7.0", result.status_code
+ assert_equal "Invalid credentials for ingress", result.message
+ assert_not result.success?
+ assert result.failure?
+ end
+
+ test "unsuccessfully relaying due to an unspecified server error" do
+ stub_request(:post, URL).to_return status: 500
+
+ result = @relayer.relay(file_fixture("welcome.eml").read)
+ assert_equal "4.0.0", result.status_code
+ assert_equal "HTTP 500", result.message
+ assert_not result.success?
+ assert result.failure?
+ end
+
+ test "unsuccessfully relaying due to a gateway timeout" do
+ stub_request(:post, URL).to_return status: 504
+
+ result = @relayer.relay(file_fixture("welcome.eml").read)
+ assert_equal "4.0.0", result.status_code
+ assert_equal "HTTP 504", result.message
+ assert_not result.success?
+ assert result.failure?
+ end
+
+ test "unsuccessfully relaying due to ECONNRESET" do
+ stub_request(:post, URL).to_raise Errno::ECONNRESET.new
+
+ result = @relayer.relay(file_fixture("welcome.eml").read)
+ assert_equal "4.4.2", result.status_code
+ assert_equal "Network error relaying to ingress: Connection reset by peer", result.message
+ assert_not result.success?
+ assert result.failure?
+ end
+
+ test "unsuccessfully relaying due to connection failure" do
+ stub_request(:post, URL).to_raise SocketError.new("Failed to open TCP connection to example.com:443")
+
+ result = @relayer.relay(file_fixture("welcome.eml").read)
+ assert_equal "4.4.2", result.status_code
+ assert_equal "Network error relaying to ingress: Failed to open TCP connection to example.com:443", result.message
+ assert_not result.success?
+ assert result.failure?
+ end
+
+ test "unsuccessfully relaying due to client-side timeout" do
+ stub_request(:post, URL).to_timeout
+
+ result = @relayer.relay(file_fixture("welcome.eml").read)
+ assert_equal "4.4.2", result.status_code
+ assert_equal "Timed out relaying to ingress", result.message
+ assert_not result.success?
+ assert result.failure?
+ end
+
+ test "unsuccessfully relaying due to an unhandled exception" do
+ stub_request(:post, URL).to_raise StandardError.new("Something went wrong")
+
+ result = @relayer.relay(file_fixture("welcome.eml").read)
+ assert_equal "4.0.0", result.status_code
+ assert_equal "Error relaying to ingress: Something went wrong", result.message
+ assert_not result.success?
+ assert result.failure?
+ end
+ end
+end
diff --git a/actionmailbox/test/unit/router_test.rb b/actionmailbox/test/unit/router_test.rb
new file mode 100644
index 0000000000..c5dce60856
--- /dev/null
+++ b/actionmailbox/test/unit/router_test.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+class RootMailbox < ActionMailbox::Base
+ def process
+ $processed_by = self.class.to_s
+ $processed_mail = mail
+ end
+end
+
+class FirstMailbox < RootMailbox
+end
+
+class SecondMailbox < RootMailbox
+end
+
+module Nested
+ class FirstMailbox < RootMailbox
+ end
+end
+
+class FirstMailboxAddress
+ def match?(inbound_email)
+ inbound_email.mail.to.include?("replies-class@example.com")
+ end
+end
+
+module ActionMailbox
+ class RouterTest < ActiveSupport::TestCase
+ setup do
+ @router = ActionMailbox::Router.new
+ $processed_by = $processed_mail = nil
+ end
+
+ test "single string route" do
+ @router.add_routes("first@example.com" => :first)
+
+ inbound_email = create_inbound_email_from_mail(to: "first@example.com", subject: "This is a reply")
+ @router.route inbound_email
+ assert_equal "FirstMailbox", $processed_by
+ assert_equal inbound_email.mail, $processed_mail
+ end
+
+ test "single string routing on cc" do
+ @router.add_routes("first@example.com" => :first)
+
+ inbound_email = create_inbound_email_from_mail(to: "someone@example.com", cc: "first@example.com", subject: "This is a reply")
+ @router.route inbound_email
+ assert_equal "FirstMailbox", $processed_by
+ assert_equal inbound_email.mail, $processed_mail
+ end
+
+ test "single string routing case-insensitively" do
+ @router.add_routes("first@example.com" => :first)
+
+ inbound_email = create_inbound_email_from_mail(to: "FIRST@example.com", subject: "This is a reply")
+ @router.route inbound_email
+ assert_equal "FirstMailbox", $processed_by
+ assert_equal inbound_email.mail, $processed_mail
+ end
+
+ test "multiple string routes" do
+ @router.add_routes("first@example.com" => :first, "second@example.com" => :second)
+
+ inbound_email = create_inbound_email_from_mail(to: "first@example.com", subject: "This is a reply")
+ @router.route inbound_email
+ assert_equal "FirstMailbox", $processed_by
+ assert_equal inbound_email.mail, $processed_mail
+
+ inbound_email = create_inbound_email_from_mail(to: "second@example.com", subject: "This is a reply")
+ @router.route inbound_email
+ assert_equal "SecondMailbox", $processed_by
+ assert_equal inbound_email.mail, $processed_mail
+ end
+
+ test "single regexp route" do
+ @router.add_routes(/replies-\w+@example.com/ => :first, "replies-nowhere@example.com" => :second)
+
+ inbound_email = create_inbound_email_from_mail(to: "replies-okay@example.com", subject: "This is a reply")
+ @router.route inbound_email
+ assert_equal "FirstMailbox", $processed_by
+ end
+
+ test "single proc route" do
+ @router.add_route \
+ ->(inbound_email) { inbound_email.mail.to.include?("replies-proc@example.com") },
+ to: :second
+
+ @router.route create_inbound_email_from_mail(to: "replies-proc@example.com", subject: "This is a reply")
+ assert_equal "SecondMailbox", $processed_by
+ end
+
+ test "address class route" do
+ @router.add_route FirstMailboxAddress.new, to: :first
+ @router.route create_inbound_email_from_mail(to: "replies-class@example.com", subject: "This is a reply")
+ assert_equal "FirstMailbox", $processed_by
+ end
+
+ test "string route to nested mailbox" do
+ @router.add_route "first@example.com", to: "nested/first"
+
+ inbound_email = create_inbound_email_from_mail(to: "first@example.com", subject: "This is a reply")
+ @router.route inbound_email
+ assert_equal "Nested::FirstMailbox", $processed_by
+ end
+
+ test "all as the only route" do
+ @router.add_route :all, to: :first
+ @router.route create_inbound_email_from_mail(to: "replies-class@example.com", subject: "This is a reply")
+ assert_equal "FirstMailbox", $processed_by
+ end
+
+ test "all as the second route" do
+ @router.add_route FirstMailboxAddress.new, to: :first
+ @router.add_route :all, to: :second
+
+ @router.route create_inbound_email_from_mail(to: "replies-class@example.com", subject: "This is a reply")
+ assert_equal "FirstMailbox", $processed_by
+
+ @router.route create_inbound_email_from_mail(to: "elsewhere@example.com", subject: "This is a reply")
+ assert_equal "SecondMailbox", $processed_by
+ end
+
+ test "missing route" do
+ assert_raises(ActionMailbox::Router::RoutingError) do
+ inbound_email = create_inbound_email_from_mail(to: "going-nowhere@example.com", subject: "This is a reply")
+ @router.route inbound_email
+ assert inbound_email.bounced?
+ end
+ end
+
+ test "invalid address" do
+ assert_raises(ArgumentError) do
+ @router.add_route Array.new, to: :first
+ end
+ end
+ end
+end
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 5f3fc44e6e..806e428481 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,9 @@
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Deprecate `ActionMailer::Base.receive` in favor of [Action Mailbox](https://github.com/rails/rails/tree/master/actionmailbox).
+
+ *George Claghorn*
+
* Add `MailDeliveryJob` for delivering both regular and parameterized mail. Deprecate using `DeliveryJob` and `Parameterized::DeliveryJob`.
*Gannon McGibbon*
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index 1cb3add0fc..ab7c27c209 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2018 David Heinemeier Hansson
+Copyright (c) 2004-2019 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 14dfb82234..74d7ea65ce 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -93,42 +93,6 @@ Example:
.....
end
-== Receiving emails
-
-To receive emails, you need to implement a public instance method called
-+receive+ that takes an email object as its single parameter. The Action Mailer
-framework has a corresponding class method, which is also called +receive+, that
-accepts a raw, unprocessed email as a string, which it then turns into the email
-object and calls the receive instance method.
-
-Example:
-
- class Mailman < ActionMailer::Base
- def receive(email)
- page = Page.find_by(address: email.to.first)
- page.emails.create(
- subject: email.subject, body: email.body
- )
-
- if email.has_attachments?
- email.attachments.each do |attachment|
- page.attachments.create({
- file: attachment, description: email.subject
- })
- end
- end
- end
- end
-
-This Mailman can be the target for Postfix or other MTAs. In Rails, you would use
-the runner in the trivial case like this:
-
- rails runner 'Mailman.receive(STDIN.read)'
-
-However, invoking Rails in the runner for each mail to be received is very
-resource intensive. A single instance of Rails should be run within a daemon, if
-it is going to process more than just a limited amount of email.
-
== Configuration
The Base class has the full list of configuration options. Here's an example:
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index a6c482f1a0..c76cb3ec72 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = "actionmailer"
s.version = version
- s.summary = "Email composition, delivery, and receiving framework (part of Rails)."
- s.description = "Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments."
+ s.summary = "Email composition and delivery framework (part of Rails)."
+ s.description = "Email on Rails. Compose, deliver, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments."
s.required_ruby_version = ">= 2.5.0"
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 48f16180be..953c5fb508 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2018 David Heinemeier Hansson
+# Copyright (c) 2004-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 8ddc90b9df..5610212fad 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -461,7 +461,7 @@ module ActionMailer
helper ActionMailer::MailHelper
- class_attribute :delivery_job, default: ::ActionMailer::MailDeliveryJob
+ class_attribute :delivery_job, default: ::ActionMailer::DeliveryJob
class_attribute :default_params, default: {
mime_version: "1.0",
charset: "UTF-8",
@@ -565,6 +565,11 @@ module ActionMailer
# end
# end
def receive(raw_mail)
+ ActiveSupport::Deprecation.warn(<<~MESSAGE.squish)
+ ActionMailer::Base.receive is deprecated and will be removed in Rails 6.1.
+ Use Action Mailbox to process inbound email.
+ MESSAGE
+
ActiveSupport::Notifications.instrument("receive.action_mailer") do |payload|
mail = Mail.new(raw_mail)
set_payload_for_mail(payload, mail)
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index 72eb5d61e8..4cab52d1f3 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -10,7 +10,7 @@ module ActionMailer
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionmailer/lib/action_mailer/parameterized.rb b/actionmailer/lib/action_mailer/parameterized.rb
index 999435919e..0a97af8105 100644
--- a/actionmailer/lib/action_mailer/parameterized.rb
+++ b/actionmailer/lib/action_mailer/parameterized.rb
@@ -145,12 +145,20 @@ module ActionMailer
if processed?
super
else
- job = @mailer_class.delivery_job
+ job = delivery_job_class
args = arguments_for(job, delivery_method)
job.set(options).perform_later(*args)
end
end
+ def delivery_job_class
+ if @mailer_class.delivery_job <= MailDeliveryJob
+ @mailer_class.delivery_job
+ else
+ Parameterized::DeliveryJob
+ end
+ end
+
def arguments_for(delivery_job, delivery_method)
if delivery_job <= MailDeliveryJob
[@mailer_class.name, @action.to_s, delivery_method.to_s, params: @params, args: @args]
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index bb6141b406..23488db790 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -46,6 +46,10 @@ module ActionMailer
register_preview_interceptors(options.delete(:preview_interceptors))
register_observers(options.delete(:observers))
+ if delivery_job = options.delete(:delivery_job)
+ self.delivery_job = delivery_job.constantize
+ end
+
options.each { |k, v| send("#{k}=", v) }
end
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index f647896374..448807c144 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -33,6 +33,8 @@ I18n.enforce_available_locales = false
FIXTURE_LOAD_PATH = File.expand_path("fixtures", __dir__)
ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
+ActionMailer::Base.delivery_job = ActionMailer::MailDeliveryJob
+
class ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
diff --git a/actionmailer/test/legacy_delivery_job_test.rb b/actionmailer/test/legacy_delivery_job_test.rb
index 112c842beb..3a3872df47 100644
--- a/actionmailer/test/legacy_delivery_job_test.rb
+++ b/actionmailer/test/legacy_delivery_job_test.rb
@@ -11,9 +11,6 @@ class LegacyDeliveryJobTest < ActiveSupport::TestCase
class LegacyDeliveryJob < ActionMailer::DeliveryJob
end
- class LegacyParmeterizedDeliveryJob < ActionMailer::Parameterized::DeliveryJob
- end
-
setup do
@previous_logger = ActiveJob::Base.logger
ActiveJob::Base.logger = Logger.new(nil)
@@ -42,9 +39,9 @@ class LegacyDeliveryJobTest < ActiveSupport::TestCase
{ inviter: "david@basecamp.com", invitee: "jason@basecamp.com" },
]
- with_delivery_job(LegacyParmeterizedDeliveryJob) do
+ with_delivery_job(LegacyDeliveryJob) do
assert_deprecated do
- assert_performed_with(job: LegacyParmeterizedDeliveryJob, args: args) do
+ assert_performed_with(job: ActionMailer::Parameterized::DeliveryJob, args: args) do
mail.deliver_later
end
end
diff --git a/actionmailer/test/log_subscriber_test.rb b/actionmailer/test/log_subscriber_test.rb
index 7686fd10c9..fb569ce45f 100644
--- a/actionmailer/test/log_subscriber_test.rb
+++ b/actionmailer/test/log_subscriber_test.rb
@@ -53,7 +53,9 @@ class AMLogSubscriberTest < ActionMailer::TestCase
def test_receive_is_notified
fixture = File.read(File.expand_path("fixtures/raw_email", __dir__))
- TestMailer.receive(fixture)
+ assert_deprecated do
+ TestMailer.receive(fixture)
+ end
wait
assert_equal(1, @logger.logged(:info).size)
assert_match(/Received mail/, @logger.logged(:info).first)
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 95da4265a4..1d2f6b09c3 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,16 @@
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Remove deprecated `fragment_cache_key` helper in favor of `combined_fragment_cache_key`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated methods in `ActionDispatch::TestResponse`.
+
+ `#success?`, `missing?` and `error?` were deprecated in Rails 5.2 in favor of
+ `#successful?`, `not_found?` and `server_error?`.
+
+ *Rafael Mendonça França*
+
* Introduce ActionDispatch::HostAuthorization
This is a new middleware that guards against DNS rebinding attacks by
@@ -63,7 +76,7 @@
* Apply mapping to symbols returned from dynamic CSP sources
Previously if a dynamic source returned a symbol such as :self it
- would be converted to a string implicity, e.g:
+ would be converted to a string implicitly, e.g:
policy.default_src -> { :self }
@@ -85,10 +98,6 @@
*Yoshiyuki Kinjo*
-* Remove undocumented `params` option from `url_for` helper.
-
- *Ilkka Oksanen*
-
* Encode Content-Disposition filenames on `send_data` and `send_file`.
Previously, `send_data 'data', filename: "\u{3042}.txt"` sends
`"filename=\"\u{3042}.txt\""` as Content-Disposition and it can be
@@ -120,7 +129,7 @@
*Assain Jaleel*
-* Raises `ActionController::RespondToMismatchError` with confliciting `respond_to` invocations.
+* Raises `ActionController::RespondToMismatchError` with conflicting `respond_to` invocations.
`respond_to` can match multiple types and lead to undefined behavior when
multiple invocations are made and the types do not match:
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index 1cb3add0fc..ab7c27c209 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2018 David Heinemeier Hansson
+Copyright (c) 2004-2019 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb
index 95078a2a28..4e454adc5f 100644
--- a/actionpack/lib/abstract_controller/caching/fragments.rb
+++ b/actionpack/lib/abstract_controller/caching/fragments.rb
@@ -61,25 +61,6 @@ module AbstractController
end
# Given a key (as described in +expire_fragment+), returns
- # a key suitable for use in reading, writing, or expiring a
- # cached fragment. All keys begin with <tt>views/</tt>,
- # followed by any controller-wide key prefix values, ending
- # with the specified +key+ value. The key is expanded using
- # ActiveSupport::Cache.expand_cache_key.
- def fragment_cache_key(key)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Calling fragment_cache_key directly is deprecated and will be removed in Rails 6.0.
- All fragment accessors now use the combined_fragment_cache_key method that retains the key as an array,
- such that the caching stores can interrogate the parts for cache versions used in
- recyclable cache keys.
- MSG
-
- head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
- tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
- ActiveSupport::Cache.expand_cache_key([*head, *tail], :views)
- end
-
- # Given a key (as described in +expire_fragment+), returns
# a key array suitable for use in reading, writing, or expiring a
# cached fragment. All keys begin with <tt>:views</tt>,
# followed by <tt>ENV["RAILS_CACHE_ID"]</tt> or <tt>ENV["RAILS_APP_VERSION"]</tt> if set,
diff --git a/actionpack/lib/action_controller/metal/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
index 2dc990f303..f9a758ff0e 100644
--- a/actionpack/lib/action_controller/metal/basic_implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
@@ -6,7 +6,7 @@ module ActionController
super.tap { default_render unless performed? }
end
- def default_render(*args)
+ def default_render
head :no_content
end
end
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index 380f2e9591..a4861dc2c0 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -44,18 +44,18 @@ module ActionController #:nodoc:
end
private
- def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
+ def redirect_to(options = {}, response_options_and_flash = {}) #:doc:
self.class._flash_types.each do |flash_type|
- if type = response_status_and_flash.delete(flash_type)
+ if type = response_options_and_flash.delete(flash_type)
flash[flash_type] = type
end
end
- if other_flashes = response_status_and_flash.delete(:flash)
+ if other_flashes = response_options_and_flash.delete(:flash)
flash.update(other_flashes)
end
- super(options, response_status_and_flash)
+ super(options, response_options_and_flash)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index 26e6f72b66..93fd57b640 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -40,7 +40,7 @@ module ActionController
protocol: "https://",
host: request.host,
path: request.fullpath,
- status: :moved_permanently
+ status: :moved_permanently,
}
if host_or_options.is_a?(Hash)
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 7036123d5d..6a274d35cb 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -69,21 +69,20 @@ module ActionController
extend ActiveSupport::Concern
module ClassMethods
- def http_basic_authenticate_with(options = {})
- before_action(options.except(:name, :password, :realm)) do
- authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
- # This comparison uses & so that it doesn't short circuit and
- # uses `secure_compare` so that length information
- # isn't leaked.
- ActiveSupport::SecurityUtils.secure_compare(name, options[:name]) &
- ActiveSupport::SecurityUtils.secure_compare(password, options[:password])
- end
- end
+ def http_basic_authenticate_with(name:, password:, realm: nil, **options)
+ before_action(options) { http_basic_authenticate_or_request_with name: name, password: password, realm: realm }
+ end
+ end
+
+ def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
+ authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
+ ActiveSupport::SecurityUtils.secure_compare(given_name, name) &
+ ActiveSupport::SecurityUtils.secure_compare(given_password, password)
end
end
- def authenticate_or_request_with_http_basic(realm = "Application", message = nil, &login_procedure)
- authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm, message)
+ def authenticate_or_request_with_http_basic(realm = nil, message = nil, &login_procedure)
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm || "Application", message)
end
def authenticate_with_http_basic(&login_procedure)
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index d3bb58f48b..8365ddca57 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -30,9 +30,9 @@ module ActionController
# :stopdoc:
include BasicImplicitRender
- def default_render(*args)
+ def default_render
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
- render(*args)
+ render
elsif any_templates?(action_name.to_s, _prefixes)
message = "#{self.class.name}\##{action_name} is missing a template " \
"for this request format and variant.\n" \
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 118da11990..bf5e7a433f 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -124,6 +124,14 @@ module ActionController #:nodoc:
#
# render json: @people
#
+ # +any+ can also be used with no arguments, in which case it will be used for any format requested by
+ # the user:
+ #
+ # respond_to do |format|
+ # format.html
+ # format.any { redirect_to support_path }
+ # end
+ #
# Formats can have different variants.
#
# The request variant is a specialization of the request format, like <tt>:tablet</tt>,
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 7361946de5..09716f7588 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -241,22 +241,8 @@ module ActionController
# Performs parameters wrapping upon the request. Called automatically
# by the metal call stack.
def process_action(*args)
- if _wrapper_enabled?
- wrapped_hash = _wrap_parameters request.request_parameters
- wrapped_keys = request.request_parameters.keys
- wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
-
- # This will make the wrapped hash accessible from controller and view.
- request.parameters.merge! wrapped_hash
- request.request_parameters.merge! wrapped_hash
-
- # This will display the wrapped hash in the log file.
- request.filtered_parameters.merge! wrapped_filtered_hash
- end
- ensure
- # NOTE: Rescues all exceptions so they
- # may be caught in ActionController::Rescue.
- return super
+ _perform_parameter_wrapping if _wrapper_enabled?
+ super
end
private
@@ -292,5 +278,20 @@ module ActionController
ref = request.content_mime_type.ref
_wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key)
end
+
+ def _perform_parameter_wrapping
+ wrapped_hash = _wrap_parameters request.request_parameters
+ wrapped_keys = request.request_parameters.keys
+ wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
+
+ # This will make the wrapped hash accessible from controller and view.
+ request.parameters.merge! wrapped_hash
+ request.request_parameters.merge! wrapped_hash
+
+ # This will display the wrapped hash in the log file.
+ request.filtered_parameters.merge! wrapped_filtered_hash
+ rescue ActionDispatch::Http::Parameters::ParseError
+ # swallow parse error exception
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 2804a06a58..67c198d150 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -55,11 +55,11 @@ module ActionController
# Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
# To terminate the execution of the function immediately after the +redirect_to+, use return.
# redirect_to post_url(@post) and return
- def redirect_to(options = {}, response_status = {})
+ def redirect_to(options = {}, response_options = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
- self.status = _extract_redirect_to_status(options, response_status)
+ self.status = _extract_redirect_to_status(options, response_options)
self.location = _compute_redirect_to_location(request, options)
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
end
@@ -114,11 +114,11 @@ module ActionController
public :_compute_redirect_to_location
private
- def _extract_redirect_to_status(options, response_status)
+ def _extract_redirect_to_status(options, response_options)
if options.is_a?(Hash) && options.key?(:status)
Rack::Utils.status_code(options.delete(:status))
- elsif response_status.key?(:status)
- Rack::Utils.status_code(response_status[:status])
+ elsif response_options.key?(:status)
+ Rack::Utils.status_code(response_options[:status])
else
302
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index a643484d96..57921f32b7 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -26,7 +26,7 @@ module ActionController
end
end
- # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
+ # ActionController::TestCase will be deprecated and moved to a gem in the future.
# Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest #:nodoc:
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
@@ -276,9 +276,6 @@ module ActionController
# after calling +post+. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail.
#
- # (Earlier versions of \Rails required each functional test to subclass
- # Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
- #
# == Controller is automatically inferred
#
# ActionController::TestCase will automatically infer the controller under test
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index a92d336214..8f39b88d56 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2018 David Heinemeier Hansson
+# Copyright (c) 2004-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index f67b13f657..8cc84ff36c 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -197,10 +197,12 @@ module ActionDispatch
if control.empty?
# Let middleware handle default behavior
elsif control[:no_cache]
- self._cache_control = NO_CACHE
- if control[:extras]
- self._cache_control = _cache_control + ", #{control[:extras].join(', ')}"
- end
+ options = []
+ options << PUBLIC if control[:public]
+ options << NO_CACHE
+ options.concat(control[:extras]) if control[:extras]
+
+ self._cache_control = options.join(", ")
else
extras = control[:extras]
max_age = control[:max_age]
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 26d3fd936f..cb28baa229 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -61,10 +61,6 @@ module ActionDispatch
get_header Cookies::SIGNED_COOKIE_DIGEST
end
- def secret_token
- get_header Cookies::SECRET_TOKEN
- end
-
def secret_key_base
get_header Cookies::SECRET_KEY_BASE
end
@@ -181,7 +177,6 @@ module ActionDispatch
USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption"
ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher"
SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest"
- SECRET_TOKEN = "action_dispatch.secret_token"
SECRET_KEY_BASE = "action_dispatch.secret_key_base"
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer"
COOKIES_DIGEST = "action_dispatch.cookies_digest"
@@ -215,9 +210,6 @@ module ActionDispatch
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
# cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
- # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
- # legacy cookies signed with the old key generator will be transparently upgraded.
- #
# This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
#
# Example:
@@ -233,9 +225,6 @@ module ActionDispatch
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
# If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
- # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
- # legacy cookies signed with the old key generator will be transparently upgraded.
- #
# If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
# are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
#
@@ -264,10 +253,6 @@ module ActionDispatch
private
- def upgrade_legacy_signed_cookies?
- request.secret_token.present? && request.secret_key_base.present?
- end
-
def upgrade_legacy_hmac_aes_cbc_cookies?
request.secret_key_base.present? &&
request.encrypted_signed_cookie_salt.present? &&
@@ -592,10 +577,6 @@ module ActionDispatch
request.cookies_rotations.signed.each do |*secrets, **options|
@verifier.rotate(*secrets, serializer: SERIALIZER, **options)
end
-
- if upgrade_legacy_signed_cookies?
- @verifier.rotate request.secret_token, serializer: SERIALIZER
- end
end
private
@@ -640,10 +621,6 @@ module ActionDispatch
@encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
end
-
- if upgrade_legacy_signed_cookies?
- @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
- end
end
private
@@ -652,7 +629,7 @@ module ActionDispatch
@encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
end
rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
- parse_legacy_signed_message(name, encrypted_message)
+ nil
end
def commit(name, options)
@@ -660,16 +637,6 @@ module ActionDispatch
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
end
-
- def parse_legacy_signed_message(name, legacy_signed_message)
- if defined?(@legacy_verifier)
- deserialize(name) do |rotate|
- rotate.call
-
- @legacy_verifier.verified(legacy_signed_message)
- end
- end
- end
end
def initialize(app)
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index df680c1c5f..02ccfbc81a 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -16,11 +16,6 @@ module ActionDispatch
# The cookie jar used for storage is automatically configured to be the
# best possible option given your application's configuration.
#
- # If you only have secret_token set, your cookies will be signed, but
- # not encrypted. This means a user cannot alter their +user_id+ without
- # knowing your app's secret key, but can easily read their +user_id+. This
- # was the default for Rails 3 apps.
- #
# Your cookies will be encrypted using your apps secret_key_base. This
# goes a step further than signed cookies in that encrypted cookies cannot
# be altered or read by users. This is the default starting in Rails 4.
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb
index 2fa78dd385..1fbc107e28 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb
@@ -2,6 +2,6 @@
<h1>Blocked host: <%= @host %></h1>
</header>
<div id="container">
- <h2>To allow requests to <%= @host %>, add the following configuration:</h2>
- <pre>Rails.application.config.hosts &lt;&lt; "<%= @host %>"</pre>
+ <h2>To allow requests to <%= @host %>, add the following to your environment configuration:</h2>
+ <pre>config.hosts &lt;&lt; "<%= @host %>"</pre>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb
index 4e2d1d0b08..a94dd982a7 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb
@@ -1,5 +1,5 @@
Blocked host: <%= @host %>
-To allow requests to <%= @host %>, add the following configuration:
+To allow requests to <%= @host %>, add the following to your environment configuration:
- Rails.application.config.hosts << "<%= @host %>"
+ config.hosts << "<%= @host %>"
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 2966c969f6..972953d4f3 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -820,6 +820,10 @@ module ActionDispatch
path, params = generate(route_name, path_options, recall)
+ if options.key? :params
+ params.merge! options[:params]
+ end
+
options[:path] = path
options[:script_name] = script_name
options[:params] = params
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 1a31c7dbb8..fcb8ae296b 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -133,6 +133,7 @@ module ActionDispatch
# <tt>ActionDispatch::Http::URL.tld_length</tt>, which in turn defaults to 1.
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
+ # * <tt>:params</tt> - The query parameters to be appended to the path.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
# * <tt>:script_name</tt> - Specifies application path relative to domain root. If provided, prepends application path.
#
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index af41521c5c..28cde6704e 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -160,9 +160,16 @@ module ActionDispatch
@controller.singleton_class.include(_routes.url_helpers)
if @controller.respond_to? :view_context_class
- @controller.view_context_class = Class.new(@controller.view_context_class) do
+ view_context_class = Class.new(@controller.view_context_class) do
include _routes.url_helpers
end
+
+ custom_view_context = Module.new {
+ define_method(:view_context_class) do
+ view_context_class
+ end
+ }
+ @controller.extend(custom_view_context)
end
end
yield @routes
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 7c1202dc0e..6f7c86fdcf 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -14,33 +14,6 @@ module ActionDispatch
new response.status, response.headers, response.body
end
- # Was the response successful?
- def success?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The success? predicate is deprecated and will be removed in Rails 6.0.
- Please use successful? as provided by Rack::Response::Helpers.
- MSG
- successful?
- end
-
- # Was the URL not found?
- def missing?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The missing? predicate is deprecated and will be removed in Rails 6.0.
- Please use not_found? as provided by Rack::Response::Helpers.
- MSG
- not_found?
- end
-
- # Was there a server-side error?
- def error?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The error? predicate is deprecated and will be removed in Rails 6.0.
- Please use server_error? as provided by Rack::Response::Helpers.
- MSG
- server_error?
- end
-
def parsed_body
@parsed_body ||= response_parser.call(body)
end
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index 3f69109633..36ee77c693 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2018 David Heinemeier Hansson
+# Copyright (c) 2004-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index 37969fcb57..c7cb3cb91e 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -10,7 +10,7 @@ module ActionPack
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 558e710df9..d8cea10153 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -193,7 +193,7 @@ class UrlOptionsTest < ActionController::TestCase
action: "home",
controller: "pages",
only_path: true,
- token: "secret"
+ params: { "token" => "secret" }
}
assert_equal "/home?token=secret", rs.url_for(options)
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 6fe036dd15..5543f9120f 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -60,14 +60,6 @@ class FragmentCachingTest < ActionController::TestCase
@m2v2 = ModelWithKeyAndVersion.new("model/2", "2")
end
- def test_fragment_cache_key
- assert_deprecated do
- assert_equal "views/what a key", @controller.fragment_cache_key("what a key")
- assert_equal "views/test.host/fragment_caching_test/some_action",
- @controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action")
- end
- end
-
def test_combined_fragment_cache_key
assert_equal [ :views, "what a key" ], @controller.combined_fragment_cache_key("what a key")
assert_equal [ :views, "test.host/fragment_caching_test/some_action" ],
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 104c9eeade..fcee812ee4 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -888,7 +888,7 @@ class ControllerWithSymbolAsFilter < PostsController
yield
# Do stuff...
- wtf += 1
+ wtf + 1
end
end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 409a4ec2e6..bf95c633e5 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -242,8 +242,11 @@ end
class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionKey = "_myapp_session"
- Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
- Rotations = ActiveSupport::Messages::RotationConfiguration.new
+ Generator = ActiveSupport::CachingKeyGenerator.new(
+ ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 1000)
+ )
+ Rotations = ActiveSupport::Messages::RotationConfiguration.new
+ SIGNED_COOKIE_SALT = "signed cookie"
class TestController < ActionController::Base
add_flash_types :bar
@@ -365,6 +368,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
args[0][:env] ||= {}
args[0][:env]["action_dispatch.key_generator"] ||= Generator
args[0][:env]["action_dispatch.cookies_rotations"] = Rotations
+ args[0][:env]["action_dispatch.signed_cookie_salt"] = SIGNED_COOKIE_SALT
super(path, *args)
end
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index b133afb343..dd4ff85d11 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -44,7 +44,10 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
setup do
# Used as secret in generating nonce to prevent tampering of timestamp
@secret = "4fb45da9e4ab4ddeb7580d6a35503d99"
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(@secret)
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::CachingKeyGenerator.new(
+ ActiveSupport::KeyGenerator.new(@secret)
+ )
+ @request.env["action_dispatch.http_auth_salt"] = "http authentication"
end
teardown do
diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb
deleted file mode 100644
index 5e570a1d79..0000000000
--- a/actionpack/test/controller/new_base/render_context_test.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require "abstract_unit"
-
-# This is testing the decoupling of view renderer and view context
-# by allowing the controller to be used as view context. This is
-# similar to the way sinatra renders templates.
-module RenderContext
- class BasicController < ActionController::Base
- self.view_paths = [ActionView::FixtureResolver.new(
- "render_context/basic/hello_world.html.erb" => "<%= @value %> from <%= self.__controller_method__ %>",
- "layouts/basic.html.erb" => "?<%= yield %>?"
- )]
-
- # 1) Include ActionView::Context to bring the required dependencies
- include ActionView::Context
-
- # 2) Call _prepare_context that will do the required initialization
- before_action :_prepare_context
-
- def hello_world
- @value = "Hello"
- render action: "hello_world", layout: false
- end
-
- def with_layout
- @value = "Hello"
- render action: "hello_world", layout: "basic"
- end
-
- protected def __controller_method__
- "controller context!"
- end
-
- private
- # 3) Set view_context to self
- def view_context
- self
- end
- end
-
- class RenderContextTest < Rack::TestCase
- test "rendering using the controller as context" do
- get "/render_context/basic/hello_world"
- assert_body "Hello from controller context!"
- assert_status 200
- end
-
- test "rendering using the controller as context with layout" do
- get "/render_context/basic/with_layout"
- assert_body "?Hello from controller context!?"
- assert_status 200
- end
- end
-end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 306b245bd1..4750093c5c 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -183,6 +183,11 @@ class TestController < ActionController::Base
render action: "hello_world"
end
+ def conditional_hello_without_expires_and_public_header
+ response.headers["Cache-Control"] = "public, no-cache"
+ render action: "hello_world"
+ end
+
def conditional_hello_with_bangs
render action: "hello_world"
end
@@ -418,6 +423,11 @@ class ExpiresInRenderTest < ActionController::TestCase
assert_equal "no-cache", @response.headers["Cache-Control"]
end
+ def test_no_expires_now_with_public
+ get :conditional_hello_without_expires_and_public_header
+ assert_equal "public, no-cache", @response.headers["Cache-Control"]
+ end
+
def test_date_header_when_expires_in
time = Time.mktime(2011, 10, 30)
Time.stub :now, time do
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index e381abee36..9222250b9c 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -354,6 +354,14 @@ module AbstractController
assert_equal({ p2: "Y2" }.to_query, params[1])
end
+ def test_params_option
+ url = W.new.url_for(only_path: true, controller: "c", action: "a", params: { domain: "foo", id: "1" })
+ params = extract_params(url)
+ assert_equal("/c/a?domain=foo&id=1", url)
+ assert_equal({ domain: "foo" }.to_query, params[0])
+ assert_equal({ id: "1" }.to_query, params[1])
+ end
+
def test_hash_parameter
url = W.new.url_for(only_path: true, controller: "c", action: "a", query: { name: "Bob", category: "prof" })
params = extract_params(url)
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index 4a10637b54..23a46df5cd 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -14,7 +14,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
end
def dump_params_keys(hash = params)
- hash.keys.sort.inject("") do |s, k|
+ hash.keys.sort.each_with_object(+"") do |k, s|
value = hash[k]
if value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
@@ -23,8 +23,8 @@ class WebServiceTest < ActionDispatch::IntegrationTest
value = ""
end
- s += ", " unless s.empty?
- s += "#{k}#{value}"
+ s << ", " unless s.empty?
+ s << "#{k}#{value}"
end
end
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 6637c2cae9..8f9dbaf4b3 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -525,21 +525,6 @@ class CookiesTest < ActionController::TestCase
assert_equal 45, verifier.verify(@response.cookies["user_id"])
end
- def test_signed_cookie_with_legacy_secret_scheme
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- old_message = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", digest: "SHA1", serializer: Marshal).generate(45)
-
- @request.headers["Cookie"] = "user_id=#{old_message}"
- get :get_signed_cookie
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
-
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key("signed cookie")
- verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA1", serializer: Marshal)
- assert_equal 45, verifier.verify(@response.cookies["user_id"])
- end
-
def test_tampered_with_signed_cookie
key_generator = @request.env["action_dispatch.key_generator"]
secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
@@ -759,175 +744,7 @@ class CookiesTest < ActionController::TestCase
assert_equal ["user_name", "user_id"], @request.cookie_jar.instance_variable_get(:@cookies).keys
end
- def test_raises_argument_error_if_missing_secret
- assert_raise(ArgumentError, nil.inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(nil)
- get :set_signed_cookie
- }
-
- assert_raise(ArgumentError, "".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("")
- get :set_signed_cookie
- }
- end
-
- def test_raises_argument_error_if_secret_is_probably_insecure
- assert_raise(ArgumentError, "password".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("password")
- get :set_signed_cookie
- }
-
- assert_raise(ArgumentError, "secret".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("secret")
- get :set_signed_cookie
- }
-
- assert_raise(ArgumentError, "12345678901234567890123456789".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("12345678901234567890123456789")
- get :set_signed_cookie
- }
- end
-
- def test_legacy_signed_cookie_is_read_and_transparently_upgraded_by_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
-
- @request.headers["Cookie"] = "user_id=#{legacy_value}"
- get :get_signed_cookie
-
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
-
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
- verifier = ActiveSupport::MessageVerifier.new(secret)
- assert_equal 45, verifier.verify(@response.cookies["user_id"])
- end
-
- def test_legacy_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar")
-
- @request.headers["Cookie"] = "foo=#{legacy_value}"
- get :get_encrypted_cookie
-
- assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
-
- secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.authenticated_encrypted_cookie_salt"], 32)
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal)
- assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
- end
-
- def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :json
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
-
- @request.headers["Cookie"] = "user_id=#{legacy_value}"
- get :get_signed_cookie
-
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
-
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
- verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
- assert_equal 45, verifier.verify(@response.cookies["user_id"])
- end
-
- def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :json
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar")
-
- @request.headers["Cookie"] = "foo=#{legacy_value}"
- get :get_encrypted_cookie
-
- assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
-
- cipher = "aes-256-gcm"
- salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
- assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
- end
-
- def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :hybrid
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
-
- @request.headers["Cookie"] = "user_id=#{legacy_value}"
- get :get_signed_cookie
-
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
-
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
- verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
- assert_equal 45, verifier.verify(@response.cookies["user_id"])
- end
-
- def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :hybrid
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar")
-
- @request.headers["Cookie"] = "foo=#{legacy_value}"
- get :get_encrypted_cookie
-
- assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
-
- salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON)
- assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
- end
-
- def test_legacy_marshal_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :hybrid
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
-
- @request.headers["Cookie"] = "user_id=#{legacy_value}"
- get :get_signed_cookie
-
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
-
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
- verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
- assert_equal 45, verifier.verify(@response.cookies["user_id"])
- end
-
- def test_legacy_marshal_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :hybrid
-
- @request.env["action_dispatch.use_authenticated_cookie_encryption"] = true
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar")
-
- @request.headers["Cookie"] = "foo=#{legacy_value}"
- get :get_encrypted_cookie
-
- assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
-
- salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON)
- assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
- end
-
def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
@request.headers["Cookie"] = "user_id=45"
get :get_signed_cookie
@@ -936,8 +753,6 @@ class CookiesTest < ActionController::TestCase
end
def test_legacy_signed_cookie_is_treated_as_nil_by_encrypted_cookie_jar_if_tampered
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
@request.headers["Cookie"] = "foo=baz"
get :get_encrypted_cookie
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index c326f276bf..214e063276 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -8,7 +8,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
class Boomer
attr_accessor :closed
- def initialize(detailed = false)
+ def initialize(detailed = false)
@detailed = detailed
@closed = false
end
@@ -39,52 +39,50 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
def call(env)
env["action_dispatch.show_detailed_exceptions"] = @detailed
req = ActionDispatch::Request.new(env)
+ template = ActionView::Template.new(File.read(__FILE__), __FILE__, ActionView::Template::Handlers::Raw.new, {})
+
case req.path
- when %r{/pass}
+ when "/pass"
[404, { "X-Cascade" => "pass" }, self]
- when %r{/not_found}
+ when "/not_found"
raise AbstractController::ActionNotFound
- when %r{/runtime_error}
+ when "/runtime_error"
raise RuntimeError
- when %r{/method_not_allowed}
+ when "/method_not_allowed"
raise ActionController::MethodNotAllowed
- when %r{/intercepted_error}
+ when "/intercepted_error"
raise InterceptedErrorInstance
- when %r{/unknown_http_method}
+ when "/unknown_http_method"
raise ActionController::UnknownHttpMethod
- when %r{/not_implemented}
+ when "/not_implemented"
raise ActionController::NotImplemented
- when %r{/unprocessable_entity}
+ when "/unprocessable_entity"
raise ActionController::InvalidAuthenticityToken
- when %r{/not_found_original_exception}
+ when "/not_found_original_exception"
begin
raise AbstractController::ActionNotFound.new
rescue
- raise ActionView::Template::Error.new("template")
+ raise ActionView::Template::Error.new(template)
end
- when %r{/missing_template}
+ when "/missing_template"
raise ActionView::MissingTemplate.new(%w(foo), "foo/index", %w(foo), false, "mailer")
- when %r{/bad_request}
+ when "/bad_request"
raise ActionController::BadRequest
- when %r{/missing_keys}
+ when "/missing_keys"
raise ActionController::UrlGenerationError, "No route matches"
- when %r{/parameter_missing}
+ when "/parameter_missing"
raise ActionController::ParameterMissing, :missing_param_key
- when %r{/original_syntax_error}
+ when "/original_syntax_error"
eval "broke_syntax =" # `eval` need for raise native SyntaxError at runtime
- when %r{/syntax_error_into_view}
+ when "/syntax_error_into_view"
begin
eval "broke_syntax ="
rescue Exception
- template = ActionView::Template.new(File.read(__FILE__),
- __FILE__,
- ActionView::Template::Handlers::Raw.new,
- {})
raise ActionView::Template::Error.new(template)
end
- when %r{/framework_raises}
+ when "/framework_raises"
method_that_raises
- when %r{/nested_exceptions}
+ when "/nested_exceptions"
raise_nested_exceptions
else
raise "puke!"
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index a9a56f205f..b673fd3805 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -62,7 +62,7 @@ module ActionController
assert_nil @response.headers["Content-Length"]
end
- def test_headers_cannot_be_written_after_webserver_reads
+ def test_headers_cannot_be_written_after_web_server_reads
@response.stream.write "omg"
latch = Concurrent::CountDownLatch.new
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 9d1246b3a4..2a4d59affe 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -763,7 +763,6 @@ class RequestMethod < BaseRequestTest
test "post uneffected by local inflections" do
existing_acronyms = ActiveSupport::Inflector.inflections.acronyms.dup
- assert_deprecated { ActiveSupport::Inflector.inflections.acronym_regex.dup }
begin
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym "POS"
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 4dffbd0db1..897d17885e 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -4991,8 +4991,12 @@ end
class FlashRedirectTest < ActionDispatch::IntegrationTest
SessionKey = "_myapp_session"
- Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
- Rotations = ActiveSupport::Messages::RotationConfiguration.new
+ Generator = ActiveSupport::CachingKeyGenerator.new(
+ ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 1000)
+ )
+ Rotations = ActiveSupport::Messages::RotationConfiguration.new
+ SIGNED_COOKIE_SALT = "signed cookie"
+ ENCRYPTED_SIGNED_COOKIE_SALT = "sigend encrypted cookie"
class KeyGeneratorMiddleware
def initialize(app)
@@ -5002,6 +5006,8 @@ class FlashRedirectTest < ActionDispatch::IntegrationTest
def call(env)
env["action_dispatch.key_generator"] ||= Generator
env["action_dispatch.cookies_rotations"] ||= Rotations
+ env["action_dispatch.signed_cookie_salt"] = SIGNED_COOKIE_SALT
+ env["action_dispatch.encrypted_signed_cookie_salt"] = ENCRYPTED_SIGNED_COOKIE_SALT
@app.call(env)
end
diff --git a/actionpack/test/dispatch/test_response_test.rb b/actionpack/test/dispatch/test_response_test.rb
index f0b8f7785d..2629a61057 100644
--- a/actionpack/test/dispatch/test_response_test.rb
+++ b/actionpack/test/dispatch/test_response_test.rb
@@ -27,11 +27,4 @@ class TestResponseTest < ActiveSupport::TestCase
response = ActionDispatch::TestResponse.create(200, { "Content-Type" => "application/json" }, '{ "foo": "fighters" }')
assert_equal({ "foo" => "fighters" }, response.parsed_body)
end
-
- test "response status aliases deprecated" do
- response = ActionDispatch::TestResponse.create
- assert_deprecated { response.success? }
- assert_deprecated { response.missing? }
- assert_deprecated { response.error? }
- end
end
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index 1f4e14aef6..f8d89def6a 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -284,7 +284,7 @@ module ActionDispatch
def test_generate_missing_keys_no_matches_different_format_keys
get "/:controller/:action/:name", to: "foo#bar"
- primarty_parameters = {
+ primary_parameters = {
id: 1,
controller: "tasks",
action: "show",
@@ -297,9 +297,9 @@ module ActionDispatch
missing_parameters = {
missing_key => "task_1"
}
- request_parameters = primarty_parameters.merge(redirection_parameters).merge(missing_parameters)
+ request_parameters = primary_parameters.merge(redirection_parameters).merge(missing_parameters)
- message = "No route matches #{Hash[request_parameters.sort_by { |k, v|k.to_s }].inspect}, missing required keys: #{[missing_key.to_sym].inspect}"
+ message = "No route matches #{Hash[request_parameters.sort_by { |k, _|k.to_s }].inspect}, missing required keys: #{[missing_key.to_sym].inspect}"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(
diff --git a/actiontext/.gitignore b/actiontext/.gitignore
new file mode 100644
index 0000000000..739e00a2cb
--- /dev/null
+++ b/actiontext/.gitignore
@@ -0,0 +1,5 @@
+/test/dummy/db/*.sqlite3
+/test/dummy/db/*.sqlite3-journal
+/test/dummy/log/*.log
+/test/dummy/tmp/
+/tmp/
diff --git a/actiontext/CHANGELOG.md b/actiontext/CHANGELOG.md
new file mode 100644
index 0000000000..d92367dc17
--- /dev/null
+++ b/actiontext/CHANGELOG.md
@@ -0,0 +1,5 @@
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Added to Rails.
+
+ *DHH*
diff --git a/actiontext/MIT-LICENSE b/actiontext/MIT-LICENSE
new file mode 100644
index 0000000000..e28efa78e1
--- /dev/null
+++ b/actiontext/MIT-LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Basecamp, LLC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/actiontext/README.md b/actiontext/README.md
new file mode 100644
index 0000000000..aa1ad0153b
--- /dev/null
+++ b/actiontext/README.md
@@ -0,0 +1,9 @@
+# Action Text
+
+Action Text brings rich text content and editing to Rails. It includes the [Trix editor](https://trix-editor.org) that handles everything from formatting to links to quotes to lists to embedded images and galleries. The rich text content generated by the Trix editor is saved in its own RichText model that's associated with any existing Active Record model in the application. Any embedded images (or other attachments) are automatically stored using Active Storage and associated with the included RichText model.
+
+You can read more about Action Text in the [Action Text Overview](https://edgeguides.rubyonrails.org/action_text_overview.html) guide.
+
+## License
+
+Action Text is released under the [MIT License](https://opensource.org/licenses/MIT).
diff --git a/actiontext/Rakefile b/actiontext/Rakefile
new file mode 100644
index 0000000000..36aed17282
--- /dev/null
+++ b/actiontext/Rakefile
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require "bundler/setup"
+require "bundler/gem_tasks"
+require "rake/testtask"
+
+task :package
+
+Rake::TestTask.new do |t|
+ t.libs << "test"
+ t.pattern = "test/**/*_test.rb"
+ t.verbose = true
+end
+
+task default: :test
diff --git a/actiontext/actiontext.gemspec b/actiontext/actiontext.gemspec
new file mode 100644
index 0000000000..5583178e12
--- /dev/null
+++ b/actiontext/actiontext.gemspec
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
+
+Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = "actiontext"
+ s.version = version
+ s.summary = "Rich text framework."
+ s.description = "Edit and display rich text in Rails applications."
+
+ s.required_ruby_version = ">= 2.5.0"
+
+ s.license = "MIT"
+
+ s.authors = ["Javan Makhmali", "Sam Stephenson", "David Heinemeier Hansson"]
+ s.email = ["javan@javan.us", "sstephenson@gmail.com", "david@loudthinking.com"]
+ s.homepage = "https://rubyonrails.org"
+
+ s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*", "app/**/*", "config/**/*", "db/**/*", "package.json"]
+ s.require_path = "lib"
+
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/actiontext",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actiontext/CHANGELOG.md"
+ }
+
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
+ s.add_dependency "activesupport", version
+ s.add_dependency "activerecord", version
+ s.add_dependency "activestorage", version
+ s.add_dependency "actionpack", version
+
+ s.add_dependency "nokogiri", ">= 1.8.5"
+end
diff --git a/actiontext/app/helpers/action_text/content_helper.rb b/actiontext/app/helpers/action_text/content_helper.rb
new file mode 100644
index 0000000000..b3335601f9
--- /dev/null
+++ b/actiontext/app/helpers/action_text/content_helper.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module ActionText
+ module ContentHelper
+ SANITIZER = Rails::Html::Sanitizer.white_list_sanitizer
+ ALLOWED_TAGS = SANITIZER.allowed_tags + [ ActionText::Attachment::TAG_NAME, "figure", "figcaption" ]
+ ALLOWED_ATTRIBUTES = SANITIZER.allowed_attributes + ActionText::Attachment::ATTRIBUTES
+
+ def render_action_text_content(content)
+ content = content.render_attachments do |attachment|
+ unless attachment.in?(content.gallery_attachments)
+ attachment.node.tap do |node|
+ node.inner_html = render(attachment, in_gallery: false).chomp
+ end
+ end
+ end
+
+ content = content.render_attachment_galleries do |attachment_gallery|
+ render(layout: attachment_gallery, object: attachment_gallery) do
+ attachment_gallery.attachments.map do |attachment|
+ attachment.node.inner_html = render(attachment, in_gallery: true).chomp
+ attachment.to_html
+ end.join("").html_safe
+ end.chomp
+ end
+
+ sanitize content.to_html, tags: ALLOWED_TAGS, attributes: ALLOWED_ATTRIBUTES
+ end
+ end
+end
diff --git a/actiontext/app/helpers/action_text/tag_helper.rb b/actiontext/app/helpers/action_text/tag_helper.rb
new file mode 100644
index 0000000000..8434f2c611
--- /dev/null
+++ b/actiontext/app/helpers/action_text/tag_helper.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module ActionText
+ module TagHelper
+ cattr_accessor(:id, instance_accessor: false) { 0 }
+
+ # Returns a +trix-editor+ tag that instantiates the Trix JavaScript editor as well as a hidden field
+ # that Trix will write to on changes, so the content will be sent on form submissions.
+ #
+ # ==== Options
+ # * <tt>:class</tt> - Defaults to "trix-content" which ensures default styling is applied.
+ #
+ # ==== Example
+ #
+ # rich_text_area_tag "content", message.content
+ # # <input type="hidden" name="content" id="trix_input_post_1">
+ # # <trix-editor id="content" input="trix_input_post_1" class="trix-content" ...></trix-editor>
+ def rich_text_area_tag(name, value = nil, options = {})
+ options = options.symbolize_keys
+
+ options[:input] ||= "trix_input_#{ActionText::TagHelper.id += 1}"
+ options[:class] ||= "trix-content"
+
+ options[:data] ||= {}
+ options[:data][:direct_upload_url] = main_app.rails_direct_uploads_url
+ options[:data][:blob_url_template] = main_app.rails_service_blob_url(":signed_id", ":filename")
+
+ editor_tag = content_tag("trix-editor", "", options)
+ input_tag = hidden_field_tag(name, value, id: options[:input])
+
+ input_tag + editor_tag
+ end
+ end
+end
+
+module ActionView::Helpers
+ class Tags::ActionText < Tags::Base
+ delegate :dom_id, to: ActionView::RecordIdentifier
+
+ def render
+ options = @options.stringify_keys
+ add_default_name_and_id(options)
+ options["input"] ||= dom_id(object, [options["id"], :trix_input].compact.join("_")) if object
+ @template_object.rich_text_area_tag(options.delete("name"), editable_value, options)
+ end
+
+ def editable_value
+ value&.body.try(:to_trix_html)
+ end
+ end
+
+ module FormHelper
+ # Returns a +trix-editor+ tag that instantiates the Trix JavaScript editor as well as a hidden field
+ # that Trix will write to on changes, so the content will be sent on form submissions.
+ #
+ # ==== Options
+ # * <tt>:class</tt> - Defaults to "trix-content" which ensures default styling is applied.
+ #
+ # ==== Example
+ # form_with(model: @message) do |form|
+ # form.rich_text_area :content
+ # end
+ # # <input type="hidden" name="message[content]" id="message_content_trix_input_message_1">
+ # # <trix-editor id="content" input="message_content_trix_input_message_1" class="trix-content" ...></trix-editor>
+ def rich_text_area(object_name, method, options = {})
+ Tags::ActionText.new(object_name, method, self, options).render
+ end
+ end
+
+ class FormBuilder
+ def rich_text_area(method, options = {})
+ @template.rich_text_area(@object_name, method, objectify_options(options))
+ end
+ end
+end
diff --git a/actiontext/app/javascript/actiontext/attachment_upload.js b/actiontext/app/javascript/actiontext/attachment_upload.js
new file mode 100644
index 0000000000..77fbc97df6
--- /dev/null
+++ b/actiontext/app/javascript/actiontext/attachment_upload.js
@@ -0,0 +1,45 @@
+import { DirectUpload } from "@rails/activestorage"
+
+export class AttachmentUpload {
+ constructor(attachment, element) {
+ this.attachment = attachment
+ this.element = element
+ this.directUpload = new DirectUpload(attachment.file, this.directUploadUrl, this)
+ }
+
+ start() {
+ this.directUpload.create(this.directUploadDidComplete.bind(this))
+ }
+
+ directUploadWillStoreFileWithXHR(xhr) {
+ xhr.upload.addEventListener("progress", event => {
+ const progress = event.loaded / event.total * 100
+ this.attachment.setUploadProgress(progress)
+ })
+ }
+
+ directUploadDidComplete(error, attributes) {
+ if (error) {
+ throw new Error(`Direct upload failed: ${error}`)
+ }
+
+ this.attachment.setAttributes({
+ sgid: attributes.attachable_sgid,
+ url: this.createBlobUrl(attributes.signed_id, attributes.filename)
+ })
+ }
+
+ createBlobUrl(signedId, filename) {
+ return this.blobUrlTemplate
+ .replace(":signed_id", signedId)
+ .replace(":filename", encodeURIComponent(filename))
+ }
+
+ get directUploadUrl() {
+ return this.element.dataset.directUploadUrl
+ }
+
+ get blobUrlTemplate() {
+ return this.element.dataset.blobUrlTemplate
+ }
+}
diff --git a/actiontext/app/javascript/actiontext/index.js b/actiontext/app/javascript/actiontext/index.js
new file mode 100644
index 0000000000..0e9251018a
--- /dev/null
+++ b/actiontext/app/javascript/actiontext/index.js
@@ -0,0 +1,10 @@
+import { AttachmentUpload } from "./attachment_upload"
+
+addEventListener("trix-attachment-add", event => {
+ const { attachment, target } = event
+
+ if (attachment.file) {
+ const upload = new AttachmentUpload(attachment, target)
+ upload.start()
+ }
+})
diff --git a/actiontext/app/models/action_text/rich_text.rb b/actiontext/app/models/action_text/rich_text.rb
new file mode 100644
index 0000000000..1f39bc51b9
--- /dev/null
+++ b/actiontext/app/models/action_text/rich_text.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module ActionText
+ # The RichText record holds the content produced by the Trix editor in a serialized +body+ attribute.
+ # It also holds all the references to the embedded files, which are stored using Active Storage.
+ # This record is then associated with the Active Record model the application desires to have
+ # rich text content using the +has_rich_text+ class method.
+ class RichText < ActiveRecord::Base
+ self.table_name = "action_text_rich_texts"
+
+ serialize :body, ActionText::Content
+ delegate :to_s, :nil?, to: :body
+
+ belongs_to :record, polymorphic: true, touch: true
+ has_many_attached :embeds
+
+ before_save do
+ self.embeds = body.attachments.map(&:attachable) if body.present?
+ end
+
+ def to_plain_text
+ body&.to_plain_text.to_s
+ end
+
+ delegate :blank?, :empty?, :present?, to: :to_plain_text
+ end
+end
+
+ActiveSupport.run_load_hooks :action_text_rich_text, ActionText::RichText
diff --git a/actiontext/app/views/action_text/attachables/_missing_attachable.html.erb b/actiontext/app/views/action_text/attachables/_missing_attachable.html.erb
new file mode 100644
index 0000000000..5ffd93b89e
--- /dev/null
+++ b/actiontext/app/views/action_text/attachables/_missing_attachable.html.erb
@@ -0,0 +1 @@
+<%= "☒" -%>
diff --git a/actiontext/app/views/action_text/attachables/_remote_image.html.erb b/actiontext/app/views/action_text/attachables/_remote_image.html.erb
new file mode 100644
index 0000000000..3372f8d940
--- /dev/null
+++ b/actiontext/app/views/action_text/attachables/_remote_image.html.erb
@@ -0,0 +1,8 @@
+<figure class="attachment attachment--preview">
+ <%= image_tag(remote_image.url, width: remote_image.width, height: remote_image.height) %>
+ <% if caption = remote_image.try(:caption) %>
+ <figcaption class="attachment__caption">
+ <%= caption %>
+ </figcaption>
+ <% end %>
+</figure>
diff --git a/actiontext/app/views/action_text/attachment_galleries/_attachment_gallery.html.erb b/actiontext/app/views/action_text/attachment_galleries/_attachment_gallery.html.erb
new file mode 100644
index 0000000000..6bc8674dc5
--- /dev/null
+++ b/actiontext/app/views/action_text/attachment_galleries/_attachment_gallery.html.erb
@@ -0,0 +1,3 @@
+<div class="attachment-gallery attachment-gallery--<%= attachment_gallery.size %>">
+ <%= yield %>
+</div>
diff --git a/actiontext/app/views/action_text/content/_layout.html.erb b/actiontext/app/views/action_text/content/_layout.html.erb
new file mode 100644
index 0000000000..55cb708ac4
--- /dev/null
+++ b/actiontext/app/views/action_text/content/_layout.html.erb
@@ -0,0 +1,3 @@
+<div class="trix-content">
+ <%= render_action_text_content(content) %>
+</div>
diff --git a/actiontext/app/views/active_storage/blobs/_blob.html.erb b/actiontext/app/views/active_storage/blobs/_blob.html.erb
new file mode 100644
index 0000000000..49ba357dd1
--- /dev/null
+++ b/actiontext/app/views/active_storage/blobs/_blob.html.erb
@@ -0,0 +1,14 @@
+<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
+ <% if blob.representable? %>
+ <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
+ <% end %>
+
+ <figcaption class="attachment__caption">
+ <% if caption = blob.try(:caption) %>
+ <%= caption %>
+ <% else %>
+ <span class="attachment__name"><%= blob.filename %></span>
+ <span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
+ <% end %>
+ </figcaption>
+</figure>
diff --git a/actiontext/bin/test b/actiontext/bin/test
new file mode 100755
index 0000000000..c53377cc97
--- /dev/null
+++ b/actiontext/bin/test
@@ -0,0 +1,5 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+COMPONENT_ROOT = File.expand_path("..", __dir__)
+require_relative "../../tools/test"
diff --git a/actiontext/bin/webpack b/actiontext/bin/webpack
new file mode 100755
index 0000000000..ac2adfb6b9
--- /dev/null
+++ b/actiontext/bin/webpack
@@ -0,0 +1,29 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application 'webpack' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require "pathname"
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+bundle_binstub = File.expand_path("../bundle", __FILE__)
+
+if File.file?(bundle_binstub)
+ if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 150))
+ load(bundle_binstub)
+ else
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
+Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
+ end
+end
+
+require "rubygems"
+require "bundler/setup"
+
+load Gem.bin_path("webpacker", "webpack")
diff --git a/actiontext/bin/webpack-dev-server b/actiontext/bin/webpack-dev-server
new file mode 100755
index 0000000000..c1ac52d675
--- /dev/null
+++ b/actiontext/bin/webpack-dev-server
@@ -0,0 +1,29 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application 'webpack-dev-server' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require "pathname"
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+bundle_binstub = File.expand_path("../bundle", __FILE__)
+
+if File.file?(bundle_binstub)
+ if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 150))
+ load(bundle_binstub)
+ else
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
+Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
+ end
+end
+
+require "rubygems"
+require "bundler/setup"
+
+load Gem.bin_path("webpacker", "webpack-dev-server")
diff --git a/actiontext/db/migrate/201805281641_create_action_text_tables.rb b/actiontext/db/migrate/201805281641_create_action_text_tables.rb
new file mode 100644
index 0000000000..74c7a0ecb9
--- /dev/null
+++ b/actiontext/db/migrate/201805281641_create_action_text_tables.rb
@@ -0,0 +1,14 @@
+class CreateActionTextTables < ActiveRecord::Migration[6.0]
+ def change
+ create_table :action_text_rich_texts do |t|
+ t.string :name, null: false
+ t.text :body, limit: 16777215
+ t.references :record, null: false, polymorphic: true, index: false
+
+ t.datetime :created_at, null: false
+ t.datetime :updated_at, null: false
+
+ t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
+ end
+ end
+end
diff --git a/actiontext/lib/action_text.rb b/actiontext/lib/action_text.rb
new file mode 100644
index 0000000000..0dd6318f89
--- /dev/null
+++ b/actiontext/lib/action_text.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require "active_support"
+require "active_support/rails"
+
+require "nokogiri"
+
+module ActionText
+ extend ActiveSupport::Autoload
+
+ autoload :Attachable
+ autoload :AttachmentGallery
+ autoload :Attachment
+ autoload :Attribute
+ autoload :Content
+ autoload :Fragment
+ autoload :HtmlConversion
+ autoload :PlainTextConversion
+ autoload :Serialization
+ autoload :TrixAttachment
+
+ module Attachables
+ extend ActiveSupport::Autoload
+
+ autoload :ContentAttachment
+ autoload :MissingAttachable
+ autoload :RemoteImage
+ end
+
+ module Attachments
+ extend ActiveSupport::Autoload
+
+ autoload :Caching
+ autoload :Minification
+ autoload :TrixConversion
+ end
+end
diff --git a/actiontext/lib/action_text/attachable.rb b/actiontext/lib/action_text/attachable.rb
new file mode 100644
index 0000000000..38cd24aa8d
--- /dev/null
+++ b/actiontext/lib/action_text/attachable.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module ActionText
+ module Attachable
+ extend ActiveSupport::Concern
+
+ LOCATOR_NAME = "attachable"
+
+ class << self
+ def from_node(node)
+ if attachable = attachable_from_sgid(node["sgid"])
+ attachable
+ elsif attachable = ActionText::Attachables::ContentAttachment.from_node(node)
+ attachable
+ elsif attachable = ActionText::Attachables::RemoteImage.from_node(node)
+ attachable
+ else
+ ActionText::Attachables::MissingAttachable
+ end
+ end
+
+ def from_attachable_sgid(sgid, options = {})
+ method = sgid.is_a?(Array) ? :locate_many_signed : :locate_signed
+ record = GlobalID::Locator.public_send(method, sgid, options.merge(for: LOCATOR_NAME))
+ record || raise(ActiveRecord::RecordNotFound)
+ end
+
+ private
+ def attachable_from_sgid(sgid)
+ from_attachable_sgid(sgid)
+ rescue ActiveRecord::RecordNotFound
+ nil
+ end
+ end
+
+ class_methods do
+ def from_attachable_sgid(sgid)
+ ActionText::Attachable.from_attachable_sgid(sgid, only: self)
+ end
+ end
+
+ def attachable_sgid
+ to_sgid(expires_in: nil, for: LOCATOR_NAME).to_s
+ end
+
+ def attachable_content_type
+ try(:content_type) || "application/octet-stream"
+ end
+
+ def attachable_filename
+ filename.to_s if respond_to?(:filename)
+ end
+
+ def attachable_filesize
+ try(:byte_size) || try(:filesize)
+ end
+
+ def attachable_metadata
+ try(:metadata) || {}
+ end
+
+ def previewable_attachable?
+ false
+ end
+
+ def as_json(*)
+ super.merge(attachable_sgid: attachable_sgid)
+ end
+
+ def to_rich_text_attributes(attributes = {})
+ attributes.dup.tap do |attrs|
+ attrs[:sgid] = attachable_sgid
+ attrs[:content_type] = attachable_content_type
+ attrs[:previewable] = true if previewable_attachable?
+ attrs[:filename] = attachable_filename
+ attrs[:filesize] = attachable_filesize
+ attrs[:width] = attachable_metadata[:width]
+ attrs[:height] = attachable_metadata[:height]
+ end.compact
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/attachables/content_attachment.rb b/actiontext/lib/action_text/attachables/content_attachment.rb
new file mode 100644
index 0000000000..804f74713f
--- /dev/null
+++ b/actiontext/lib/action_text/attachables/content_attachment.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module ActionText
+ module Attachables
+ class ContentAttachment
+ include ActiveModel::Model
+
+ def self.from_node(node)
+ if node["content-type"]
+ if matches = node["content-type"].match(/vnd\.rubyonrails\.(.+)\.html/)
+ attachment = new(name: matches[1])
+ attachment if attachment.valid?
+ end
+ end
+ end
+
+ attr_accessor :name
+ validates_inclusion_of :name, in: %w( horizontal-rule )
+
+ def attachable_plain_text_representation(caption)
+ case name
+ when "horizontal-rule"
+ " ┄ "
+ else
+ " "
+ end
+ end
+
+ def to_partial_path
+ "action_text/attachables/content_attachment"
+ end
+
+ def to_trix_content_attachment_partial_path
+ "action_text/attachables/content_attachments/#{name.underscore}"
+ end
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/attachables/missing_attachable.rb b/actiontext/lib/action_text/attachables/missing_attachable.rb
new file mode 100644
index 0000000000..2f3bd40563
--- /dev/null
+++ b/actiontext/lib/action_text/attachables/missing_attachable.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module ActionText
+ module Attachables
+ module MissingAttachable
+ extend ActiveModel::Naming
+
+ def self.to_partial_path
+ "action_text/attachables/missing_attachable"
+ end
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/attachables/remote_image.rb b/actiontext/lib/action_text/attachables/remote_image.rb
new file mode 100644
index 0000000000..650b11862b
--- /dev/null
+++ b/actiontext/lib/action_text/attachables/remote_image.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module ActionText
+ module Attachables
+ class RemoteImage
+ extend ActiveModel::Naming
+
+ class << self
+ def from_node(node)
+ if node["url"] && content_type_is_image?(node["content-type"])
+ new(attributes_from_node(node))
+ end
+ end
+
+ private
+ def content_type_is_image?(content_type)
+ content_type.to_s =~ /^image(\/.+|$)/
+ end
+
+ def attributes_from_node(node)
+ { url: node["url"],
+ content_type: node["content-type"],
+ width: node["width"],
+ height: node["height"] }
+ end
+ end
+
+ attr_reader :url, :content_type, :width, :height
+
+ def initialize(attributes = {})
+ @url = attributes[:url]
+ @content_type = attributes[:content_type]
+ @width = attributes[:width]
+ @height = attributes[:height]
+ end
+
+ def attachable_plain_text_representation(caption)
+ "[#{caption || "Image"}]"
+ end
+
+ def to_partial_path
+ "action_text/attachables/remote_image"
+ end
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/attachment.rb b/actiontext/lib/action_text/attachment.rb
new file mode 100644
index 0000000000..e90a3e7d48
--- /dev/null
+++ b/actiontext/lib/action_text/attachment.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+module ActionText
+ class Attachment
+ include Attachments::TrixConversion, Attachments::Minification, Attachments::Caching
+
+ TAG_NAME = "action-text-attachment"
+ SELECTOR = TAG_NAME
+ ATTRIBUTES = %w( sgid content-type url href filename filesize width height previewable presentation caption )
+
+ class << self
+ def fragment_by_canonicalizing_attachments(content)
+ fragment_by_minifying_attachments(fragment_by_converting_trix_attachments(content))
+ end
+
+ def from_node(node, attachable = nil)
+ new(node, attachable || ActionText::Attachable.from_node(node))
+ end
+
+ def from_attachables(attachables)
+ Array(attachables).map { |attachable| from_attachable(attachable) }.compact
+ end
+
+ def from_attachable(attachable, attributes = {})
+ if node = node_from_attributes(attachable.to_rich_text_attributes(attributes))
+ new(node, attachable)
+ end
+ end
+
+ def from_attributes(attributes, attachable = nil)
+ if node = node_from_attributes(attributes)
+ from_node(node, attachable)
+ end
+ end
+
+ private
+ def node_from_attributes(attributes)
+ if attributes = process_attributes(attributes).presence
+ ActionText::HtmlConversion.create_element(TAG_NAME, attributes)
+ end
+ end
+
+ def process_attributes(attributes)
+ attributes.transform_keys { |key| key.to_s.underscore.dasherize }.slice(*ATTRIBUTES)
+ end
+ end
+
+ attr_reader :node, :attachable
+
+ delegate :to_param, to: :attachable
+ delegate_missing_to :attachable
+
+ def initialize(node, attachable)
+ @node = node
+ @attachable = attachable
+ end
+
+ def caption
+ node_attributes["caption"].presence
+ end
+
+ def full_attributes
+ node_attributes.merge(attachable_attributes).merge(sgid_attributes)
+ end
+
+ def with_full_attributes
+ self.class.from_attributes(full_attributes, attachable)
+ end
+
+ def to_plain_text
+ if respond_to?(:attachable_plain_text_representation)
+ attachable_plain_text_representation(caption)
+ else
+ caption.to_s
+ end
+ end
+
+ def to_html
+ HtmlConversion.node_to_html(node)
+ end
+
+ def to_s
+ to_html
+ end
+
+ def inspect
+ "#<#{self.class.name} attachable=#{attachable.inspect}>"
+ end
+
+ private
+ def node_attributes
+ @node_attributes ||= ATTRIBUTES.map { |name| [ name.underscore, node[name] ] }.to_h.compact
+ end
+
+ def attachable_attributes
+ @attachable_attributes ||= (attachable.try(:to_rich_text_attributes) || {}).stringify_keys
+ end
+
+ def sgid_attributes
+ @sgid_attributes ||= node_attributes.slice("sgid").presence || attachable_attributes.slice("sgid")
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/attachment_gallery.rb b/actiontext/lib/action_text/attachment_gallery.rb
new file mode 100644
index 0000000000..45afbff058
--- /dev/null
+++ b/actiontext/lib/action_text/attachment_gallery.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+module ActionText
+ class AttachmentGallery
+ include ActiveModel::Model
+
+ class << self
+ def fragment_by_canonicalizing_attachment_galleries(content)
+ fragment_by_replacing_attachment_gallery_nodes(content) do |node|
+ "<#{TAG_NAME}>#{node.inner_html}</#{TAG_NAME}>"
+ end
+ end
+
+ def fragment_by_replacing_attachment_gallery_nodes(content)
+ Fragment.wrap(content).update do |source|
+ find_attachment_gallery_nodes(source).each do |node|
+ node.replace(yield(node).to_s)
+ end
+ end
+ end
+
+ def find_attachment_gallery_nodes(content)
+ Fragment.wrap(content).find_all(SELECTOR).select do |node|
+ node.children.all? do |child|
+ if child.text?
+ child.text =~ /\A(\n|\ )*\z/
+ else
+ child.matches? ATTACHMENT_SELECTOR
+ end
+ end
+ end
+ end
+
+ def from_node(node)
+ new(node)
+ end
+ end
+
+ attr_reader :node
+
+ def initialize(node)
+ @node = node
+ end
+
+ def attachments
+ @attachments ||= node.css(ATTACHMENT_SELECTOR).map do |node|
+ ActionText::Attachment.from_node(node).with_full_attributes
+ end
+ end
+
+ def size
+ attachments.size
+ end
+
+ def inspect
+ "#<#{self.class.name} size=#{size.inspect}>"
+ end
+
+ TAG_NAME = "div"
+ ATTACHMENT_SELECTOR = "#{ActionText::Attachment::SELECTOR}[presentation=gallery]"
+ SELECTOR = "#{TAG_NAME}:has(#{ATTACHMENT_SELECTOR} + #{ATTACHMENT_SELECTOR})"
+
+ private_constant :TAG_NAME, :ATTACHMENT_SELECTOR, :SELECTOR
+ end
+end
diff --git a/actiontext/lib/action_text/attachments/caching.rb b/actiontext/lib/action_text/attachments/caching.rb
new file mode 100644
index 0000000000..7c727bfc26
--- /dev/null
+++ b/actiontext/lib/action_text/attachments/caching.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module ActionText
+ module Attachments
+ module Caching
+ def cache_key(*args)
+ [self.class.name, cache_digest, *attachable.cache_key(*args)].join("/")
+ end
+
+ private
+ def cache_digest
+ Digest::SHA256.hexdigest(node.to_s)
+ end
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/attachments/minification.rb b/actiontext/lib/action_text/attachments/minification.rb
new file mode 100644
index 0000000000..edc8f876d6
--- /dev/null
+++ b/actiontext/lib/action_text/attachments/minification.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module ActionText
+ module Attachments
+ module Minification
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def fragment_by_minifying_attachments(content)
+ Fragment.wrap(content).replace(ActionText::Attachment::SELECTOR) do |node|
+ node.tap { |n| n.inner_html = "" }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/attachments/trix_conversion.rb b/actiontext/lib/action_text/attachments/trix_conversion.rb
new file mode 100644
index 0000000000..24937d6c22
--- /dev/null
+++ b/actiontext/lib/action_text/attachments/trix_conversion.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module ActionText
+ module Attachments
+ module TrixConversion
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def fragment_by_converting_trix_attachments(content)
+ Fragment.wrap(content).replace(TrixAttachment::SELECTOR) do |node|
+ from_trix_attachment(TrixAttachment.new(node))
+ end
+ end
+
+ def from_trix_attachment(trix_attachment)
+ from_attributes(trix_attachment.attributes)
+ end
+ end
+
+ def to_trix_attachment(content = trix_attachment_content)
+ attributes = full_attributes.dup
+ attributes["content"] = content if content
+ TrixAttachment.from_attributes(attributes)
+ end
+
+ private
+ def trix_attachment_content
+ if partial_path = attachable.try(:to_trix_content_attachment_partial_path)
+ ActionText::Content.renderer.render(partial: partial_path, object: self, as: model_name.element)
+ end
+ end
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/attribute.rb b/actiontext/lib/action_text/attribute.rb
new file mode 100644
index 0000000000..f226dd21bd
--- /dev/null
+++ b/actiontext/lib/action_text/attribute.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module ActionText
+ module Attribute
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Provides access to a dependent RichText model that holds the body and attachments for a single named rich text attribute.
+ # This dependent attribute is lazily instantiated and will be auto-saved when it's been changed. Example:
+ #
+ # class Message < ActiveRecord::Base
+ # has_rich_text :content
+ # end
+ #
+ # message = Message.create!(content: "<h1>Funny times!</h1>")
+ # message.content.to_s # => "<h1>Funny times!</h1>"
+ # message.content.to_plain_text # => "Funny times!"
+ #
+ # The dependent RichText model will also automatically process attachments links as sent via the Trix-powered editor.
+ # These attachments are associated with the RichText model using Active Storage.
+ #
+ # If you wish to preload the dependent RichText model, you can use the named scope:
+ #
+ # Message.all.with_rich_text_content # Avoids N+1 queries when you just want the body, not the attachments.
+ # Message.all.with_rich_text_content_and_embeds # Avoids N+1 queries when you just want the body and attachments.
+ def has_rich_text(name)
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}
+ self.rich_text_#{name} ||= ActionText::RichText.new(name: "#{name}", record: self)
+ end
+
+ def #{name}=(body)
+ self.#{name}.body = body
+ end
+ CODE
+
+ has_one :"rich_text_#{name}", -> { where(name: name) }, class_name: "ActionText::RichText", as: :record, inverse_of: :record, dependent: :destroy
+
+ scope :"with_rich_text_#{name}", -> { includes("rich_text_#{name}") }
+ scope :"with_rich_text_#{name}_and_embeds", -> { includes("rich_text_#{name}": { embeds_attachments: :blob }) }
+
+ after_save do
+ public_send(name).save if public_send(name).changed?
+ end
+ end
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/content.rb b/actiontext/lib/action_text/content.rb
new file mode 100644
index 0000000000..16bc6fe031
--- /dev/null
+++ b/actiontext/lib/action_text/content.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+require "active_support/core_ext/module/attribute_accessors_per_thread"
+
+module ActionText
+ class Content
+ include Serialization
+
+ thread_cattr_accessor :renderer
+
+ attr_reader :fragment
+
+ delegate :blank?, :empty?, :html_safe, :present?, to: :to_html # Delegating to to_html to avoid including the layout
+
+ class << self
+ def fragment_by_canonicalizing_content(content)
+ fragment = ActionText::Attachment.fragment_by_canonicalizing_attachments(content)
+ fragment = ActionText::AttachmentGallery.fragment_by_canonicalizing_attachment_galleries(fragment)
+ fragment
+ end
+ end
+
+ def initialize(content = nil, options = {})
+ options.with_defaults! canonicalize: true
+
+ if options[:canonicalize]
+ @fragment = self.class.fragment_by_canonicalizing_content(content)
+ else
+ @fragment = ActionText::Fragment.wrap(content)
+ end
+ end
+
+ def links
+ @links ||= fragment.find_all("a[href]").map { |a| a["href"] }.uniq
+ end
+
+ def attachments
+ @attachments ||= attachment_nodes.map do |node|
+ attachment_for_node(node)
+ end
+ end
+
+ def attachment_galleries
+ @attachment_galleries ||= attachment_gallery_nodes.map do |node|
+ attachment_gallery_for_node(node)
+ end
+ end
+
+ def gallery_attachments
+ @gallery_attachments ||= attachment_galleries.flat_map(&:attachments)
+ end
+
+ def attachables
+ @attachables ||= attachment_nodes.map do |node|
+ ActionText::Attachable.from_node(node)
+ end
+ end
+
+ def append_attachables(attachables)
+ attachments = ActionText::Attachment.from_attachables(attachables)
+ self.class.new([self.to_s.presence, *attachments].compact.join("\n"))
+ end
+
+ def render_attachments(**options, &block)
+ content = fragment.replace(ActionText::Attachment::SELECTOR) do |node|
+ block.call(attachment_for_node(node, **options))
+ end
+ self.class.new(content, canonicalize: false)
+ end
+
+ def render_attachment_galleries(&block)
+ content = ActionText::AttachmentGallery.fragment_by_replacing_attachment_gallery_nodes(fragment) do |node|
+ block.call(attachment_gallery_for_node(node))
+ end
+ self.class.new(content, canonicalize: false)
+ end
+
+ def to_plain_text
+ render_attachments(with_full_attributes: false, &:to_plain_text).fragment.to_plain_text
+ end
+
+ def to_trix_html
+ render_attachments(&:to_trix_attachment).to_html
+ end
+
+ def to_html
+ fragment.to_html
+ end
+
+ def to_rendered_html_with_layout
+ renderer.render(partial: "action_text/content/layout", locals: { content: self })
+ end
+
+ def to_s
+ to_rendered_html_with_layout
+ end
+
+ def as_json(*)
+ to_html
+ end
+
+ def inspect
+ "#<#{self.class.name} #{to_s.truncate(25).inspect}>"
+ end
+
+ def ==(other)
+ if other.is_a?(self.class)
+ to_s == other.to_s
+ end
+ end
+
+ private
+ def attachment_nodes
+ @attachment_nodes ||= fragment.find_all(ActionText::Attachment::SELECTOR)
+ end
+
+ def attachment_gallery_nodes
+ @attachment_gallery_nodes ||= ActionText::AttachmentGallery.find_attachment_gallery_nodes(fragment)
+ end
+
+ def attachment_for_node(node, with_full_attributes: true)
+ attachment = ActionText::Attachment.from_node(node)
+ with_full_attributes ? attachment.with_full_attributes : attachment
+ end
+
+ def attachment_gallery_for_node(node)
+ ActionText::AttachmentGallery.from_node(node)
+ end
+ end
+end
+
+ActiveSupport.run_load_hooks :action_text_content, ActionText::Content
diff --git a/actiontext/lib/action_text/engine.rb b/actiontext/lib/action_text/engine.rb
new file mode 100644
index 0000000000..51ff5b575b
--- /dev/null
+++ b/actiontext/lib/action_text/engine.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require "rails"
+require "action_controller/railtie"
+require "active_record/railtie"
+require "active_storage/engine"
+
+require "action_text"
+
+module ActionText
+ class Engine < Rails::Engine
+ isolate_namespace ActionText
+ config.eager_load_namespaces << ActionText
+
+ initializer "action_text.attribute" do
+ ActiveSupport.on_load(:active_record) do
+ include ActionText::Attribute
+ end
+ end
+
+ initializer "action_text.attachable" do
+ ActiveSupport.on_load(:active_storage_blob) do
+ include ActionText::Attachable
+
+ def previewable_attachable?
+ representable?
+ end
+ end
+ end
+
+ initializer "action_text.helper" do
+ ActiveSupport.on_load(:action_controller_base) do
+ helper ActionText::Engine.helpers
+ end
+ end
+
+ initializer "action_text.renderer" do |app|
+ app.executor.to_run { ActionText::Content.renderer = ApplicationController.renderer }
+ app.executor.to_complete { ActionText::Content.renderer = ApplicationController.renderer }
+
+ ActiveSupport.on_load(:action_text_content) do
+ self.renderer = ApplicationController.renderer
+ end
+
+ ActiveSupport.on_load(:action_controller_base) do
+ before_action { ActionText::Content.renderer = ApplicationController.renderer.new(request.env) }
+ end
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/fragment.rb b/actiontext/lib/action_text/fragment.rb
new file mode 100644
index 0000000000..af276b2b26
--- /dev/null
+++ b/actiontext/lib/action_text/fragment.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module ActionText
+ class Fragment
+ class << self
+ def wrap(fragment_or_html)
+ case fragment_or_html
+ when self
+ fragment_or_html
+ when Nokogiri::HTML::DocumentFragment
+ new(fragment_or_html)
+ else
+ from_html(fragment_or_html)
+ end
+ end
+
+ def from_html(html)
+ new(ActionText::HtmlConversion.fragment_for_html(html.to_s.strip))
+ end
+ end
+
+ attr_reader :source
+
+ def initialize(source)
+ @source = source
+ end
+
+ def find_all(selector)
+ source.css(selector)
+ end
+
+ def update
+ yield source = self.source.clone
+ self.class.new(source)
+ end
+
+ def replace(selector)
+ update do |source|
+ source.css(selector).each do |node|
+ node.replace(yield(node).to_s)
+ end
+ end
+ end
+
+ def to_plain_text
+ @plain_text ||= PlainTextConversion.node_to_plain_text(source)
+ end
+
+ def to_html
+ @html ||= HtmlConversion.node_to_html(source)
+ end
+
+ def to_s
+ to_html
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/gem_version.rb b/actiontext/lib/action_text/gem_version.rb
new file mode 100644
index 0000000000..5404ac54e9
--- /dev/null
+++ b/actiontext/lib/action_text/gem_version.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module ActionText
+ # Returns the currently-loaded version of Action Text as a <tt>Gem::Version</tt>.
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 6
+ MINOR = 0
+ TINY = 0
+ PRE = "beta1"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/actiontext/lib/action_text/html_conversion.rb b/actiontext/lib/action_text/html_conversion.rb
new file mode 100644
index 0000000000..1e1062ea3f
--- /dev/null
+++ b/actiontext/lib/action_text/html_conversion.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module ActionText
+ module HtmlConversion
+ extend self
+
+ def node_to_html(node)
+ node.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_HTML)
+ end
+
+ def fragment_for_html(html)
+ document.fragment(html)
+ end
+
+ def create_element(tag_name, attributes = {})
+ document.create_element(tag_name, attributes)
+ end
+
+ private
+ def document
+ Nokogiri::HTML::Document.new.tap { |doc| doc.encoding = "UTF-8" }
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/plain_text_conversion.rb b/actiontext/lib/action_text/plain_text_conversion.rb
new file mode 100644
index 0000000000..0eb4e2e7da
--- /dev/null
+++ b/actiontext/lib/action_text/plain_text_conversion.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+module ActionText
+ module PlainTextConversion
+ extend self
+
+ def node_to_plain_text(node)
+ remove_trailing_newlines(plain_text_for_node(node))
+ end
+
+ private
+ def plain_text_for_node(node, index = 0)
+ if respond_to?(plain_text_method_for_node(node), true)
+ send(plain_text_method_for_node(node), node, index)
+ else
+ plain_text_for_node_children(node)
+ end
+ end
+
+ def plain_text_for_node_children(node)
+ node.children.each_with_index.map do |child, index|
+ plain_text_for_node(child, index)
+ end.compact.join("")
+ end
+
+ def plain_text_method_for_node(node)
+ :"plain_text_for_#{node.name}_node"
+ end
+
+ def plain_text_for_block(node, index = 0)
+ "#{remove_trailing_newlines(plain_text_for_node_children(node))}\n\n"
+ end
+
+ %i[ h1 p ul ol ].each do |element|
+ alias_method :"plain_text_for_#{element}_node", :plain_text_for_block
+ end
+
+ def plain_text_for_br_node(node, index)
+ "\n"
+ end
+
+ def plain_text_for_text_node(node, index)
+ remove_trailing_newlines(node.text)
+ end
+
+ def plain_text_for_div_node(node, index)
+ "#{remove_trailing_newlines(plain_text_for_node_children(node))}\n"
+ end
+
+ def plain_text_for_figcaption_node(node, index)
+ "[#{remove_trailing_newlines(plain_text_for_node_children(node))}]"
+ end
+
+ def plain_text_for_blockquote_node(node, index)
+ text = plain_text_for_block(node)
+ text.sub(/\A(\s*)(.+?)(\s*)\Z/m, '\1“\2”\3')
+ end
+
+ def plain_text_for_li_node(node, index)
+ bullet = bullet_for_li_node(node, index)
+ text = remove_trailing_newlines(plain_text_for_node_children(node))
+ "#{bullet} #{text}\n"
+ end
+
+ def remove_trailing_newlines(text)
+ text.chomp("")
+ end
+
+ def bullet_for_li_node(node, index)
+ if list_node_name_for_li_node(node) == "ol"
+ "#{index + 1}."
+ else
+ "•"
+ end
+ end
+
+ def list_node_name_for_li_node(node)
+ node.ancestors.lazy.map(&:name).grep(/^[uo]l$/).first
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/serialization.rb b/actiontext/lib/action_text/serialization.rb
new file mode 100644
index 0000000000..8ecf8c9157
--- /dev/null
+++ b/actiontext/lib/action_text/serialization.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module ActionText
+ module Serialization
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def load(content)
+ new(content) if content
+ end
+
+ def dump(content)
+ case content
+ when nil
+ nil
+ when self
+ content.to_html
+ else
+ new(content).to_html
+ end
+ end
+ end
+
+ # Marshal compatibility
+
+ class_methods do
+ alias_method :_load, :load
+ end
+
+ def _dump(*)
+ self.class.dump(self)
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/trix_attachment.rb b/actiontext/lib/action_text/trix_attachment.rb
new file mode 100644
index 0000000000..c16c1c090d
--- /dev/null
+++ b/actiontext/lib/action_text/trix_attachment.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+module ActionText
+ class TrixAttachment
+ TAG_NAME = "figure"
+ SELECTOR = "[data-trix-attachment]"
+
+ COMPOSED_ATTRIBUTES = %w( caption presentation )
+ ATTRIBUTES = %w( sgid contentType url href filename filesize width height previewable content ) + COMPOSED_ATTRIBUTES
+ ATTRIBUTE_TYPES = {
+ "previewable" => ->(value) { value.to_s == "true" },
+ "filesize" => ->(value) { Integer(value.to_s) rescue value },
+ "width" => ->(value) { Integer(value.to_s) rescue nil },
+ "height" => ->(value) { Integer(value.to_s) rescue nil },
+ :default => ->(value) { value.to_s }
+ }
+
+ class << self
+ def from_attributes(attributes)
+ attributes = process_attributes(attributes)
+
+ trix_attachment_attributes = attributes.except(*COMPOSED_ATTRIBUTES)
+ trix_attributes = attributes.slice(*COMPOSED_ATTRIBUTES)
+
+ node = ActionText::HtmlConversion.create_element(TAG_NAME)
+ node["data-trix-attachment"] = JSON.generate(trix_attachment_attributes)
+ node["data-trix-attributes"] = JSON.generate(trix_attributes) if trix_attributes.any?
+
+ new(node)
+ end
+
+ private
+ def process_attributes(attributes)
+ typecast_attribute_values(transform_attribute_keys(attributes))
+ end
+
+ def transform_attribute_keys(attributes)
+ attributes.transform_keys { |key| key.to_s.underscore.camelize(:lower) }
+ end
+
+ def typecast_attribute_values(attributes)
+ attributes.map do |key, value|
+ typecast = ATTRIBUTE_TYPES[key] || ATTRIBUTE_TYPES[:default]
+ [key, typecast.call(value)]
+ end.to_h
+ end
+ end
+
+ attr_reader :node
+
+ def initialize(node)
+ @node = node
+ end
+
+ def attributes
+ @attributes ||= attachment_attributes.merge(composed_attributes).slice(*ATTRIBUTES)
+ end
+
+ def to_html
+ ActionText::HtmlConversion.node_to_html(node)
+ end
+
+ def to_s
+ to_html
+ end
+
+ private
+ def attachment_attributes
+ read_json_object_attribute("data-trix-attachment")
+ end
+
+ def composed_attributes
+ read_json_object_attribute("data-trix-attributes")
+ end
+
+ def read_json_object_attribute(name)
+ read_json_attribute(name) || {}
+ end
+
+ def read_json_attribute(name)
+ if value = node[name]
+ begin
+ JSON.parse(value)
+ rescue => e
+ Rails.logger.error "[#{self.class.name}] Couldn't parse JSON #{value} from NODE #{node.inspect}"
+ Rails.logger.error "[#{self.class.name}] Failed with #{e.class}: #{e.backtrace}"
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/actiontext/lib/action_text/version.rb b/actiontext/lib/action_text/version.rb
new file mode 100644
index 0000000000..ed72859fa4
--- /dev/null
+++ b/actiontext/lib/action_text/version.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require_relative "gem_version"
+
+module ActionText
+ # Returns the currently-loaded version of Action Text as a <tt>Gem::Version</tt>.
+ def self.version
+ gem_version
+ end
+end
diff --git a/actiontext/lib/tasks/actiontext.rake b/actiontext/lib/tasks/actiontext.rake
new file mode 100644
index 0000000000..4f90e4930c
--- /dev/null
+++ b/actiontext/lib/tasks/actiontext.rake
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+namespace :action_text do
+ # Prevent migration installation task from showing up twice.
+ Rake::Task["install:migrations"].clear_comments
+
+ desc "Copy over the migration, stylesheet, and JavaScript files"
+ task install: %w( environment run_installer copy_migrations )
+
+ task :run_installer do
+ installer_template = File.expand_path("../templates/installer.rb", __dir__)
+ system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{installer_template}"
+ end
+
+ task :copy_migrations do
+ Rake::Task["active_storage:install:migrations"].invoke
+ Rake::Task["railties:install:migrations"].reenable # Otherwise you can't run 2 migration copy tasks in one invocation
+ Rake::Task["action_text:install:migrations"].invoke
+ end
+end
diff --git a/actiontext/lib/templates/actiontext.scss b/actiontext/lib/templates/actiontext.scss
new file mode 100644
index 0000000000..7cb26e74ac
--- /dev/null
+++ b/actiontext/lib/templates/actiontext.scss
@@ -0,0 +1,36 @@
+//
+// Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
+// the trix-editor content (whether displayed or under editing). Feel free to incorporate this
+// inclusion directly in any other asset bundle and remove this file.
+//
+//= require trix/dist/trix
+
+// We need to override trix.css’s image gallery styles to accommodate the
+// <action-text-attachment> element we wrap around attachments. Otherwise,
+// images in galleries will be squished by the max-width: 33%; rule.
+.trix-content {
+ .attachment-gallery {
+ > action-text-attachment,
+ > .attachment {
+ flex: 1 0 33%;
+ padding: 0 0.5em;
+ max-width: 33%;
+ }
+
+ &.attachment-gallery--2,
+ &.attachment-gallery--4 {
+ > action-text-attachment,
+ > .attachment {
+ flex-basis: 50%;
+ max-width: 50%;
+ }
+ }
+ }
+
+ action-text-attachment {
+ .attachment {
+ padding: 0 !important;
+ max-width: 100% !important;
+ }
+ }
+}
diff --git a/actiontext/lib/templates/fixtures.yml b/actiontext/lib/templates/fixtures.yml
new file mode 100644
index 0000000000..8b371ea604
--- /dev/null
+++ b/actiontext/lib/templates/fixtures.yml
@@ -0,0 +1,4 @@
+# one:
+# record: name_of_fixture (ClassOfFixture)
+# name: content
+# body: <p>In a <i>million</i> stars!</p>
diff --git a/actiontext/lib/templates/installer.rb b/actiontext/lib/templates/installer.rb
new file mode 100644
index 0000000000..990e41ca00
--- /dev/null
+++ b/actiontext/lib/templates/installer.rb
@@ -0,0 +1,32 @@
+require "pathname"
+require "json"
+
+APPLICATION_PACK_PATH = Pathname.new("app/javascript/packs/application.js")
+JS_PACKAGE_PATH = Pathname.new("#{__dir__}/../../package.json")
+
+JS_PACKAGE = JSON.load(JS_PACKAGE_PATH)
+JS_DEPENDENCIES = JS_PACKAGE["peerDependencies"].dup.merge \
+ JS_PACKAGE["name"] => "^#{JS_PACKAGE["version"]}"
+
+say "Copying actiontext.scss to app/assets/stylesheets"
+copy_file "#{__dir__}/actiontext.scss", "app/assets/stylesheets/actiontext.scss"
+
+say "Copying fixtures to test/fixtures/action_text/rich_texts.yml"
+copy_file "#{__dir__}/fixtures.yml", "test/fixtures/action_text/rich_texts.yml"
+
+say "Copying blob rendering partial to app/views/active_storage/blobs/_blob.html.erb"
+copy_file "#{__dir__}/../../app/views/active_storage/blobs/_blob.html.erb",
+ "app/views/active_storage/blobs/_blob.html.erb"
+
+say "Installing JavaScript dependencies"
+run "yarn add #{JS_DEPENDENCIES.map { |name, version| "#{name}@#{version}" }.join(" ")}"
+
+if APPLICATION_PACK_PATH.exist?
+ JS_DEPENDENCIES.keys.each do |name|
+ line = %[require("#{name}")]
+ unless APPLICATION_PACK_PATH.read.include? line
+ say "Adding #{name} to #{APPLICATION_PACK_PATH}"
+ append_to_file APPLICATION_PACK_PATH, "#{line}\n"
+ end
+ end
+end
diff --git a/actiontext/package.json b/actiontext/package.json
new file mode 100644
index 0000000000..a24026bc51
--- /dev/null
+++ b/actiontext/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "@rails/actiontext",
+ "version": "6.0.0-beta1",
+ "description": "Edit and display rich text in Rails applications",
+ "main": "app/javascript/actiontext/index.js",
+ "files": [
+ "app/javascript/actiontext/*.js"
+ ],
+ "homepage": "http://rubyonrails.org/",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/rails/rails.git"
+ },
+ "bugs": {
+ "url": "https://github.com/rails/rails/issues"
+ },
+ "author": "Basecamp, LLC",
+ "contributors": [
+ "Javan Makhmali <javan@javan.us>",
+ "Sam Stephenson <sstephenson@gmail.com>"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@rails/activestorage": "^6.0.0-alpha"
+ },
+ "peerDependencies": {
+ "trix": "^1.0.0"
+ }
+}
diff --git a/actiontext/test/dummy/.babelrc b/actiontext/test/dummy/.babelrc
new file mode 100644
index 0000000000..ded31c0d80
--- /dev/null
+++ b/actiontext/test/dummy/.babelrc
@@ -0,0 +1,18 @@
+{
+ "presets": [
+ ["env", {
+ "modules": false,
+ "targets": {
+ "browsers": "> 1%",
+ "uglify": true
+ },
+ "useBuiltIns": true
+ }]
+ ],
+
+ "plugins": [
+ "syntax-dynamic-import",
+ "transform-object-rest-spread",
+ ["transform-class-properties", { "spec": true }]
+ ]
+}
diff --git a/actiontext/test/dummy/.postcssrc.yml b/actiontext/test/dummy/.postcssrc.yml
new file mode 100644
index 0000000000..150dac3c6c
--- /dev/null
+++ b/actiontext/test/dummy/.postcssrc.yml
@@ -0,0 +1,3 @@
+plugins:
+ postcss-import: {}
+ postcss-cssnext: {}
diff --git a/actiontext/test/dummy/Rakefile b/actiontext/test/dummy/Rakefile
new file mode 100644
index 0000000000..e85f913914
--- /dev/null
+++ b/actiontext/test/dummy/Rakefile
@@ -0,0 +1,6 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require_relative 'config/application'
+
+Rails.application.load_tasks
diff --git a/actiontext/test/dummy/app/assets/config/manifest.js b/actiontext/test/dummy/app/assets/config/manifest.js
new file mode 100644
index 0000000000..b16e53d6d5
--- /dev/null
+++ b/actiontext/test/dummy/app/assets/config/manifest.js
@@ -0,0 +1,3 @@
+//= link_tree ../images
+//= link_directory ../javascripts .js
+//= link_directory ../stylesheets .css
diff --git a/actiontext/test/dummy/app/assets/images/.keep b/actiontext/test/dummy/app/assets/images/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actiontext/test/dummy/app/assets/images/.keep
diff --git a/actiontext/test/dummy/app/assets/stylesheets/application.css b/actiontext/test/dummy/app/assets/stylesheets/application.css
new file mode 100644
index 0000000000..e4b10eb997
--- /dev/null
+++ b/actiontext/test/dummy/app/assets/stylesheets/application.css
@@ -0,0 +1,16 @@
+/*
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
+ * listed below.
+ *
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
+ *
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
+ * files in this directory. Styles in this file should be added after the last require_* statement.
+ * It is generally better to create a new file per style scope.
+ *
+ *= require trix/dist/trix.css
+ *= require_tree .
+ *= require_self
+ */
diff --git a/actiontext/test/dummy/app/assets/stylesheets/messages.css b/actiontext/test/dummy/app/assets/stylesheets/messages.css
new file mode 100644
index 0000000000..afad32db02
--- /dev/null
+++ b/actiontext/test/dummy/app/assets/stylesheets/messages.css
@@ -0,0 +1,4 @@
+/*
+ Place all the styles related to the matching controller here.
+ They will automatically be included in application.css.
+*/
diff --git a/actiontext/test/dummy/app/assets/stylesheets/scaffold.css b/actiontext/test/dummy/app/assets/stylesheets/scaffold.css
new file mode 100644
index 0000000000..cd4f3de38d
--- /dev/null
+++ b/actiontext/test/dummy/app/assets/stylesheets/scaffold.css
@@ -0,0 +1,80 @@
+body {
+ background-color: #fff;
+ color: #333;
+ margin: 33px;
+}
+
+body, p, ol, ul, td {
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+}
+
+pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+}
+
+a {
+ color: #000;
+}
+
+a:visited {
+ color: #666;
+}
+
+a:hover {
+ color: #fff;
+ background-color: #000;
+}
+
+th {
+ padding-bottom: 5px;
+}
+
+td {
+ padding: 0 5px 7px;
+}
+
+div.field,
+div.actions {
+ margin-bottom: 10px;
+}
+
+#notice {
+ color: green;
+}
+
+.field_with_errors {
+ padding: 2px;
+ background-color: red;
+ display: table;
+}
+
+#error_explanation {
+ width: 450px;
+ border: 2px solid red;
+ padding: 7px 7px 0;
+ margin-bottom: 20px;
+ background-color: #f0f0f0;
+}
+
+#error_explanation h2 {
+ text-align: left;
+ font-weight: bold;
+ padding: 5px 5px 5px 15px;
+ font-size: 12px;
+ margin: -7px -7px 0;
+ background-color: #c00;
+ color: #fff;
+}
+
+#error_explanation ul li {
+ font-size: 12px;
+ list-style: square;
+}
+
+label {
+ display: block;
+}
diff --git a/actiontext/test/dummy/app/channels/application_cable/channel.rb b/actiontext/test/dummy/app/channels/application_cable/channel.rb
new file mode 100644
index 0000000000..d672697283
--- /dev/null
+++ b/actiontext/test/dummy/app/channels/application_cable/channel.rb
@@ -0,0 +1,4 @@
+module ApplicationCable
+ class Channel < ActionCable::Channel::Base
+ end
+end
diff --git a/actiontext/test/dummy/app/channels/application_cable/connection.rb b/actiontext/test/dummy/app/channels/application_cable/connection.rb
new file mode 100644
index 0000000000..0ff5442f47
--- /dev/null
+++ b/actiontext/test/dummy/app/channels/application_cable/connection.rb
@@ -0,0 +1,4 @@
+module ApplicationCable
+ class Connection < ActionCable::Connection::Base
+ end
+end
diff --git a/actiontext/test/dummy/app/controllers/application_controller.rb b/actiontext/test/dummy/app/controllers/application_controller.rb
new file mode 100644
index 0000000000..09705d12ab
--- /dev/null
+++ b/actiontext/test/dummy/app/controllers/application_controller.rb
@@ -0,0 +1,2 @@
+class ApplicationController < ActionController::Base
+end
diff --git a/actiontext/test/dummy/app/controllers/concerns/.keep b/actiontext/test/dummy/app/controllers/concerns/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actiontext/test/dummy/app/controllers/concerns/.keep
diff --git a/actiontext/test/dummy/app/controllers/messages_controller.rb b/actiontext/test/dummy/app/controllers/messages_controller.rb
new file mode 100644
index 0000000000..81c79a2d1e
--- /dev/null
+++ b/actiontext/test/dummy/app/controllers/messages_controller.rb
@@ -0,0 +1,58 @@
+class MessagesController < ApplicationController
+ before_action :set_message, only: [:show, :edit, :update, :destroy]
+
+ # GET /messages
+ def index
+ @messages = Message.all
+ end
+
+ # GET /messages/1
+ def show
+ end
+
+ # GET /messages/new
+ def new
+ @message = Message.new
+ end
+
+ # GET /messages/1/edit
+ def edit
+ end
+
+ # POST /messages
+ def create
+ @message = Message.new(message_params)
+
+ if @message.save
+ redirect_to @message, notice: 'Message was successfully created.'
+ else
+ render :new
+ end
+ end
+
+ # PATCH/PUT /messages/1
+ def update
+ if @message.update(message_params)
+ redirect_to @message, notice: 'Message was successfully updated.'
+ else
+ render :edit
+ end
+ end
+
+ # DELETE /messages/1
+ def destroy
+ @message.destroy
+ redirect_to messages_url, notice: 'Message was successfully destroyed.'
+ end
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_message
+ @message = Message.find(params[:id])
+ end
+
+ # Only allow a trusted parameter "white list" through.
+ def message_params
+ params.require(:message).permit(:subject, :content)
+ end
+end
diff --git a/actiontext/test/dummy/app/helpers/application_helper.rb b/actiontext/test/dummy/app/helpers/application_helper.rb
new file mode 100644
index 0000000000..de6be7945c
--- /dev/null
+++ b/actiontext/test/dummy/app/helpers/application_helper.rb
@@ -0,0 +1,2 @@
+module ApplicationHelper
+end
diff --git a/actiontext/test/dummy/app/helpers/messages_helper.rb b/actiontext/test/dummy/app/helpers/messages_helper.rb
new file mode 100644
index 0000000000..f1bca9f6ca
--- /dev/null
+++ b/actiontext/test/dummy/app/helpers/messages_helper.rb
@@ -0,0 +1,2 @@
+module MessagesHelper
+end
diff --git a/actiontext/test/dummy/app/javascript/packs/application.js b/actiontext/test/dummy/app/javascript/packs/application.js
new file mode 100644
index 0000000000..13ac17ed58
--- /dev/null
+++ b/actiontext/test/dummy/app/javascript/packs/application.js
@@ -0,0 +1 @@
+import "@rails/actiontext"
diff --git a/actiontext/test/dummy/app/jobs/application_job.rb b/actiontext/test/dummy/app/jobs/application_job.rb
new file mode 100644
index 0000000000..a009ace51c
--- /dev/null
+++ b/actiontext/test/dummy/app/jobs/application_job.rb
@@ -0,0 +1,2 @@
+class ApplicationJob < ActiveJob::Base
+end
diff --git a/actiontext/test/dummy/app/mailers/application_mailer.rb b/actiontext/test/dummy/app/mailers/application_mailer.rb
new file mode 100644
index 0000000000..286b2239d1
--- /dev/null
+++ b/actiontext/test/dummy/app/mailers/application_mailer.rb
@@ -0,0 +1,4 @@
+class ApplicationMailer < ActionMailer::Base
+ default from: 'from@example.com'
+ layout 'mailer'
+end
diff --git a/actiontext/test/dummy/app/models/application_record.rb b/actiontext/test/dummy/app/models/application_record.rb
new file mode 100644
index 0000000000..10a4cba84d
--- /dev/null
+++ b/actiontext/test/dummy/app/models/application_record.rb
@@ -0,0 +1,3 @@
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+end
diff --git a/actiontext/test/dummy/app/models/concerns/.keep b/actiontext/test/dummy/app/models/concerns/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actiontext/test/dummy/app/models/concerns/.keep
diff --git a/actiontext/test/dummy/app/models/message.rb b/actiontext/test/dummy/app/models/message.rb
new file mode 100644
index 0000000000..9ea4dbfe78
--- /dev/null
+++ b/actiontext/test/dummy/app/models/message.rb
@@ -0,0 +1,4 @@
+class Message < ApplicationRecord
+ has_rich_text :content
+ has_rich_text :body
+end
diff --git a/actiontext/test/dummy/app/models/person.rb b/actiontext/test/dummy/app/models/person.rb
new file mode 100644
index 0000000000..0ded356d5b
--- /dev/null
+++ b/actiontext/test/dummy/app/models/person.rb
@@ -0,0 +1,7 @@
+class Person < ApplicationRecord
+ include ActionText::Attachable
+
+ def to_trix_content_attachment_partial_path
+ "people/trix_content_attachment"
+ end
+end
diff --git a/actiontext/test/dummy/app/views/layouts/application.html.erb b/actiontext/test/dummy/app/views/layouts/application.html.erb
new file mode 100644
index 0000000000..f221f512b7
--- /dev/null
+++ b/actiontext/test/dummy/app/views/layouts/application.html.erb
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Dummy</title>
+ <%= csrf_meta_tags %>
+
+ <%= stylesheet_link_tag 'application', media: 'all' %>
+ <%= javascript_pack_tag 'application' %>
+ </head>
+
+ <body>
+ <%= yield %>
+ </body>
+</html>
diff --git a/actiontext/test/dummy/app/views/layouts/mailer.html.erb b/actiontext/test/dummy/app/views/layouts/mailer.html.erb
new file mode 100644
index 0000000000..cbd34d2e9d
--- /dev/null
+++ b/actiontext/test/dummy/app/views/layouts/mailer.html.erb
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <style>
+ /* Email styles need to be inline */
+ </style>
+ </head>
+
+ <body>
+ <%= yield %>
+ </body>
+</html>
diff --git a/actiontext/test/dummy/app/views/layouts/mailer.text.erb b/actiontext/test/dummy/app/views/layouts/mailer.text.erb
new file mode 100644
index 0000000000..37f0bddbd7
--- /dev/null
+++ b/actiontext/test/dummy/app/views/layouts/mailer.text.erb
@@ -0,0 +1 @@
+<%= yield %>
diff --git a/actiontext/test/dummy/app/views/messages/_form.html.erb b/actiontext/test/dummy/app/views/messages/_form.html.erb
new file mode 100644
index 0000000000..3b8a174884
--- /dev/null
+++ b/actiontext/test/dummy/app/views/messages/_form.html.erb
@@ -0,0 +1,27 @@
+<%= form_with(model: message, local: true) do |form| %>
+ <% if message.errors.any? %>
+ <div id="error_explanation">
+ <h2><%= pluralize(message.errors.count, "error") %> prohibited this message from being saved:</h2>
+
+ <ul>
+ <% message.errors.full_messages.each do |message| %>
+ <li><%= message %></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+
+ <div class="field">
+ <%= form.label :subject %>
+ <%= form.text_field :subject %>
+ </div>
+
+ <div class="field">
+ <%= form.label :content %>
+ <%= form.rich_text_area :content, class: "trix-content" %>
+ </div>
+
+ <div class="actions">
+ <%= form.submit %>
+ </div>
+<% end %>
diff --git a/actiontext/test/dummy/app/views/messages/edit.html.erb b/actiontext/test/dummy/app/views/messages/edit.html.erb
new file mode 100644
index 0000000000..90ad68c788
--- /dev/null
+++ b/actiontext/test/dummy/app/views/messages/edit.html.erb
@@ -0,0 +1,6 @@
+<h1>Editing Message</h1>
+
+<%= render 'form', message: @message %>
+
+<%= link_to 'Show', @message %> |
+<%= link_to 'Back', messages_path %>
diff --git a/actiontext/test/dummy/app/views/messages/index.html.erb b/actiontext/test/dummy/app/views/messages/index.html.erb
new file mode 100644
index 0000000000..a8c97468c6
--- /dev/null
+++ b/actiontext/test/dummy/app/views/messages/index.html.erb
@@ -0,0 +1,29 @@
+<p id="notice"><%= notice %></p>
+
+<h1>Messages</h1>
+
+<table>
+ <thead>
+ <tr>
+ <th>Subject</th>
+ <th>Content</th>
+ <th colspan="3"></th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @messages.each do |message| %>
+ <tr>
+ <td><%= message.subject %></td>
+ <td><%= message.content %></td>
+ <td><%= link_to 'Show', message %></td>
+ <td><%= link_to 'Edit', edit_message_path(message) %></td>
+ <td><%= link_to 'Destroy', message, method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ </tr>
+ <% end %>
+ </tbody>
+</table>
+
+<br>
+
+<%= link_to 'New Message', new_message_path %>
diff --git a/actiontext/test/dummy/app/views/messages/new.html.erb b/actiontext/test/dummy/app/views/messages/new.html.erb
new file mode 100644
index 0000000000..6cbd3b8ffe
--- /dev/null
+++ b/actiontext/test/dummy/app/views/messages/new.html.erb
@@ -0,0 +1,5 @@
+<h1>New Message</h1>
+
+<%= render 'form', message: @message %>
+
+<%= link_to 'Back', messages_path %>
diff --git a/actiontext/test/dummy/app/views/messages/show.html.erb b/actiontext/test/dummy/app/views/messages/show.html.erb
new file mode 100644
index 0000000000..25fad1efba
--- /dev/null
+++ b/actiontext/test/dummy/app/views/messages/show.html.erb
@@ -0,0 +1,13 @@
+<p id="notice"><%= notice %></p>
+
+<p>
+ <strong>Subject:</strong>
+ <%= @message.subject %>
+</p>
+
+<div class="trix-content">
+ <%= @message.content.html_safe %>
+</div>
+
+<%= link_to 'Edit', edit_message_path(@message) %> |
+<%= link_to 'Back', messages_path %>
diff --git a/actiontext/test/dummy/app/views/people/_trix_content_attachment.html.erb b/actiontext/test/dummy/app/views/people/_trix_content_attachment.html.erb
new file mode 100644
index 0000000000..7db2334126
--- /dev/null
+++ b/actiontext/test/dummy/app/views/people/_trix_content_attachment.html.erb
@@ -0,0 +1,3 @@
+<span class="mentionable-person" gid="<%= person.to_gid %>">
+ <%= person.name %>
+</span>
diff --git a/actiontext/test/dummy/bin/bundle b/actiontext/test/dummy/bin/bundle
new file mode 100755
index 0000000000..f19acf5b5c
--- /dev/null
+++ b/actiontext/test/dummy/bin/bundle
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
+load Gem.bin_path('bundler', 'bundle')
diff --git a/actiontext/test/dummy/bin/rails b/actiontext/test/dummy/bin/rails
new file mode 100755
index 0000000000..0739660237
--- /dev/null
+++ b/actiontext/test/dummy/bin/rails
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+APP_PATH = File.expand_path('../config/application', __dir__)
+require_relative '../config/boot'
+require 'rails/commands'
diff --git a/actiontext/test/dummy/bin/rake b/actiontext/test/dummy/bin/rake
new file mode 100755
index 0000000000..17240489f6
--- /dev/null
+++ b/actiontext/test/dummy/bin/rake
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+require_relative '../config/boot'
+require 'rake'
+Rake.application.run
diff --git a/actiontext/test/dummy/bin/setup b/actiontext/test/dummy/bin/setup
new file mode 100755
index 0000000000..94fd4d7977
--- /dev/null
+++ b/actiontext/test/dummy/bin/setup
@@ -0,0 +1,36 @@
+#!/usr/bin/env ruby
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = File.expand_path('..', __dir__)
+
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
+chdir APP_ROOT do
+ # This script is a starting point to setup your application.
+ # Add necessary setup steps to this file.
+
+ puts '== Installing dependencies =='
+ system! 'gem install bundler --conservative'
+ system('bundle check') || system!('bundle install')
+
+ # Install JavaScript dependencies if using Yarn
+ # system('bin/yarn')
+
+ # puts "\n== Copying sample files =="
+ # unless File.exist?('config/database.yml')
+ # cp 'config/database.yml.sample', 'config/database.yml'
+ # end
+
+ puts "\n== Preparing database =="
+ system! 'bin/rails db:setup'
+
+ puts "\n== Removing old logs and tempfiles =="
+ system! 'bin/rails log:clear tmp:clear'
+
+ puts "\n== Restarting application server =="
+ system! 'bin/rails restart'
+end
diff --git a/actiontext/test/dummy/bin/update b/actiontext/test/dummy/bin/update
new file mode 100755
index 0000000000..58bfaed518
--- /dev/null
+++ b/actiontext/test/dummy/bin/update
@@ -0,0 +1,31 @@
+#!/usr/bin/env ruby
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = File.expand_path('..', __dir__)
+
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
+chdir APP_ROOT do
+ # This script is a way to update your development environment automatically.
+ # Add necessary update steps to this file.
+
+ puts '== Installing dependencies =='
+ system! 'gem install bundler --conservative'
+ system('bundle check') || system!('bundle install')
+
+ # Install JavaScript dependencies if using Yarn
+ # system('bin/yarn')
+
+ puts "\n== Updating database =="
+ system! 'bin/rails db:migrate'
+
+ puts "\n== Removing old logs and tempfiles =="
+ system! 'bin/rails log:clear tmp:clear'
+
+ puts "\n== Restarting application server =="
+ system! 'bin/rails restart'
+end
diff --git a/actiontext/test/dummy/bin/yarn b/actiontext/test/dummy/bin/yarn
new file mode 100755
index 0000000000..460dd565b4
--- /dev/null
+++ b/actiontext/test/dummy/bin/yarn
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+APP_ROOT = File.expand_path('..', __dir__)
+Dir.chdir(APP_ROOT) do
+ begin
+ exec "yarnpkg", *ARGV
+ rescue Errno::ENOENT
+ $stderr.puts "Yarn executable was not detected in the system."
+ $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
+ exit 1
+ end
+end
diff --git a/actiontext/test/dummy/config.ru b/actiontext/test/dummy/config.ru
new file mode 100644
index 0000000000..f7ba0b527b
--- /dev/null
+++ b/actiontext/test/dummy/config.ru
@@ -0,0 +1,5 @@
+# This file is used by Rack-based servers to start the application.
+
+require_relative 'config/environment'
+
+run Rails.application
diff --git a/actiontext/test/dummy/config/application.rb b/actiontext/test/dummy/config/application.rb
new file mode 100644
index 0000000000..79eeb6ba0e
--- /dev/null
+++ b/actiontext/test/dummy/config/application.rb
@@ -0,0 +1,19 @@
+require_relative 'boot'
+
+require 'rails/all'
+
+Bundler.require(*Rails.groups)
+require "action_text"
+
+module Dummy
+ class Application < Rails::Application
+ # Initialize configuration defaults for originally generated Rails version.
+ config.load_defaults 6.0
+
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration can go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded after loading
+ # the framework and any gems in your application.
+ end
+end
+
diff --git a/actiontext/test/dummy/config/boot.rb b/actiontext/test/dummy/config/boot.rb
new file mode 100644
index 0000000000..c9aef85d40
--- /dev/null
+++ b/actiontext/test/dummy/config/boot.rb
@@ -0,0 +1,5 @@
+# Set up gems listed in the Gemfile.
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
+
+require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
+$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
diff --git a/actiontext/test/dummy/config/cable.yml b/actiontext/test/dummy/config/cable.yml
new file mode 100644
index 0000000000..1cd0f8363e
--- /dev/null
+++ b/actiontext/test/dummy/config/cable.yml
@@ -0,0 +1,10 @@
+development:
+ adapter: async
+
+test:
+ adapter: async
+
+production:
+ adapter: redis
+ url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
+ channel_prefix: dummy_production
diff --git a/actiontext/test/dummy/config/database.yml b/actiontext/test/dummy/config/database.yml
new file mode 100644
index 0000000000..0d02f24980
--- /dev/null
+++ b/actiontext/test/dummy/config/database.yml
@@ -0,0 +1,25 @@
+# SQLite version 3.x
+# gem install sqlite3
+#
+# Ensure the SQLite 3 gem is defined in your Gemfile
+# gem 'sqlite3'
+#
+default: &default
+ adapter: sqlite3
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+ timeout: 5000
+
+development:
+ <<: *default
+ database: db/development.sqlite3
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ <<: *default
+ database: db/test.sqlite3
+
+production:
+ <<: *default
+ database: db/production.sqlite3
diff --git a/actiontext/test/dummy/config/environment.rb b/actiontext/test/dummy/config/environment.rb
new file mode 100644
index 0000000000..426333bb46
--- /dev/null
+++ b/actiontext/test/dummy/config/environment.rb
@@ -0,0 +1,5 @@
+# Load the Rails application.
+require_relative 'application'
+
+# Initialize the Rails application.
+Rails.application.initialize!
diff --git a/actiontext/test/dummy/config/environments/development.rb b/actiontext/test/dummy/config/environments/development.rb
new file mode 100644
index 0000000000..f09b9a00c4
--- /dev/null
+++ b/actiontext/test/dummy/config/environments/development.rb
@@ -0,0 +1,63 @@
+Rails.application.configure do
+ # Verifies that versions and hashed value of the package contents in the project's package.json
+ # config.webpacker.check_yarn_integrity = true
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for development
+ # since you don't have to restart the web server when you make code changes.
+ config.cache_classes = false
+
+ # Do not eager load code on boot.
+ config.eager_load = false
+
+ # Show full error reports.
+ config.consider_all_requests_local = true
+
+ # Enable/disable caching. By default caching is disabled.
+ # Run rails dev:cache to toggle caching.
+ if Rails.root.join('tmp', 'caching-dev.txt').exist?
+ config.action_controller.perform_caching = true
+
+ config.cache_store = :memory_store
+ config.public_file_server.headers = {
+ 'Cache-Control' => "public, max-age=#{2.days.to_i}"
+ }
+ else
+ config.action_controller.perform_caching = false
+
+ config.cache_store = :null_store
+ end
+
+ # Store uploaded files on the local file system (see config/storage.yml for options)
+ config.active_storage.service = :local
+
+ # Don't care if the mailer can't send.
+ config.action_mailer.raise_delivery_errors = false
+
+ config.action_mailer.perform_caching = false
+
+ # Print deprecation notices to the Rails logger.
+ config.active_support.deprecation = :log
+
+ # Raise an error on page load if there are pending migrations.
+ config.active_record.migration_error = :page_load
+
+ # Highlight code that triggered database queries in logs.
+ config.active_record.verbose_query_logs = true
+
+ # Debug mode disables concatenation and preprocessing of assets.
+ # This option may cause significant delays in view rendering with a large
+ # number of complex assets.
+ config.assets.debug = true
+
+ # Suppress logger output for asset requests.
+ config.assets.quiet = true
+
+ # Raises error for missing translations
+ # config.action_view.raise_on_missing_translations = true
+
+ # Use an evented file watcher to asynchronously detect changes in source code,
+ # routes, locales, etc. This feature depends on the listen gem.
+ # config.file_watcher = ActiveSupport::EventedFileUpdateChecker
+end
diff --git a/actiontext/test/dummy/config/environments/production.rb b/actiontext/test/dummy/config/environments/production.rb
new file mode 100644
index 0000000000..2aaa79f620
--- /dev/null
+++ b/actiontext/test/dummy/config/environments/production.rb
@@ -0,0 +1,96 @@
+Rails.application.configure do
+ # Verifies that versions and hashed value of the package contents in the project's package.json
+ config.webpacker.check_yarn_integrity = false
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # Code is not reloaded between requests.
+ config.cache_classes = true
+
+ # Eager load code on boot. This eager loads most of Rails and
+ # your application in memory, allowing both threaded web servers
+ # and those relying on copy on write to perform better.
+ # Rake tasks automatically ignore this option for performance.
+ config.eager_load = true
+
+ # Full error reports are disabled and caching is turned on.
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
+ # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
+ # config.require_master_key = true
+
+ # Disable serving static files from the `/public` folder by default since
+ # Apache or NGINX already handles this.
+ config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
+
+ # Compress JavaScripts and CSS.
+ config.assets.js_compressor = :uglifier
+ # config.assets.css_compressor = :sass
+
+ # Do not fallback to assets pipeline if a precompiled asset is missed.
+ config.assets.compile = false
+
+ # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
+
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
+ # config.action_controller.asset_host = 'http://assets.example.com'
+
+ # Specifies the header that your server uses for sending files.
+ # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
+
+ # Store uploaded files on the local file system (see config/storage.yml for options)
+ config.active_storage.service = :local
+
+ # Mount Action Cable outside main process or domain
+ # config.action_cable.mount_path = nil
+ # config.action_cable.url = 'wss://example.com/cable'
+ # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
+
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+ # config.force_ssl = true
+
+ # Use the lowest log level to ensure availability of diagnostic information
+ # when problems arise.
+ config.log_level = :debug
+
+ # Prepend all log lines with the following tags.
+ config.log_tags = [ :request_id ]
+
+ # Use a different cache store in production.
+ # config.cache_store = :mem_cache_store
+
+ # Use a real queuing backend for Active Job (and separate queues per environment)
+ # config.active_job.queue_adapter = :resque
+ # config.active_job.queue_name_prefix = "dummy_#{Rails.env}"
+
+ config.action_mailer.perform_caching = false
+
+ # Ignore bad email addresses and do not raise email delivery errors.
+ # Set this to true and configure the email server for immediate delivery to raise delivery errors.
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation cannot be found).
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners.
+ config.active_support.deprecation = :notify
+
+ # Use default logging formatter so that PID and timestamp are not suppressed.
+ config.log_formatter = ::Logger::Formatter.new
+
+ # Use a different logger for distributed setups.
+ # require 'syslog/logger'
+ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
+
+ if ENV["RAILS_LOG_TO_STDOUT"].present?
+ logger = ActiveSupport::Logger.new(STDOUT)
+ logger.formatter = config.log_formatter
+ config.logger = ActiveSupport::TaggedLogging.new(logger)
+ end
+
+ # Do not dump schema after migrations.
+ config.active_record.dump_schema_after_migration = false
+end
diff --git a/actiontext/test/dummy/config/environments/test.rb b/actiontext/test/dummy/config/environments/test.rb
new file mode 100644
index 0000000000..0a38fd3ce9
--- /dev/null
+++ b/actiontext/test/dummy/config/environments/test.rb
@@ -0,0 +1,46 @@
+Rails.application.configure do
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Do not eager load code on boot. This avoids loading your whole application
+ # just for the purpose of running a single test. If you are using a tool that
+ # preloads Rails for running tests, you may have to set it to true.
+ config.eager_load = false
+
+ # Configure public file server for tests with Cache-Control for performance.
+ config.public_file_server.enabled = true
+ config.public_file_server.headers = {
+ 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
+ }
+
+ # Show full error reports and disable caching.
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates.
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment.
+ config.action_controller.allow_forgery_protection = false
+
+ # Store uploaded files on the local file system in a temporary directory
+ config.active_storage.service = :test
+
+ config.action_mailer.perform_caching = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Print deprecation notices to the stderr.
+ config.active_support.deprecation = :stderr
+
+ # Raises error for missing translations
+ # config.action_view.raise_on_missing_translations = true
+end
diff --git a/actiontext/test/dummy/config/initializers/application_controller_renderer.rb b/actiontext/test/dummy/config/initializers/application_controller_renderer.rb
new file mode 100644
index 0000000000..89d2efab2b
--- /dev/null
+++ b/actiontext/test/dummy/config/initializers/application_controller_renderer.rb
@@ -0,0 +1,8 @@
+# Be sure to restart your server when you modify this file.
+
+# ActiveSupport::Reloader.to_prepare do
+# ApplicationController.renderer.defaults.merge!(
+# http_host: 'example.org',
+# https: false
+# )
+# end
diff --git a/actiontext/test/dummy/config/initializers/assets.rb b/actiontext/test/dummy/config/initializers/assets.rb
new file mode 100644
index 0000000000..4b828e80cb
--- /dev/null
+++ b/actiontext/test/dummy/config/initializers/assets.rb
@@ -0,0 +1,14 @@
+# Be sure to restart your server when you modify this file.
+
+# Version of your assets, change this if you want to expire all your assets.
+Rails.application.config.assets.version = '1.0'
+
+# Add additional assets to the asset load path.
+# Rails.application.config.assets.paths << Emoji.images_path
+# Add Yarn node_modules folder to the asset load path.
+Rails.application.config.assets.paths << Rails.root.join('node_modules')
+
+# Precompile additional assets.
+# application.js, application.css, and all non-JS/CSS in the app/assets
+# folder are already added.
+# Rails.application.config.assets.precompile += %w( admin.js admin.css )
diff --git a/actiontext/test/dummy/config/initializers/backtrace_silencers.rb b/actiontext/test/dummy/config/initializers/backtrace_silencers.rb
new file mode 100644
index 0000000000..59385cdf37
--- /dev/null
+++ b/actiontext/test/dummy/config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
diff --git a/actiontext/test/dummy/config/initializers/content_security_policy.rb b/actiontext/test/dummy/config/initializers/content_security_policy.rb
new file mode 100644
index 0000000000..edde7f42b8
--- /dev/null
+++ b/actiontext/test/dummy/config/initializers/content_security_policy.rb
@@ -0,0 +1,22 @@
+# Be sure to restart your server when you modify this file.
+
+# Define an application-wide content security policy
+# For further information see the following documentation
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
+
+# Rails.application.config.content_security_policy do |policy|
+# policy.default_src :self, :https
+# policy.font_src :self, :https, :data
+# policy.img_src :self, :https, :data
+# policy.object_src :none
+# policy.script_src :self, :https
+# policy.style_src :self, :https, :unsafe_inline
+
+# # Specify URI for violation reports
+# # policy.report_uri "/csp-violation-report-endpoint"
+# end
+
+# Report CSP violations to a specified URI
+# For further information see the following documentation:
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
+# Rails.application.config.content_security_policy_report_only = true
diff --git a/actiontext/test/dummy/config/initializers/cookies_serializer.rb b/actiontext/test/dummy/config/initializers/cookies_serializer.rb
new file mode 100644
index 0000000000..5a6a32d371
--- /dev/null
+++ b/actiontext/test/dummy/config/initializers/cookies_serializer.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Specify a serializer for the signed and encrypted cookie jars.
+# Valid options are :json, :marshal, and :hybrid.
+Rails.application.config.action_dispatch.cookies_serializer = :json
diff --git a/actiontext/test/dummy/config/initializers/filter_parameter_logging.rb b/actiontext/test/dummy/config/initializers/filter_parameter_logging.rb
new file mode 100644
index 0000000000..4a994e1e7b
--- /dev/null
+++ b/actiontext/test/dummy/config/initializers/filter_parameter_logging.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Configure sensitive parameters which will be filtered from the log file.
+Rails.application.config.filter_parameters += [:password]
diff --git a/actiontext/test/dummy/config/initializers/inflections.rb b/actiontext/test/dummy/config/initializers/inflections.rb
new file mode 100644
index 0000000000..ac033bf9dc
--- /dev/null
+++ b/actiontext/test/dummy/config/initializers/inflections.rb
@@ -0,0 +1,16 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format. Inflections
+# are locale specific, and you may define rules for as many different
+# locales as you wish. All of these examples are active by default:
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
+
+# These inflection rules are supported but not enabled by default:
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
+# inflect.acronym 'RESTful'
+# end
diff --git a/actiontext/test/dummy/config/initializers/mime_types.rb b/actiontext/test/dummy/config/initializers/mime_types.rb
new file mode 100644
index 0000000000..dc1899682b
--- /dev/null
+++ b/actiontext/test/dummy/config/initializers/mime_types.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
diff --git a/actiontext/test/dummy/config/initializers/wrap_parameters.rb b/actiontext/test/dummy/config/initializers/wrap_parameters.rb
new file mode 100644
index 0000000000..bbfc3961bf
--- /dev/null
+++ b/actiontext/test/dummy/config/initializers/wrap_parameters.rb
@@ -0,0 +1,14 @@
+# Be sure to restart your server when you modify this file.
+
+# This file contains settings for ActionController::ParamsWrapper which
+# is enabled by default.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters format: [:json]
+end
+
+# To enable root element in JSON for ActiveRecord objects.
+# ActiveSupport.on_load(:active_record) do
+# self.include_root_in_json = true
+# end
diff --git a/actiontext/test/dummy/config/locales/en.yml b/actiontext/test/dummy/config/locales/en.yml
new file mode 100644
index 0000000000..decc5a8573
--- /dev/null
+++ b/actiontext/test/dummy/config/locales/en.yml
@@ -0,0 +1,33 @@
+# Files in the config/locales directory are used for internationalization
+# and are automatically loaded by Rails. If you want to use locales other
+# than English, add the necessary files in this directory.
+#
+# To use the locales, use `I18n.t`:
+#
+# I18n.t 'hello'
+#
+# In views, this is aliased to just `t`:
+#
+# <%= t('hello') %>
+#
+# To use a different locale, set it with `I18n.locale`:
+#
+# I18n.locale = :es
+#
+# This would use the information in config/locales/es.yml.
+#
+# The following keys must be escaped otherwise they will not be retrieved by
+# the default I18n backend:
+#
+# true, false, on, off, yes, no
+#
+# Instead, surround them with single quotes.
+#
+# en:
+# 'true': 'foo'
+#
+# To learn more, please read the Rails Internationalization guide
+# available at http://guides.rubyonrails.org/i18n.html.
+
+en:
+ hello: "Hello world"
diff --git a/actiontext/test/dummy/config/puma.rb b/actiontext/test/dummy/config/puma.rb
new file mode 100644
index 0000000000..71ed69a895
--- /dev/null
+++ b/actiontext/test/dummy/config/puma.rb
@@ -0,0 +1,34 @@
+# Puma can serve each request in a thread from an internal thread pool.
+# The `threads` method setting takes two numbers: a minimum and maximum.
+# Any libraries that use thread pools should be configured to match
+# the maximum value specified for Puma. Default is set to 5 threads for minimum
+# and maximum; this matches the default thread size of Active Record.
+#
+threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
+threads threads_count, threads_count
+
+# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
+#
+port ENV.fetch("PORT") { 3000 }
+
+# Specifies the `environment` that Puma will run in.
+#
+environment ENV.fetch("RAILS_ENV") { "development" }
+
+# Specifies the number of `workers` to boot in clustered mode.
+# Workers are forked web server processes. If using threads and workers together
+# the concurrency of the application would be max `threads` * `workers`.
+# Workers do not work on JRuby or Windows (both of which do not support
+# processes).
+#
+# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
+
+# Use the `preload_app!` method when specifying a `workers` number.
+# This directive tells Puma to first boot the application and load code
+# before forking the application. This takes advantage of Copy On Write
+# process behavior so workers use less memory.
+#
+# preload_app!
+
+# Allow puma to be restarted by `rails restart` command.
+plugin :tmp_restart
diff --git a/actiontext/test/dummy/config/routes.rb b/actiontext/test/dummy/config/routes.rb
new file mode 100644
index 0000000000..30b05169b3
--- /dev/null
+++ b/actiontext/test/dummy/config/routes.rb
@@ -0,0 +1,4 @@
+Rails.application.routes.draw do
+ resources :messages
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+end
diff --git a/actiontext/test/dummy/config/spring.rb b/actiontext/test/dummy/config/spring.rb
new file mode 100644
index 0000000000..9fa7863f99
--- /dev/null
+++ b/actiontext/test/dummy/config/spring.rb
@@ -0,0 +1,6 @@
+%w[
+ .ruby-version
+ .rbenv-vars
+ tmp/restart.txt
+ tmp/caching-dev.txt
+].each { |path| Spring.watch(path) }
diff --git a/actiontext/test/dummy/config/storage.yml b/actiontext/test/dummy/config/storage.yml
new file mode 100644
index 0000000000..53e562e0fc
--- /dev/null
+++ b/actiontext/test/dummy/config/storage.yml
@@ -0,0 +1,35 @@
+test:
+ service: Disk
+ root: <%= Rails.root.join("tmp/storage") %>
+
+local:
+ service: Disk
+ root: <%= Rails.root.join("storage") %>
+
+# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
+# amazon:
+# service: S3
+# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
+# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
+# region: us-east-1
+# bucket: your_own_bucket
+
+# Remember not to checkin your GCS keyfile to a repository
+# google:
+# service: GCS
+# project: your_project
+# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
+# bucket: your_own_bucket
+
+# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
+# microsoft:
+# service: AzureStorage
+# path: your_azure_storage_path
+# storage_account_name: your_account_name
+# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
+# container: your_container_name
+
+# mirror:
+# service: Mirror
+# primary: local
+# mirrors: [ amazon, google, microsoft ]
diff --git a/actiontext/test/dummy/config/webpack/development.js b/actiontext/test/dummy/config/webpack/development.js
new file mode 100644
index 0000000000..81269f6513
--- /dev/null
+++ b/actiontext/test/dummy/config/webpack/development.js
@@ -0,0 +1,3 @@
+const environment = require('./environment')
+
+module.exports = environment.toWebpackConfig()
diff --git a/actiontext/test/dummy/config/webpack/environment.js b/actiontext/test/dummy/config/webpack/environment.js
new file mode 100644
index 0000000000..d16d9af743
--- /dev/null
+++ b/actiontext/test/dummy/config/webpack/environment.js
@@ -0,0 +1,3 @@
+const { environment } = require('@rails/webpacker')
+
+module.exports = environment
diff --git a/actiontext/test/dummy/config/webpack/production.js b/actiontext/test/dummy/config/webpack/production.js
new file mode 100644
index 0000000000..81269f6513
--- /dev/null
+++ b/actiontext/test/dummy/config/webpack/production.js
@@ -0,0 +1,3 @@
+const environment = require('./environment')
+
+module.exports = environment.toWebpackConfig()
diff --git a/actiontext/test/dummy/config/webpack/test.js b/actiontext/test/dummy/config/webpack/test.js
new file mode 100644
index 0000000000..81269f6513
--- /dev/null
+++ b/actiontext/test/dummy/config/webpack/test.js
@@ -0,0 +1,3 @@
+const environment = require('./environment')
+
+module.exports = environment.toWebpackConfig()
diff --git a/actiontext/test/dummy/config/webpacker.yml b/actiontext/test/dummy/config/webpacker.yml
new file mode 100644
index 0000000000..d3f24e1b4b
--- /dev/null
+++ b/actiontext/test/dummy/config/webpacker.yml
@@ -0,0 +1,65 @@
+# Note: You must restart bin/webpack-dev-server for changes to take effect
+
+default: &default
+ source_path: app/javascript
+ source_entry_path: packs
+ public_output_path: packs
+ cache_path: tmp/cache/webpacker
+
+ # Additional paths webpack should lookup modules
+ # ['app/assets', 'engine/foo/app/assets']
+ resolved_paths: []
+
+ # Reload manifest.json on all requests so we reload latest compiled packs
+ cache_manifest: false
+
+ extensions:
+ - .js
+ - .sass
+ - .scss
+ - .css
+ - .png
+ - .svg
+ - .gif
+ - .jpeg
+ - .jpg
+
+development:
+ <<: *default
+ compile: true
+
+ # Reference: https://webpack.js.org/configuration/dev-server/
+ dev_server:
+ https: false
+ host: localhost
+ port: 3035
+ public: localhost:3035
+ hmr: false
+ # Inline should be set to true if using HMR
+ inline: true
+ overlay: true
+ compress: true
+ disable_host_check: true
+ use_local_ip: false
+ quiet: false
+ headers:
+ 'Access-Control-Allow-Origin': '*'
+ watch_options:
+ ignored: /node_modules/
+
+
+test:
+ <<: *default
+ compile: true
+
+ # Compile test packs to a separate directory
+ public_output_path: packs-test
+
+production:
+ <<: *default
+
+ # Production depends on precompilation of packs prior to booting for performance.
+ compile: false
+
+ # Cache manifest.json for performance
+ cache_manifest: true
diff --git a/actiontext/test/dummy/db/migrate/20180208205311_create_messages.rb b/actiontext/test/dummy/db/migrate/20180208205311_create_messages.rb
new file mode 100644
index 0000000000..3552840824
--- /dev/null
+++ b/actiontext/test/dummy/db/migrate/20180208205311_create_messages.rb
@@ -0,0 +1,8 @@
+class CreateMessages < ActiveRecord::Migration[6.0]
+ def change
+ create_table :messages do |t|
+ t.string :subject
+ t.timestamps
+ end
+ end
+end
diff --git a/actiontext/test/dummy/db/migrate/20180212164506_create_active_storage_tables.rb b/actiontext/test/dummy/db/migrate/20180212164506_create_active_storage_tables.rb
new file mode 100644
index 0000000000..0b2ce257c8
--- /dev/null
+++ b/actiontext/test/dummy/db/migrate/20180212164506_create_active_storage_tables.rb
@@ -0,0 +1,27 @@
+# This migration comes from active_storage (originally 20170806125915)
+class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
+ def change
+ create_table :active_storage_blobs do |t|
+ t.string :key, null: false
+ t.string :filename, null: false
+ t.string :content_type
+ t.text :metadata
+ t.bigint :byte_size, null: false
+ t.string :checksum, null: false
+ t.datetime :created_at, null: false
+
+ t.index [ :key ], unique: true
+ end
+
+ create_table :active_storage_attachments do |t|
+ t.string :name, null: false
+ t.references :record, null: false, polymorphic: true, index: false
+ t.references :blob, null: false
+
+ t.datetime :created_at, null: false
+
+ t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
+ t.foreign_key :active_storage_blobs, column: :blob_id
+ end
+ end
+end
diff --git a/actiontext/test/dummy/db/migrate/2018052816_create_action_text_tables.rb b/actiontext/test/dummy/db/migrate/2018052816_create_action_text_tables.rb
new file mode 100644
index 0000000000..74c7a0ecb9
--- /dev/null
+++ b/actiontext/test/dummy/db/migrate/2018052816_create_action_text_tables.rb
@@ -0,0 +1,14 @@
+class CreateActionTextTables < ActiveRecord::Migration[6.0]
+ def change
+ create_table :action_text_rich_texts do |t|
+ t.string :name, null: false
+ t.text :body, limit: 16777215
+ t.references :record, null: false, polymorphic: true, index: false
+
+ t.datetime :created_at, null: false
+ t.datetime :updated_at, null: false
+
+ t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
+ end
+ end
+end
diff --git a/actiontext/test/dummy/db/migrate/20181003185713_create_people.rb b/actiontext/test/dummy/db/migrate/20181003185713_create_people.rb
new file mode 100644
index 0000000000..6928a8e20d
--- /dev/null
+++ b/actiontext/test/dummy/db/migrate/20181003185713_create_people.rb
@@ -0,0 +1,9 @@
+class CreatePeople < ActiveRecord::Migration[6.0]
+ def change
+ create_table :people do |t|
+ t.string :name
+
+ t.timestamps
+ end
+ end
+end
diff --git a/actiontext/test/dummy/db/schema.rb b/actiontext/test/dummy/db/schema.rb
new file mode 100644
index 0000000000..7f8f4dff4e
--- /dev/null
+++ b/actiontext/test/dummy/db/schema.rb
@@ -0,0 +1,58 @@
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# This file is the source Rails uses to define your schema when running `rails
+# db:schema:load`. When creating a new database, `rails db:schema:load` tends to
+# be faster and is potentially less error prone than running all of your
+# migrations from scratch. Old migrations may fail to apply correctly if those
+# migrations use external dependencies or application code.
+#
+# It's strongly recommended that you check this file into your version control system.
+
+ActiveRecord::Schema.define(version: 2018_10_03_185713) do
+
+ create_table "action_text_rich_texts", force: :cascade do |t|
+ t.string "name", null: false
+ t.text "body", limit: 16777215
+ t.string "record_type", null: false
+ t.integer "record_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
+ end
+
+ create_table "active_storage_attachments", force: :cascade do |t|
+ t.string "name", null: false
+ t.string "record_type", null: false
+ t.integer "record_id", null: false
+ t.integer "blob_id", null: false
+ t.datetime "created_at", null: false
+ t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
+ t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
+ end
+
+ create_table "active_storage_blobs", force: :cascade do |t|
+ t.string "key", null: false
+ t.string "filename", null: false
+ t.string "content_type"
+ t.text "metadata"
+ t.bigint "byte_size", null: false
+ t.string "checksum", null: false
+ t.datetime "created_at", null: false
+ t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
+ end
+
+ create_table "messages", force: :cascade do |t|
+ t.string "subject"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ create_table "people", force: :cascade do |t|
+ t.string "name"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+end
diff --git a/actiontext/test/dummy/lib/assets/.keep b/actiontext/test/dummy/lib/assets/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actiontext/test/dummy/lib/assets/.keep
diff --git a/actiontext/test/dummy/log/.keep b/actiontext/test/dummy/log/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actiontext/test/dummy/log/.keep
diff --git a/actiontext/test/dummy/package.json b/actiontext/test/dummy/package.json
new file mode 100644
index 0000000000..ff2b802bba
--- /dev/null
+++ b/actiontext/test/dummy/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "dummy",
+ "private": true,
+ "dependencies": {
+ "@rails/webpacker": "^3.2.2",
+ "actiontext": "file:../.."
+ },
+ "devDependencies": {
+ "webpack-dev-server": "^2.11.1"
+ }
+}
diff --git a/actiontext/test/dummy/public/404.html b/actiontext/test/dummy/public/404.html
new file mode 100644
index 0000000000..2be3af26fc
--- /dev/null
+++ b/actiontext/test/dummy/public/404.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The page you were looking for doesn't exist (404)</title>
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ <style>
+ .rails-default-error-page {
+ background-color: #EFEFEF;
+ color: #2E2F30;
+ text-align: center;
+ font-family: arial, sans-serif;
+ margin: 0;
+ }
+
+ .rails-default-error-page div.dialog {
+ width: 95%;
+ max-width: 33em;
+ margin: 4em auto 0;
+ }
+
+ .rails-default-error-page div.dialog > div {
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #BBB;
+ border-top: #B00100 solid 4px;
+ border-top-left-radius: 9px;
+ border-top-right-radius: 9px;
+ background-color: white;
+ padding: 7px 12% 0;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+
+ .rails-default-error-page h1 {
+ font-size: 100%;
+ color: #730E15;
+ line-height: 1.5em;
+ }
+
+ .rails-default-error-page div.dialog > p {
+ margin: 0 0 1em;
+ padding: 1em;
+ background-color: #F7F7F7;
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #999;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-top-color: #DADADA;
+ color: #666;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+ </style>
+</head>
+
+<body class="rails-default-error-page">
+ <!-- This file lives in public/404.html -->
+ <div class="dialog">
+ <div>
+ <h1>The page you were looking for doesn't exist.</h1>
+ <p>You may have mistyped the address or the page may have moved.</p>
+ </div>
+ <p>If you are the application owner check the logs for more information.</p>
+ </div>
+</body>
+</html>
diff --git a/actiontext/test/dummy/public/422.html b/actiontext/test/dummy/public/422.html
new file mode 100644
index 0000000000..c08eac0d1d
--- /dev/null
+++ b/actiontext/test/dummy/public/422.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The change you wanted was rejected (422)</title>
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ <style>
+ .rails-default-error-page {
+ background-color: #EFEFEF;
+ color: #2E2F30;
+ text-align: center;
+ font-family: arial, sans-serif;
+ margin: 0;
+ }
+
+ .rails-default-error-page div.dialog {
+ width: 95%;
+ max-width: 33em;
+ margin: 4em auto 0;
+ }
+
+ .rails-default-error-page div.dialog > div {
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #BBB;
+ border-top: #B00100 solid 4px;
+ border-top-left-radius: 9px;
+ border-top-right-radius: 9px;
+ background-color: white;
+ padding: 7px 12% 0;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+
+ .rails-default-error-page h1 {
+ font-size: 100%;
+ color: #730E15;
+ line-height: 1.5em;
+ }
+
+ .rails-default-error-page div.dialog > p {
+ margin: 0 0 1em;
+ padding: 1em;
+ background-color: #F7F7F7;
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #999;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-top-color: #DADADA;
+ color: #666;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+ </style>
+</head>
+
+<body class="rails-default-error-page">
+ <!-- This file lives in public/422.html -->
+ <div class="dialog">
+ <div>
+ <h1>The change you wanted was rejected.</h1>
+ <p>Maybe you tried to change something you didn't have access to.</p>
+ </div>
+ <p>If you are the application owner check the logs for more information.</p>
+ </div>
+</body>
+</html>
diff --git a/actiontext/test/dummy/public/500.html b/actiontext/test/dummy/public/500.html
new file mode 100644
index 0000000000..78a030af22
--- /dev/null
+++ b/actiontext/test/dummy/public/500.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>We're sorry, but something went wrong (500)</title>
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ <style>
+ .rails-default-error-page {
+ background-color: #EFEFEF;
+ color: #2E2F30;
+ text-align: center;
+ font-family: arial, sans-serif;
+ margin: 0;
+ }
+
+ .rails-default-error-page div.dialog {
+ width: 95%;
+ max-width: 33em;
+ margin: 4em auto 0;
+ }
+
+ .rails-default-error-page div.dialog > div {
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #BBB;
+ border-top: #B00100 solid 4px;
+ border-top-left-radius: 9px;
+ border-top-right-radius: 9px;
+ background-color: white;
+ padding: 7px 12% 0;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+
+ .rails-default-error-page h1 {
+ font-size: 100%;
+ color: #730E15;
+ line-height: 1.5em;
+ }
+
+ .rails-default-error-page div.dialog > p {
+ margin: 0 0 1em;
+ padding: 1em;
+ background-color: #F7F7F7;
+ border: 1px solid #CCC;
+ border-right-color: #999;
+ border-left-color: #999;
+ border-bottom-color: #999;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-top-color: #DADADA;
+ color: #666;
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
+ }
+ </style>
+</head>
+
+<body class="rails-default-error-page">
+ <!-- This file lives in public/500.html -->
+ <div class="dialog">
+ <div>
+ <h1>We're sorry, but something went wrong.</h1>
+ </div>
+ <p>If you are the application owner check the logs for more information.</p>
+ </div>
+</body>
+</html>
diff --git a/actiontext/test/dummy/public/apple-touch-icon-precomposed.png b/actiontext/test/dummy/public/apple-touch-icon-precomposed.png
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actiontext/test/dummy/public/apple-touch-icon-precomposed.png
diff --git a/actiontext/test/dummy/public/apple-touch-icon.png b/actiontext/test/dummy/public/apple-touch-icon.png
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actiontext/test/dummy/public/apple-touch-icon.png
diff --git a/actiontext/test/dummy/public/favicon.ico b/actiontext/test/dummy/public/favicon.ico
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actiontext/test/dummy/public/favicon.ico
diff --git a/actiontext/test/dummy/storage/.keep b/actiontext/test/dummy/storage/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actiontext/test/dummy/storage/.keep
diff --git a/actiontext/test/dummy/tmp/.keep b/actiontext/test/dummy/tmp/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actiontext/test/dummy/tmp/.keep
diff --git a/actiontext/test/dummy/tmp/storage/.keep b/actiontext/test/dummy/tmp/storage/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actiontext/test/dummy/tmp/storage/.keep
diff --git a/actiontext/test/dummy/yarn.lock b/actiontext/test/dummy/yarn.lock
new file mode 100644
index 0000000000..40444165f0
--- /dev/null
+++ b/actiontext/test/dummy/yarn.lock
@@ -0,0 +1,6071 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@rails/webpacker@^3.2.2":
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-3.2.2.tgz#6d60e1cf729dc2ccc52053c9b6b8d30c9a48a297"
+ dependencies:
+ babel-core "^6.26.0"
+ babel-loader "^7.1.2"
+ babel-plugin-syntax-dynamic-import "^6.18.0"
+ babel-plugin-transform-class-properties "^6.24.1"
+ babel-plugin-transform-object-rest-spread "^6.26.0"
+ babel-polyfill "^6.26.0"
+ babel-preset-env "^1.6.1"
+ case-sensitive-paths-webpack-plugin "^2.1.1"
+ compression-webpack-plugin "^1.1.6"
+ css-loader "^0.28.9"
+ extract-text-webpack-plugin "^3.0.2"
+ file-loader "^1.1.6"
+ glob "^7.1.2"
+ js-yaml "^3.10.0"
+ node-sass "^4.7.2"
+ path-complete-extname "^0.1.0"
+ postcss-cssnext "^3.1.0"
+ postcss-import "^11.0.0"
+ postcss-loader "^2.1.0"
+ sass-loader "^6.0.6"
+ style-loader "^0.20.1"
+ uglifyjs-webpack-plugin "^1.1.8"
+ webpack "^3.10.0"
+ webpack-manifest-plugin "^1.3.2"
+
+abbrev@1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+
+accepts@~1.3.4:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f"
+ dependencies:
+ mime-types "~2.1.16"
+ negotiator "0.6.1"
+
+acorn-dynamic-import@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4"
+ dependencies:
+ acorn "^4.0.3"
+
+acorn@^4.0.3:
+ version "4.0.13"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
+
+acorn@^5.0.0:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102"
+
+"activestorage@>= 5.2.0-rc1":
+ version "5.2.0-rc1"
+ resolved "https://registry.yarnpkg.com/activestorage/-/activestorage-5.2.0-rc1.tgz#79898996eceb0f13575eff41fb109051fbfa49b0"
+
+"actiontext@file:../..":
+ version "0.1"
+ dependencies:
+ activestorage ">= 5.2.0-rc1"
+ trix "^0.11.1"
+
+ajv-keywords@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
+
+ajv-keywords@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be"
+
+ajv@^4.9.1:
+ version "4.11.8"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
+ dependencies:
+ co "^4.6.0"
+ json-stable-stringify "^1.0.1"
+
+ajv@^5.0.0, ajv@^5.1.0:
+ version "5.5.2"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
+ dependencies:
+ co "^4.6.0"
+ fast-deep-equal "^1.0.0"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.3.0"
+
+ajv@^6.1.0:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.1.1.tgz#978d597fbc2b7d0e5a5c3ddeb149a682f2abfa0e"
+ dependencies:
+ fast-deep-equal "^1.0.0"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.3.0"
+
+align-text@^0.1.1, align-text@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
+ dependencies:
+ kind-of "^3.0.2"
+ longest "^1.0.1"
+ repeat-string "^1.5.2"
+
+alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
+
+amdefine@>=0.0.4:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
+
+ansi-html@0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
+
+ansi-regex@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+
+ansi-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+
+ansi-styles@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+
+ansi-styles@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
+ dependencies:
+ color-convert "^1.9.0"
+
+anymatch@^1.3.0:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
+ dependencies:
+ micromatch "^2.1.5"
+ normalize-path "^2.0.0"
+
+anymatch@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
+ dependencies:
+ micromatch "^3.1.4"
+ normalize-path "^2.1.1"
+
+aproba@^1.0.3, aproba@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
+
+are-we-there-yet@~1.1.2:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d"
+ dependencies:
+ delegates "^1.0.0"
+ readable-stream "^2.0.6"
+
+argparse@^1.0.7:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
+ dependencies:
+ sprintf-js "~1.0.2"
+
+arr-diff@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
+ dependencies:
+ arr-flatten "^1.0.1"
+
+arr-diff@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
+
+arr-flatten@^1.0.1, arr-flatten@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
+
+arr-union@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
+
+array-find-index@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
+
+array-flatten@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+
+array-flatten@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296"
+
+array-includes@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
+ dependencies:
+ define-properties "^1.1.2"
+ es-abstract "^1.7.0"
+
+array-union@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
+ dependencies:
+ array-uniq "^1.0.1"
+
+array-uniq@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
+
+array-unique@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
+
+array-unique@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
+
+asn1.js@^4.0.0:
+ version "4.9.2"
+ resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.2.tgz#8117ef4f7ed87cd8f89044b5bff97ac243a16c9a"
+ dependencies:
+ bn.js "^4.0.0"
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+
+asn1@~0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
+
+assert-plus@1.0.0, assert-plus@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
+
+assert-plus@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
+
+assert@^1.1.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
+ dependencies:
+ util "0.10.3"
+
+assign-symbols@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
+
+async-each@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
+
+async-foreach@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
+
+async@^1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
+
+async@^2.1.2, async@^2.1.5, async@^2.4.1:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
+ dependencies:
+ lodash "^4.14.0"
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+
+atob@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d"
+
+autoprefixer@^6.3.1:
+ version "6.7.7"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014"
+ dependencies:
+ browserslist "^1.7.6"
+ caniuse-db "^1.0.30000634"
+ normalize-range "^0.1.2"
+ num2fraction "^1.2.2"
+ postcss "^5.2.16"
+ postcss-value-parser "^3.2.3"
+
+autoprefixer@^7.1.1:
+ version "7.2.6"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.2.6.tgz#256672f86f7c735da849c4f07d008abb056067dc"
+ dependencies:
+ browserslist "^2.11.3"
+ caniuse-lite "^1.0.30000805"
+ normalize-range "^0.1.2"
+ num2fraction "^1.2.2"
+ postcss "^6.0.17"
+ postcss-value-parser "^3.2.3"
+
+aws-sign2@~0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
+
+aws-sign2@~0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
+
+aws4@^1.2.1, aws4@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
+
+babel-code-frame@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
+ dependencies:
+ chalk "^1.1.3"
+ esutils "^2.0.2"
+ js-tokens "^3.0.2"
+
+babel-core@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8"
+ dependencies:
+ babel-code-frame "^6.26.0"
+ babel-generator "^6.26.0"
+ babel-helpers "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-register "^6.26.0"
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ convert-source-map "^1.5.0"
+ debug "^2.6.8"
+ json5 "^0.5.1"
+ lodash "^4.17.4"
+ minimatch "^3.0.4"
+ path-is-absolute "^1.0.1"
+ private "^0.1.7"
+ slash "^1.0.0"
+ source-map "^0.5.6"
+
+babel-generator@^6.26.0:
+ version "6.26.1"
+ resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
+ dependencies:
+ babel-messages "^6.23.0"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ detect-indent "^4.0.0"
+ jsesc "^1.3.0"
+ lodash "^4.17.4"
+ source-map "^0.5.7"
+ trim-right "^1.0.1"
+
+babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
+ dependencies:
+ babel-helper-explode-assignable-expression "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-call-delegate@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
+ dependencies:
+ babel-helper-hoist-variables "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-define-map@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f"
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-helper-explode-assignable-expression@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-function-name@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
+ dependencies:
+ babel-helper-get-function-arity "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-get-function-arity@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-hoist-variables@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-optimise-call-expression@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-regex@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72"
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-helper-remap-async-to-generator@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b"
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-replace-supers@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a"
+ dependencies:
+ babel-helper-optimise-call-expression "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helpers@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-loader@^7.1.2:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.2.tgz#f6cbe122710f1aa2af4d881c6d5b54358ca24126"
+ dependencies:
+ find-cache-dir "^1.0.0"
+ loader-utils "^1.0.2"
+ mkdirp "^0.5.1"
+
+babel-messages@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-check-es2015-constants@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-syntax-async-functions@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
+
+babel-plugin-syntax-class-properties@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de"
+
+babel-plugin-syntax-dynamic-import@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
+
+babel-plugin-syntax-exponentiation-operator@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
+
+babel-plugin-syntax-object-rest-spread@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
+
+babel-plugin-syntax-trailing-function-commas@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
+
+babel-plugin-transform-async-to-generator@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
+ dependencies:
+ babel-helper-remap-async-to-generator "^6.24.1"
+ babel-plugin-syntax-async-functions "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-class-properties@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac"
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-plugin-syntax-class-properties "^6.8.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-arrow-functions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-block-scoping@^6.23.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-plugin-transform-es2015-classes@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
+ dependencies:
+ babel-helper-define-map "^6.24.1"
+ babel-helper-function-name "^6.24.1"
+ babel-helper-optimise-call-expression "^6.24.1"
+ babel-helper-replace-supers "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-computed-properties@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-destructuring@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-duplicate-keys@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-for-of@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-function-name@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-literals@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
+ dependencies:
+ babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
+ dependencies:
+ babel-plugin-transform-strict-mode "^6.24.1"
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-types "^6.26.0"
+
+babel-plugin-transform-es2015-modules-systemjs@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
+ dependencies:
+ babel-helper-hoist-variables "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-modules-umd@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
+ dependencies:
+ babel-plugin-transform-es2015-modules-amd "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-object-super@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
+ dependencies:
+ babel-helper-replace-supers "^6.24.1"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-parameters@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
+ dependencies:
+ babel-helper-call-delegate "^6.24.1"
+ babel-helper-get-function-arity "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-shorthand-properties@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-spread@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-sticky-regex@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
+ dependencies:
+ babel-helper-regex "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-template-literals@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-unicode-regex@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
+ dependencies:
+ babel-helper-regex "^6.24.1"
+ babel-runtime "^6.22.0"
+ regexpu-core "^2.0.0"
+
+babel-plugin-transform-exponentiation-operator@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
+ dependencies:
+ babel-helper-builder-binary-assignment-operator-visitor "^6.24.1"
+ babel-plugin-syntax-exponentiation-operator "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-object-rest-spread@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06"
+ dependencies:
+ babel-plugin-syntax-object-rest-spread "^6.8.0"
+ babel-runtime "^6.26.0"
+
+babel-plugin-transform-regenerator@^6.22.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
+ dependencies:
+ regenerator-transform "^0.10.0"
+
+babel-plugin-transform-strict-mode@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-polyfill@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153"
+ dependencies:
+ babel-runtime "^6.26.0"
+ core-js "^2.5.0"
+ regenerator-runtime "^0.10.5"
+
+babel-preset-env@^1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.1.tgz#a18b564cc9b9afdf4aae57ae3c1b0d99188e6f48"
+ dependencies:
+ babel-plugin-check-es2015-constants "^6.22.0"
+ babel-plugin-syntax-trailing-function-commas "^6.22.0"
+ babel-plugin-transform-async-to-generator "^6.22.0"
+ babel-plugin-transform-es2015-arrow-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoping "^6.23.0"
+ babel-plugin-transform-es2015-classes "^6.23.0"
+ babel-plugin-transform-es2015-computed-properties "^6.22.0"
+ babel-plugin-transform-es2015-destructuring "^6.23.0"
+ babel-plugin-transform-es2015-duplicate-keys "^6.22.0"
+ babel-plugin-transform-es2015-for-of "^6.23.0"
+ babel-plugin-transform-es2015-function-name "^6.22.0"
+ babel-plugin-transform-es2015-literals "^6.22.0"
+ babel-plugin-transform-es2015-modules-amd "^6.22.0"
+ babel-plugin-transform-es2015-modules-commonjs "^6.23.0"
+ babel-plugin-transform-es2015-modules-systemjs "^6.23.0"
+ babel-plugin-transform-es2015-modules-umd "^6.23.0"
+ babel-plugin-transform-es2015-object-super "^6.22.0"
+ babel-plugin-transform-es2015-parameters "^6.23.0"
+ babel-plugin-transform-es2015-shorthand-properties "^6.22.0"
+ babel-plugin-transform-es2015-spread "^6.22.0"
+ babel-plugin-transform-es2015-sticky-regex "^6.22.0"
+ babel-plugin-transform-es2015-template-literals "^6.22.0"
+ babel-plugin-transform-es2015-typeof-symbol "^6.23.0"
+ babel-plugin-transform-es2015-unicode-regex "^6.22.0"
+ babel-plugin-transform-exponentiation-operator "^6.22.0"
+ babel-plugin-transform-regenerator "^6.22.0"
+ browserslist "^2.1.2"
+ invariant "^2.2.2"
+ semver "^5.3.0"
+
+babel-register@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
+ dependencies:
+ babel-core "^6.26.0"
+ babel-runtime "^6.26.0"
+ core-js "^2.5.0"
+ home-or-tmp "^2.0.0"
+ lodash "^4.17.4"
+ mkdirp "^0.5.1"
+ source-map-support "^0.4.15"
+
+babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
+ dependencies:
+ core-js "^2.4.0"
+ regenerator-runtime "^0.11.0"
+
+babel-template@^6.24.1, babel-template@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ lodash "^4.17.4"
+
+babel-traverse@^6.24.1, babel-traverse@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
+ dependencies:
+ babel-code-frame "^6.26.0"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ debug "^2.6.8"
+ globals "^9.18.0"
+ invariant "^2.2.2"
+ lodash "^4.17.4"
+
+babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
+ dependencies:
+ babel-runtime "^6.26.0"
+ esutils "^2.0.2"
+ lodash "^4.17.4"
+ to-fast-properties "^1.0.3"
+
+babylon@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
+
+balanced-match@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.1.0.tgz#b504bd05869b39259dd0c5efc35d843176dccc4a"
+
+balanced-match@^0.4.2:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+
+base64-js@^1.0.2:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
+
+base@^0.11.1:
+ version "0.11.2"
+ resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
+ dependencies:
+ cache-base "^1.0.1"
+ class-utils "^0.3.5"
+ component-emitter "^1.2.1"
+ define-property "^1.0.0"
+ isobject "^3.0.1"
+ mixin-deep "^1.2.0"
+ pascalcase "^0.1.1"
+
+batch@0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
+
+bcrypt-pbkdf@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
+ dependencies:
+ tweetnacl "^0.14.3"
+
+big.js@^3.1.3:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
+
+binary-extensions@^1.0.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
+
+block-stream@*:
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
+ dependencies:
+ inherits "~2.0.0"
+
+bluebird@^3.5.0:
+ version "3.5.1"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
+
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
+ version "4.11.8"
+ resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
+
+body-parser@1.18.2:
+ version "1.18.2"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454"
+ dependencies:
+ bytes "3.0.0"
+ content-type "~1.0.4"
+ debug "2.6.9"
+ depd "~1.1.1"
+ http-errors "~1.6.2"
+ iconv-lite "0.4.19"
+ on-finished "~2.3.0"
+ qs "6.5.1"
+ raw-body "2.3.2"
+ type-is "~1.6.15"
+
+bonjour@^3.5.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
+ dependencies:
+ array-flatten "^2.1.0"
+ deep-equal "^1.0.1"
+ dns-equal "^1.0.0"
+ dns-txt "^2.0.2"
+ multicast-dns "^6.0.1"
+ multicast-dns-service-types "^1.1.0"
+
+boom@2.x.x:
+ version "2.10.1"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
+ dependencies:
+ hoek "2.x.x"
+
+boom@4.x.x:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
+ dependencies:
+ hoek "4.x.x"
+
+boom@5.x.x:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
+ dependencies:
+ hoek "4.x.x"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+braces@^1.8.2:
+ version "1.8.5"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
+ dependencies:
+ expand-range "^1.8.1"
+ preserve "^0.2.0"
+ repeat-element "^1.1.2"
+
+braces@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.0.tgz#a46941cb5fb492156b3d6a656e06c35364e3e66e"
+ dependencies:
+ arr-flatten "^1.1.0"
+ array-unique "^0.3.2"
+ define-property "^1.0.0"
+ extend-shallow "^2.0.1"
+ fill-range "^4.0.0"
+ isobject "^3.0.1"
+ repeat-element "^1.1.2"
+ snapdragon "^0.8.1"
+ snapdragon-node "^2.0.1"
+ split-string "^3.0.2"
+ to-regex "^3.0.1"
+
+brorand@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
+
+browserify-aes@^1.0.0, browserify-aes@^1.0.4:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f"
+ dependencies:
+ buffer-xor "^1.0.3"
+ cipher-base "^1.0.0"
+ create-hash "^1.1.0"
+ evp_bytestokey "^1.0.3"
+ inherits "^2.0.1"
+ safe-buffer "^5.0.1"
+
+browserify-cipher@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a"
+ dependencies:
+ browserify-aes "^1.0.4"
+ browserify-des "^1.0.0"
+ evp_bytestokey "^1.0.0"
+
+browserify-des@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd"
+ dependencies:
+ cipher-base "^1.0.1"
+ des.js "^1.0.0"
+ inherits "^2.0.1"
+
+browserify-rsa@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
+ dependencies:
+ bn.js "^4.1.0"
+ randombytes "^2.0.1"
+
+browserify-sign@^4.0.0:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298"
+ dependencies:
+ bn.js "^4.1.1"
+ browserify-rsa "^4.0.0"
+ create-hash "^1.1.0"
+ create-hmac "^1.1.2"
+ elliptic "^6.0.0"
+ inherits "^2.0.1"
+ parse-asn1 "^5.0.0"
+
+browserify-zlib@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
+ dependencies:
+ pako "~1.0.5"
+
+browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
+ version "1.7.7"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9"
+ dependencies:
+ caniuse-db "^1.0.30000639"
+ electron-to-chromium "^1.2.7"
+
+browserslist@^2.0.0, browserslist@^2.1.2, browserslist@^2.11.3:
+ version "2.11.3"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.11.3.tgz#fe36167aed1bbcde4827ebfe71347a2cc70b99b2"
+ dependencies:
+ caniuse-lite "^1.0.30000792"
+ electron-to-chromium "^1.3.30"
+
+buffer-indexof@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
+
+buffer-xor@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
+
+buffer@^4.3.0:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
+ dependencies:
+ base64-js "^1.0.2"
+ ieee754 "^1.1.4"
+ isarray "^1.0.0"
+
+builtin-modules@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+
+builtin-status-codes@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
+
+bytes@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
+
+cacache@^10.0.1:
+ version "10.0.2"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.2.tgz#105a93a162bbedf3a25da42e1939ed99ffb145f8"
+ dependencies:
+ bluebird "^3.5.0"
+ chownr "^1.0.1"
+ glob "^7.1.2"
+ graceful-fs "^4.1.11"
+ lru-cache "^4.1.1"
+ mississippi "^1.3.0"
+ mkdirp "^0.5.1"
+ move-concurrently "^1.0.1"
+ promise-inflight "^1.0.1"
+ rimraf "^2.6.1"
+ ssri "^5.0.0"
+ unique-filename "^1.1.0"
+ y18n "^3.2.1"
+
+cache-base@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
+ dependencies:
+ collection-visit "^1.0.0"
+ component-emitter "^1.2.1"
+ get-value "^2.0.6"
+ has-value "^1.0.0"
+ isobject "^3.0.1"
+ set-value "^2.0.0"
+ to-object-path "^0.3.0"
+ union-value "^1.0.0"
+ unset-value "^1.0.0"
+
+camelcase-keys@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
+ dependencies:
+ camelcase "^2.0.0"
+ map-obj "^1.0.0"
+
+camelcase@^1.0.2:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
+
+camelcase@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
+
+camelcase@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
+
+camelcase@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
+
+caniuse-api@^1.5.2:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"
+ dependencies:
+ browserslist "^1.3.6"
+ caniuse-db "^1.0.30000529"
+ lodash.memoize "^4.1.2"
+ lodash.uniq "^4.5.0"
+
+caniuse-api@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-2.0.0.tgz#b1ddb5a5966b16f48dc4998444d4bbc6c7d9d834"
+ dependencies:
+ browserslist "^2.0.0"
+ caniuse-lite "^1.0.0"
+ lodash.memoize "^4.1.2"
+ lodash.uniq "^4.5.0"
+
+caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
+ version "1.0.30000808"
+ resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000808.tgz#30dfd83009d5704f02dffb37725068ed12a366bb"
+
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805:
+ version "1.0.30000808"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000808.tgz#7d759b5518529ea08b6705a19e70dbf401628ffc"
+
+case-sensitive-paths-webpack-plugin@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.1.1.tgz#3d29ced8c1f124bf6f53846fb3f5894731fdc909"
+
+caseless@~0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
+
+caseless@~0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
+
+center-align@^0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
+ dependencies:
+ align-text "^0.1.3"
+ lazy-cache "^1.0.3"
+
+chalk@^1.1.1, chalk@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
+ dependencies:
+ ansi-styles "^2.2.1"
+ escape-string-regexp "^1.0.2"
+ has-ansi "^2.0.0"
+ strip-ansi "^3.0.0"
+ supports-color "^2.0.0"
+
+chalk@^2.0.1, chalk@^2.3.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
+ dependencies:
+ ansi-styles "^3.2.0"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.2.0"
+
+chokidar@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
+ dependencies:
+ anymatch "^1.3.0"
+ async-each "^1.0.0"
+ glob-parent "^2.0.0"
+ inherits "^2.0.1"
+ is-binary-path "^1.0.0"
+ is-glob "^2.0.0"
+ path-is-absolute "^1.0.0"
+ readdirp "^2.0.0"
+ optionalDependencies:
+ fsevents "^1.0.0"
+
+chokidar@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.1.tgz#6e67e9998fe10e8f651e975ca62460456ff8e297"
+ dependencies:
+ anymatch "^2.0.0"
+ async-each "^1.0.0"
+ braces "^2.3.0"
+ glob-parent "^3.1.0"
+ inherits "^2.0.1"
+ is-binary-path "^1.0.0"
+ is-glob "^4.0.0"
+ normalize-path "^2.1.1"
+ path-is-absolute "^1.0.0"
+ readdirp "^2.0.0"
+ upath "1.0.0"
+ optionalDependencies:
+ fsevents "^1.0.0"
+
+chownr@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
+
+cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
+ dependencies:
+ inherits "^2.0.1"
+ safe-buffer "^5.0.1"
+
+clap@^1.0.9:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51"
+ dependencies:
+ chalk "^1.1.3"
+
+class-utils@^0.3.5:
+ version "0.3.6"
+ resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
+ dependencies:
+ arr-union "^3.1.0"
+ define-property "^0.2.5"
+ isobject "^3.0.0"
+ static-extend "^0.1.1"
+
+cliui@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
+ dependencies:
+ center-align "^0.1.1"
+ right-align "^0.1.1"
+ wordwrap "0.0.2"
+
+cliui@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wrap-ansi "^2.0.0"
+
+clone-deep@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.3.0.tgz#348c61ae9cdbe0edfe053d91ff4cc521d790ede8"
+ dependencies:
+ for-own "^1.0.0"
+ is-plain-object "^2.0.1"
+ kind-of "^3.2.2"
+ shallow-clone "^0.1.2"
+
+clone@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f"
+
+co@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+
+coa@~1.0.1:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd"
+ dependencies:
+ q "^1.1.2"
+
+code-point-at@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+
+collection-visit@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
+ dependencies:
+ map-visit "^1.0.0"
+ object-visit "^1.0.0"
+
+color-convert@^1.3.0, color-convert@^1.8.2, color-convert@^1.9.0, color-convert@^1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed"
+ dependencies:
+ color-name "^1.1.1"
+
+color-name@^1.0.0, color-name@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+
+color-string@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
+ dependencies:
+ color-name "^1.0.0"
+
+color-string@^1.4.0, color-string@^1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.2.tgz#26e45814bc3c9a7cbd6751648a41434514a773a9"
+ dependencies:
+ color-name "^1.0.0"
+ simple-swizzle "^0.2.2"
+
+color@^0.11.0:
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764"
+ dependencies:
+ clone "^1.0.2"
+ color-convert "^1.3.0"
+ color-string "^0.3.0"
+
+color@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/color/-/color-1.0.3.tgz#e48e832d85f14ef694fb468811c2d5cfe729b55d"
+ dependencies:
+ color-convert "^1.8.2"
+ color-string "^1.4.0"
+
+color@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color/-/color-2.0.1.tgz#e4ed78a3c4603d0891eba5430b04b86314f4c839"
+ dependencies:
+ color-convert "^1.9.1"
+ color-string "^1.5.2"
+
+colormin@^1.0.5:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133"
+ dependencies:
+ color "^0.11.0"
+ css-color-names "0.0.4"
+ has "^1.0.1"
+
+colors@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
+
+combined-stream@^1.0.5, combined-stream@~1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
+ dependencies:
+ delayed-stream "~1.0.0"
+
+commander@^2.9.0, commander@~2.14.1:
+ version "2.14.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
+
+commondir@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
+
+component-emitter@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
+
+compressible@~2.0.11:
+ version "2.0.12"
+ resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66"
+ dependencies:
+ mime-db ">= 1.30.0 < 2"
+
+compression-webpack-plugin@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-1.1.6.tgz#450808fe143b4c5216a14f0c315c47bec3d83cec"
+ dependencies:
+ async "^2.4.1"
+ cacache "^10.0.1"
+ find-cache-dir "^1.0.0"
+ serialize-javascript "^1.4.0"
+ webpack-sources "^1.0.1"
+
+compression@^1.5.2:
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.1.tgz#eff2603efc2e22cf86f35d2eb93589f9875373db"
+ dependencies:
+ accepts "~1.3.4"
+ bytes "3.0.0"
+ compressible "~2.0.11"
+ debug "2.6.9"
+ on-headers "~1.0.1"
+ safe-buffer "5.1.1"
+ vary "~1.1.2"
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+
+concat-stream@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
+ dependencies:
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
+connect-history-api-fallback@^1.3.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a"
+
+console-browserify@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
+ dependencies:
+ date-now "^0.1.4"
+
+console-control-strings@^1.0.0, console-control-strings@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+
+constants-browserify@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
+
+content-disposition@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+
+content-type@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+
+convert-source-map@^1.5.0:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
+
+cookie-signature@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+
+cookie@0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
+
+copy-concurrently@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
+ dependencies:
+ aproba "^1.1.1"
+ fs-write-stream-atomic "^1.0.8"
+ iferr "^0.1.5"
+ mkdirp "^0.5.1"
+ rimraf "^2.5.4"
+ run-queue "^1.0.0"
+
+copy-descriptor@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
+
+core-js@^2.4.0, core-js@^2.5.0:
+ version "2.5.3"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
+
+core-util-is@1.0.2, core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+
+cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.2.2.tgz#6173cebd56fac042c1f4390edf7af6c07c7cb892"
+ dependencies:
+ is-directory "^0.3.1"
+ js-yaml "^3.4.3"
+ minimist "^1.2.0"
+ object-assign "^4.1.0"
+ os-homedir "^1.0.1"
+ parse-json "^2.2.0"
+ require-from-string "^1.1.0"
+
+create-ecdh@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
+ dependencies:
+ bn.js "^4.1.0"
+ elliptic "^6.0.0"
+
+create-hash@^1.1.0, create-hash@^1.1.2:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd"
+ dependencies:
+ cipher-base "^1.0.1"
+ inherits "^2.0.1"
+ ripemd160 "^2.0.0"
+ sha.js "^2.4.0"
+
+create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06"
+ dependencies:
+ cipher-base "^1.0.3"
+ create-hash "^1.1.0"
+ inherits "^2.0.1"
+ ripemd160 "^2.0.0"
+ safe-buffer "^5.0.1"
+ sha.js "^2.4.8"
+
+cross-spawn@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
+ dependencies:
+ lru-cache "^4.0.1"
+ which "^1.2.9"
+
+cross-spawn@^5.0.1:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
+ dependencies:
+ lru-cache "^4.0.1"
+ shebang-command "^1.2.0"
+ which "^1.2.9"
+
+cryptiles@2.x.x:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
+ dependencies:
+ boom "2.x.x"
+
+cryptiles@3.x.x:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
+ dependencies:
+ boom "5.x.x"
+
+crypto-browserify@^3.11.0:
+ version "3.12.0"
+ resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
+ dependencies:
+ browserify-cipher "^1.0.0"
+ browserify-sign "^4.0.0"
+ create-ecdh "^4.0.0"
+ create-hash "^1.1.0"
+ create-hmac "^1.1.0"
+ diffie-hellman "^5.0.0"
+ inherits "^2.0.1"
+ pbkdf2 "^3.0.3"
+ public-encrypt "^4.0.0"
+ randombytes "^2.0.0"
+ randomfill "^1.0.3"
+
+css-color-function@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/css-color-function/-/css-color-function-1.3.3.tgz#8ed24c2c0205073339fafa004bc8c141fccb282e"
+ dependencies:
+ balanced-match "0.1.0"
+ color "^0.11.0"
+ debug "^3.1.0"
+ rgb "~0.1.0"
+
+css-color-names@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
+
+css-loader@^0.28.9:
+ version "0.28.9"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.9.tgz#68064b85f4e271d7ce4c48a58300928e535d1c95"
+ dependencies:
+ babel-code-frame "^6.26.0"
+ css-selector-tokenizer "^0.7.0"
+ cssnano "^3.10.0"
+ icss-utils "^2.1.0"
+ loader-utils "^1.0.2"
+ lodash.camelcase "^4.3.0"
+ object-assign "^4.1.1"
+ postcss "^5.0.6"
+ postcss-modules-extract-imports "^1.2.0"
+ postcss-modules-local-by-default "^1.2.0"
+ postcss-modules-scope "^1.1.0"
+ postcss-modules-values "^1.3.0"
+ postcss-value-parser "^3.3.0"
+ source-list-map "^2.0.0"
+
+css-selector-tokenizer@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86"
+ dependencies:
+ cssesc "^0.1.0"
+ fastparse "^1.1.1"
+ regexpu-core "^1.0.0"
+
+css-unit-converter@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996"
+
+cssesc@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
+
+cssnano@^3.10.0:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38"
+ dependencies:
+ autoprefixer "^6.3.1"
+ decamelize "^1.1.2"
+ defined "^1.0.0"
+ has "^1.0.1"
+ object-assign "^4.0.1"
+ postcss "^5.0.14"
+ postcss-calc "^5.2.0"
+ postcss-colormin "^2.1.8"
+ postcss-convert-values "^2.3.4"
+ postcss-discard-comments "^2.0.4"
+ postcss-discard-duplicates "^2.0.1"
+ postcss-discard-empty "^2.0.1"
+ postcss-discard-overridden "^0.1.1"
+ postcss-discard-unused "^2.2.1"
+ postcss-filter-plugins "^2.0.0"
+ postcss-merge-idents "^2.1.5"
+ postcss-merge-longhand "^2.0.1"
+ postcss-merge-rules "^2.0.3"
+ postcss-minify-font-values "^1.0.2"
+ postcss-minify-gradients "^1.0.1"
+ postcss-minify-params "^1.0.4"
+ postcss-minify-selectors "^2.0.4"
+ postcss-normalize-charset "^1.1.0"
+ postcss-normalize-url "^3.0.7"
+ postcss-ordered-values "^2.1.0"
+ postcss-reduce-idents "^2.2.2"
+ postcss-reduce-initial "^1.0.0"
+ postcss-reduce-transforms "^1.0.3"
+ postcss-svgo "^2.1.1"
+ postcss-unique-selectors "^2.0.2"
+ postcss-value-parser "^3.2.3"
+ postcss-zindex "^2.0.1"
+
+csso@~2.3.1:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85"
+ dependencies:
+ clap "^1.0.9"
+ source-map "^0.5.3"
+
+currently-unhandled@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
+ dependencies:
+ array-find-index "^1.0.1"
+
+cyclist@~0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
+
+d@1:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
+ dependencies:
+ es5-ext "^0.10.9"
+
+dashdash@^1.12.0:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
+ dependencies:
+ assert-plus "^1.0.0"
+
+date-now@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
+
+debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ dependencies:
+ ms "2.0.0"
+
+debug@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ dependencies:
+ ms "2.0.0"
+
+decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+
+decode-uri-component@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
+
+deep-equal@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
+
+deep-extend@~0.4.0:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
+
+define-properties@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
+ dependencies:
+ foreach "^2.0.5"
+ object-keys "^1.0.8"
+
+define-property@^0.2.5:
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
+ dependencies:
+ is-descriptor "^0.1.0"
+
+define-property@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6"
+ dependencies:
+ is-descriptor "^1.0.0"
+
+defined@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+
+del@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5"
+ dependencies:
+ globby "^6.1.0"
+ is-path-cwd "^1.0.0"
+ is-path-in-cwd "^1.0.0"
+ p-map "^1.1.1"
+ pify "^3.0.0"
+ rimraf "^2.2.8"
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+
+delegates@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+
+depd@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
+
+depd@~1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
+
+des.js@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
+ dependencies:
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+
+destroy@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+
+detect-indent@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
+ dependencies:
+ repeating "^2.0.0"
+
+detect-libc@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
+
+detect-node@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127"
+
+diffie-hellman@^5.0.0:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
+ dependencies:
+ bn.js "^4.1.0"
+ miller-rabin "^4.0.0"
+ randombytes "^2.0.0"
+
+dns-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
+
+dns-packet@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a"
+ dependencies:
+ ip "^1.1.0"
+ safe-buffer "^5.0.1"
+
+dns-txt@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6"
+ dependencies:
+ buffer-indexof "^1.0.0"
+
+domain-browser@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
+
+duplexify@^3.4.2, duplexify@^3.5.3:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.3.tgz#8b5818800df92fd0125b27ab896491912858243e"
+ dependencies:
+ end-of-stream "^1.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+ stream-shift "^1.0.0"
+
+ecc-jsbn@~0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
+ dependencies:
+ jsbn "~0.1.0"
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+
+electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.30:
+ version "1.3.33"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.33.tgz#bf00703d62a7c65238136578c352d6c5c042a545"
+
+elliptic@^6.0.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
+ dependencies:
+ bn.js "^4.4.0"
+ brorand "^1.0.1"
+ hash.js "^1.0.0"
+ hmac-drbg "^1.0.0"
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+ minimalistic-crypto-utils "^1.0.0"
+
+emojis-list@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
+
+encodeurl@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+
+end-of-stream@^1.0.0, end-of-stream@^1.1.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
+ dependencies:
+ once "^1.4.0"
+
+enhanced-resolve@^3.4.0:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e"
+ dependencies:
+ graceful-fs "^4.1.2"
+ memory-fs "^0.4.0"
+ object-assign "^4.0.1"
+ tapable "^0.2.7"
+
+errno@^0.1.3, errno@^0.1.4:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.6.tgz#c386ce8a6283f14fc09563b71560908c9bf53026"
+ dependencies:
+ prr "~1.0.1"
+
+error-ex@^1.2.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
+ dependencies:
+ is-arrayish "^0.2.1"
+
+es-abstract@^1.7.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864"
+ dependencies:
+ es-to-primitive "^1.1.1"
+ function-bind "^1.1.1"
+ has "^1.0.1"
+ is-callable "^1.1.3"
+ is-regex "^1.0.4"
+
+es-to-primitive@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
+ dependencies:
+ is-callable "^1.1.1"
+ is-date-object "^1.0.1"
+ is-symbol "^1.0.1"
+
+es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
+ version "0.10.38"
+ resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.38.tgz#fa7d40d65bbc9bb8a67e1d3f9cc656a00530eed3"
+ dependencies:
+ es6-iterator "~2.0.3"
+ es6-symbol "~3.1.1"
+
+es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
+ dependencies:
+ d "1"
+ es5-ext "^0.10.35"
+ es6-symbol "^3.1.1"
+
+es6-map@^0.1.3:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
+ dependencies:
+ d "1"
+ es5-ext "~0.10.14"
+ es6-iterator "~2.0.1"
+ es6-set "~0.1.5"
+ es6-symbol "~3.1.1"
+ event-emitter "~0.3.5"
+
+es6-set@~0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"
+ dependencies:
+ d "1"
+ es5-ext "~0.10.14"
+ es6-iterator "~2.0.1"
+ es6-symbol "3.1.1"
+ event-emitter "~0.3.5"
+
+es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
+ dependencies:
+ d "1"
+ es5-ext "~0.10.14"
+
+es6-weak-map@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f"
+ dependencies:
+ d "1"
+ es5-ext "^0.10.14"
+ es6-iterator "^2.0.1"
+ es6-symbol "^3.1.1"
+
+escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+
+escope@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3"
+ dependencies:
+ es6-map "^0.1.3"
+ es6-weak-map "^2.0.1"
+ esrecurse "^4.1.0"
+ estraverse "^4.1.1"
+
+esprima@^2.6.0:
+ version "2.7.3"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
+
+esprima@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
+
+esrecurse@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163"
+ dependencies:
+ estraverse "^4.1.0"
+ object-assign "^4.0.1"
+
+estraverse@^4.1.0, estraverse@^4.1.1:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
+
+esutils@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
+
+etag@~1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+
+event-emitter@~0.3.5:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
+ dependencies:
+ d "1"
+ es5-ext "~0.10.14"
+
+eventemitter3@1.x.x:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
+
+events@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
+
+eventsource@0.1.6:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232"
+ dependencies:
+ original ">=0.0.5"
+
+evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
+ dependencies:
+ md5.js "^1.3.4"
+ safe-buffer "^5.1.1"
+
+execa@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
+ dependencies:
+ cross-spawn "^5.0.1"
+ get-stream "^3.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
+expand-brackets@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
+ dependencies:
+ is-posix-bracket "^0.1.0"
+
+expand-brackets@^2.1.4:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
+ dependencies:
+ debug "^2.3.3"
+ define-property "^0.2.5"
+ extend-shallow "^2.0.1"
+ posix-character-classes "^0.1.0"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.1"
+
+expand-range@^1.8.1:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
+ dependencies:
+ fill-range "^2.1.0"
+
+express@^4.16.2:
+ version "4.16.2"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c"
+ dependencies:
+ accepts "~1.3.4"
+ array-flatten "1.1.1"
+ body-parser "1.18.2"
+ content-disposition "0.5.2"
+ content-type "~1.0.4"
+ cookie "0.3.1"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "~1.1.1"
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ finalhandler "1.1.0"
+ fresh "0.5.2"
+ merge-descriptors "1.0.1"
+ methods "~1.1.2"
+ on-finished "~2.3.0"
+ parseurl "~1.3.2"
+ path-to-regexp "0.1.7"
+ proxy-addr "~2.0.2"
+ qs "6.5.1"
+ range-parser "~1.2.0"
+ safe-buffer "5.1.1"
+ send "0.16.1"
+ serve-static "1.13.1"
+ setprototypeof "1.1.0"
+ statuses "~1.3.1"
+ type-is "~1.6.15"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
+
+extend-shallow@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
+ dependencies:
+ is-extendable "^0.1.0"
+
+extend-shallow@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8"
+ dependencies:
+ assign-symbols "^1.0.0"
+ is-extendable "^1.0.1"
+
+extend@~3.0.0, extend@~3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
+
+extglob@^0.3.1:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
+ dependencies:
+ is-extglob "^1.0.0"
+
+extglob@^2.0.2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
+ dependencies:
+ array-unique "^0.3.2"
+ define-property "^1.0.0"
+ expand-brackets "^2.1.4"
+ extend-shallow "^2.0.1"
+ fragment-cache "^0.2.1"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.1"
+
+extract-text-webpack-plugin@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz#5f043eaa02f9750a9258b78c0a6e0dc1408fb2f7"
+ dependencies:
+ async "^2.4.1"
+ loader-utils "^1.1.0"
+ schema-utils "^0.3.0"
+ webpack-sources "^1.0.1"
+
+extsprintf@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
+
+extsprintf@^1.2.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
+
+fast-deep-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
+
+fastparse@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
+
+faye-websocket@^0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+faye-websocket@~0.11.0:
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38"
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+file-loader@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.6.tgz#7b9a8f2c58f00a77fddf49e940f7ac978a3ea0e8"
+ dependencies:
+ loader-utils "^1.0.2"
+ schema-utils "^0.3.0"
+
+filename-regex@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
+
+fill-range@^2.1.0:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
+ dependencies:
+ is-number "^2.1.0"
+ isobject "^2.0.0"
+ randomatic "^1.1.3"
+ repeat-element "^1.1.2"
+ repeat-string "^1.5.2"
+
+fill-range@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
+ dependencies:
+ extend-shallow "^2.0.1"
+ is-number "^3.0.0"
+ repeat-string "^1.6.1"
+ to-regex-range "^2.1.0"
+
+finalhandler@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5"
+ dependencies:
+ debug "2.6.9"
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ on-finished "~2.3.0"
+ parseurl "~1.3.2"
+ statuses "~1.3.1"
+ unpipe "~1.0.0"
+
+find-cache-dir@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
+ dependencies:
+ commondir "^1.0.1"
+ make-dir "^1.0.0"
+ pkg-dir "^2.0.0"
+
+find-up@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
+ dependencies:
+ path-exists "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+find-up@^2.0.0, find-up@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
+ dependencies:
+ locate-path "^2.0.0"
+
+flatten@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
+
+flush-write-stream@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417"
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "^2.0.4"
+
+for-in@^0.1.3:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
+
+for-in@^1.0.1, for-in@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
+
+for-own@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
+ dependencies:
+ for-in "^1.0.1"
+
+for-own@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b"
+ dependencies:
+ for-in "^1.0.1"
+
+foreach@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
+
+forever-agent@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
+
+form-data@~2.1.1:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.5"
+ mime-types "^2.1.12"
+
+form-data@~2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.5"
+ mime-types "^2.1.12"
+
+forwarded@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
+
+fragment-cache@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
+ dependencies:
+ map-cache "^0.2.2"
+
+fresh@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+
+from2@^2.1.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+
+fs-extra@^0.30.0:
+ version "0.30.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0"
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^2.1.0"
+ klaw "^1.0.0"
+ path-is-absolute "^1.0.0"
+ rimraf "^2.2.8"
+
+fs-write-stream-atomic@^1.0.8:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
+ dependencies:
+ graceful-fs "^4.1.2"
+ iferr "^0.1.5"
+ imurmurhash "^0.1.4"
+ readable-stream "1 || 2"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+
+fsevents@^1.0.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8"
+ dependencies:
+ nan "^2.3.0"
+ node-pre-gyp "^0.6.39"
+
+fstream-ignore@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
+ dependencies:
+ fstream "^1.0.0"
+ inherits "2"
+ minimatch "^3.0.0"
+
+fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
+ dependencies:
+ graceful-fs "^4.1.2"
+ inherits "~2.0.0"
+ mkdirp ">=0.5 0"
+ rimraf "2"
+
+function-bind@^1.0.2, function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+
+gauge@~2.7.3:
+ version "2.7.4"
+ resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
+ dependencies:
+ aproba "^1.0.3"
+ console-control-strings "^1.0.0"
+ has-unicode "^2.0.0"
+ object-assign "^4.1.0"
+ signal-exit "^3.0.0"
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wide-align "^1.1.0"
+
+gaze@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.2.tgz#847224677adb8870d679257ed3388fdb61e40105"
+ dependencies:
+ globule "^1.0.0"
+
+generate-function@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
+
+generate-object-property@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
+ dependencies:
+ is-property "^1.0.0"
+
+get-caller-file@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
+
+get-stdin@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
+
+get-stream@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
+
+get-value@^2.0.3, get-value@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
+
+getpass@^0.1.1:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
+ dependencies:
+ assert-plus "^1.0.0"
+
+glob-base@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
+ dependencies:
+ glob-parent "^2.0.0"
+ is-glob "^2.0.0"
+
+glob-parent@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
+ dependencies:
+ is-glob "^2.0.0"
+
+glob-parent@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
+ dependencies:
+ is-glob "^3.1.0"
+ path-dirname "^1.0.0"
+
+glob@^6.0.4:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
+ dependencies:
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "2 || 3"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2, glob@~7.1.1:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
+ 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"
+
+globals@^9.18.0:
+ version "9.18.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
+
+globby@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
+ dependencies:
+ array-union "^1.0.1"
+ glob "^7.0.3"
+ object-assign "^4.0.1"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+globule@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09"
+ dependencies:
+ glob "~7.1.1"
+ lodash "~4.17.4"
+ minimatch "~3.0.2"
+
+graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
+ version "4.1.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
+
+handle-thing@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
+
+har-schema@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
+
+har-schema@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
+
+har-validator@~2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
+ dependencies:
+ chalk "^1.1.1"
+ commander "^2.9.0"
+ is-my-json-valid "^2.12.4"
+ pinkie-promise "^2.0.0"
+
+har-validator@~4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
+ dependencies:
+ ajv "^4.9.1"
+ har-schema "^1.0.5"
+
+har-validator@~5.0.3:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
+ dependencies:
+ ajv "^5.1.0"
+ har-schema "^2.0.0"
+
+has-ansi@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+has-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
+
+has-flag@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+
+has-unicode@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+
+has-value@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
+ dependencies:
+ get-value "^2.0.3"
+ has-values "^0.1.4"
+ isobject "^2.0.0"
+
+has-value@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177"
+ dependencies:
+ get-value "^2.0.6"
+ has-values "^1.0.0"
+ isobject "^3.0.0"
+
+has-values@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771"
+
+has-values@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f"
+ dependencies:
+ is-number "^3.0.0"
+ kind-of "^4.0.0"
+
+has@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
+ dependencies:
+ function-bind "^1.0.2"
+
+hash-base@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1"
+ dependencies:
+ inherits "^2.0.1"
+
+hash-base@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918"
+ dependencies:
+ inherits "^2.0.1"
+ safe-buffer "^5.0.1"
+
+hash.js@^1.0.0, hash.js@^1.0.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846"
+ dependencies:
+ inherits "^2.0.3"
+ minimalistic-assert "^1.0.0"
+
+hawk@3.1.3, hawk@~3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
+ dependencies:
+ boom "2.x.x"
+ cryptiles "2.x.x"
+ hoek "2.x.x"
+ sntp "1.x.x"
+
+hawk@~6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
+ dependencies:
+ boom "4.x.x"
+ cryptiles "3.x.x"
+ hoek "4.x.x"
+ sntp "2.x.x"
+
+hmac-drbg@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
+ dependencies:
+ hash.js "^1.0.3"
+ minimalistic-assert "^1.0.0"
+ minimalistic-crypto-utils "^1.0.1"
+
+hoek@2.x.x:
+ version "2.16.3"
+ resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
+
+hoek@4.x.x:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
+
+home-or-tmp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.1"
+
+hosted-git-info@^2.1.4:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
+
+hpack.js@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
+ dependencies:
+ inherits "^2.0.1"
+ obuf "^1.0.0"
+ readable-stream "^2.0.1"
+ wbuf "^1.1.0"
+
+html-comment-regex@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
+
+html-entities@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
+
+http-deceiver@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
+
+http-errors@1.6.2, http-errors@~1.6.2:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
+ dependencies:
+ depd "1.1.1"
+ inherits "2.0.3"
+ setprototypeof "1.0.3"
+ statuses ">= 1.3.1 < 2"
+
+http-parser-js@>=0.4.0:
+ version "0.4.10"
+ resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4"
+
+http-proxy-middleware@~0.17.4:
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833"
+ dependencies:
+ http-proxy "^1.16.2"
+ is-glob "^3.1.0"
+ lodash "^4.17.2"
+ micromatch "^2.3.11"
+
+http-proxy@^1.16.2:
+ version "1.16.2"
+ resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742"
+ dependencies:
+ eventemitter3 "1.x.x"
+ requires-port "1.x.x"
+
+http-signature@~1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
+ dependencies:
+ assert-plus "^0.2.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
+http-signature@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
+ dependencies:
+ assert-plus "^1.0.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
+https-browserify@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
+
+iconv-lite@0.4.19:
+ version "0.4.19"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
+
+icss-replace-symbols@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
+
+icss-utils@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962"
+ dependencies:
+ postcss "^6.0.1"
+
+ieee754@^1.1.4:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
+
+iferr@^0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
+
+import-local@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
+ dependencies:
+ pkg-dir "^2.0.0"
+ resolve-cwd "^2.0.0"
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+
+in-publish@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51"
+
+indent-string@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
+ dependencies:
+ repeating "^2.0.0"
+
+indexes-of@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
+
+indexof@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+
+inherits@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
+
+ini@~1.3.0:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
+
+internal-ip@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c"
+ dependencies:
+ meow "^3.3.0"
+
+interpret@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
+
+invariant@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
+ dependencies:
+ loose-envify "^1.0.0"
+
+invert-kv@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
+
+ip@^1.1.0, ip@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
+
+ipaddr.js@1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0"
+
+is-absolute-url@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
+
+is-accessor-descriptor@^0.1.6:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
+ dependencies:
+ kind-of "^3.0.2"
+
+is-accessor-descriptor@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656"
+ dependencies:
+ kind-of "^6.0.0"
+
+is-arrayish@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+
+is-arrayish@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.1.tgz#c2dfc386abaa0c3e33c48db3fe87059e69065efd"
+
+is-binary-path@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
+ dependencies:
+ binary-extensions "^1.0.0"
+
+is-buffer@^1.0.2, is-buffer@^1.1.5:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
+
+is-builtin-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
+ dependencies:
+ builtin-modules "^1.0.0"
+
+is-callable@^1.1.1, is-callable@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2"
+
+is-data-descriptor@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
+ dependencies:
+ kind-of "^3.0.2"
+
+is-data-descriptor@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7"
+ dependencies:
+ kind-of "^6.0.0"
+
+is-date-object@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
+
+is-descriptor@^0.1.0:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
+ dependencies:
+ is-accessor-descriptor "^0.1.6"
+ is-data-descriptor "^0.1.4"
+ kind-of "^5.0.0"
+
+is-descriptor@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec"
+ dependencies:
+ is-accessor-descriptor "^1.0.0"
+ is-data-descriptor "^1.0.0"
+ kind-of "^6.0.2"
+
+is-directory@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
+
+is-dotfile@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
+
+is-equal-shallow@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
+ dependencies:
+ is-primitive "^2.0.0"
+
+is-extendable@^0.1.0, is-extendable@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
+
+is-extendable@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4"
+ dependencies:
+ is-plain-object "^2.0.4"
+
+is-extglob@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
+
+is-extglob@^2.1.0, is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+
+is-finite@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+
+is-glob@^2.0.0, is-glob@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
+ dependencies:
+ is-extglob "^1.0.0"
+
+is-glob@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
+ dependencies:
+ is-extglob "^2.1.0"
+
+is-glob@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-my-json-valid@^2.12.4:
+ version "2.17.1"
+ resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz#3da98914a70a22f0a8563ef1511a246c6fc55471"
+ dependencies:
+ generate-function "^2.0.0"
+ generate-object-property "^1.1.0"
+ jsonpointer "^4.0.0"
+ xtend "^4.0.0"
+
+is-number@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
+ dependencies:
+ kind-of "^3.0.2"
+
+is-number@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
+ dependencies:
+ kind-of "^3.0.2"
+
+is-odd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-1.0.0.tgz#3b8a932eb028b3775c39bb09e91767accdb69088"
+ dependencies:
+ is-number "^3.0.0"
+
+is-path-cwd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
+
+is-path-in-cwd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc"
+ dependencies:
+ is-path-inside "^1.0.0"
+
+is-path-inside@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
+ dependencies:
+ path-is-inside "^1.0.1"
+
+is-plain-obj@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
+
+is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
+ dependencies:
+ isobject "^3.0.1"
+
+is-posix-bracket@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
+
+is-primitive@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
+
+is-property@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
+
+is-regex@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
+ dependencies:
+ has "^1.0.1"
+
+is-stream@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+
+is-svg@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9"
+ dependencies:
+ html-comment-regex "^1.1.0"
+
+is-symbol@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
+
+is-typedarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+
+is-utf8@^0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
+
+is-wsl@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
+
+isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+
+isnumeric@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/isnumeric/-/isnumeric-0.2.0.tgz#a2347ba360de19e33d0ffd590fddf7755cbf2e64"
+
+isobject@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
+ dependencies:
+ isarray "1.0.0"
+
+isobject@^3.0.0, isobject@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
+
+isstream@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
+
+js-base64@^2.1.8, js-base64@^2.1.9:
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582"
+
+js-tokens@^3.0.0, js-tokens@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
+
+js-yaml@^3.10.0, js-yaml@^3.4.3:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
+js-yaml@~3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^2.6.0"
+
+jsbn@~0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
+
+jsesc@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
+
+jsesc@~0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
+
+json-loader@^0.5.4:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
+
+json-schema-traverse@^0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
+
+json-schema@0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
+
+json-stable-stringify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
+ dependencies:
+ jsonify "~0.0.0"
+
+json-stringify-safe@~5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+
+json3@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
+
+json5@^0.5.0, json5@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+
+jsonfile@^2.1.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+jsonify@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
+
+jsonpointer@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
+
+jsprim@^1.2.2:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
+ dependencies:
+ assert-plus "1.0.0"
+ extsprintf "1.3.0"
+ json-schema "0.2.3"
+ verror "1.10.0"
+
+killable@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b"
+
+kind-of@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5"
+ dependencies:
+ is-buffer "^1.0.2"
+
+kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0, kind-of@^3.2.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
+ dependencies:
+ is-buffer "^1.1.5"
+
+kind-of@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
+ dependencies:
+ is-buffer "^1.1.5"
+
+kind-of@^5.0.0, kind-of@^5.0.2:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
+
+kind-of@^6.0.0, kind-of@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
+
+klaw@^1.0.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
+ optionalDependencies:
+ graceful-fs "^4.1.9"
+
+lazy-cache@^0.2.3:
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65"
+
+lazy-cache@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
+
+lazy-cache@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
+ dependencies:
+ set-getter "^0.1.0"
+
+lcid@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
+ dependencies:
+ invert-kv "^1.0.0"
+
+load-json-file@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^2.2.0"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+ strip-bom "^2.0.0"
+
+load-json-file@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^2.2.0"
+ pify "^2.0.0"
+ strip-bom "^3.0.0"
+
+loader-runner@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
+
+loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
+ dependencies:
+ big.js "^3.1.3"
+ emojis-list "^2.0.0"
+ json5 "^0.5.0"
+
+locate-path@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
+ dependencies:
+ p-locate "^2.0.0"
+ path-exists "^3.0.0"
+
+lodash._reinterpolate@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
+
+lodash.assign@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
+
+lodash.camelcase@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+
+lodash.clonedeep@^4.3.2:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+
+lodash.memoize@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+
+lodash.mergewith@^4.6.0:
+ version "4.6.1"
+ resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
+
+lodash.tail@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
+
+lodash.template@^4.2.4:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0"
+ dependencies:
+ lodash._reinterpolate "~3.0.0"
+ lodash.templatesettings "^4.0.0"
+
+lodash.templatesettings@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316"
+ dependencies:
+ lodash._reinterpolate "~3.0.0"
+
+lodash.uniq@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
+
+lodash@3.x:
+ version "3.10.1"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
+
+"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@~4.17.4:
+ version "4.17.5"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
+
+loglevel@^1.4.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa"
+
+longest@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
+
+loose-envify@^1.0.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
+ dependencies:
+ js-tokens "^3.0.0"
+
+loud-rejection@^1.0.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
+ dependencies:
+ currently-unhandled "^0.4.1"
+ signal-exit "^3.0.0"
+
+lru-cache@^4.0.1, lru-cache@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
+ dependencies:
+ pseudomap "^1.0.2"
+ yallist "^2.1.2"
+
+macaddress@^0.2.8:
+ version "0.2.8"
+ resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
+
+make-dir@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51"
+ dependencies:
+ pify "^3.0.0"
+
+map-cache@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
+
+map-obj@^1.0.0, map-obj@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
+
+map-visit@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
+ dependencies:
+ object-visit "^1.0.0"
+
+math-expression-evaluator@^1.2.14:
+ version "1.2.17"
+ resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
+
+md5.js@^1.3.4:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d"
+ dependencies:
+ hash-base "^3.0.0"
+ inherits "^2.0.1"
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+
+mem@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
+ dependencies:
+ mimic-fn "^1.0.0"
+
+memory-fs@^0.4.0, memory-fs@~0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
+ dependencies:
+ errno "^0.1.3"
+ readable-stream "^2.0.1"
+
+meow@^3.3.0, meow@^3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
+ dependencies:
+ camelcase-keys "^2.0.0"
+ decamelize "^1.1.2"
+ loud-rejection "^1.0.0"
+ map-obj "^1.0.1"
+ minimist "^1.1.3"
+ normalize-package-data "^2.3.4"
+ object-assign "^4.0.1"
+ read-pkg-up "^1.0.1"
+ redent "^1.0.0"
+ trim-newlines "^1.0.0"
+
+merge-descriptors@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+
+methods@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+
+micromatch@^2.1.5, micromatch@^2.3.11:
+ version "2.3.11"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
+ dependencies:
+ arr-diff "^2.0.0"
+ array-unique "^0.2.1"
+ braces "^1.8.2"
+ expand-brackets "^0.1.4"
+ extglob "^0.3.1"
+ filename-regex "^2.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.1"
+ kind-of "^3.0.2"
+ normalize-path "^2.0.1"
+ object.omit "^2.0.0"
+ parse-glob "^3.0.4"
+ regex-cache "^0.4.2"
+
+micromatch@^3.1.4:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.5.tgz#d05e168c206472dfbca985bfef4f57797b4cd4ba"
+ dependencies:
+ arr-diff "^4.0.0"
+ array-unique "^0.3.2"
+ braces "^2.3.0"
+ define-property "^1.0.0"
+ extend-shallow "^2.0.1"
+ extglob "^2.0.2"
+ fragment-cache "^0.2.1"
+ kind-of "^6.0.0"
+ nanomatch "^1.2.5"
+ object.pick "^1.3.0"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.1"
+
+miller-rabin@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
+ dependencies:
+ bn.js "^4.0.0"
+ brorand "^1.0.1"
+
+"mime-db@>= 1.30.0 < 2":
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.32.0.tgz#485b3848b01a3cda5f968b4882c0771e58e09414"
+
+mime-db@~1.30.0:
+ version "1.30.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
+
+mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7:
+ version "2.1.17"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
+ dependencies:
+ mime-db "~1.30.0"
+
+mime@1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
+
+mime@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+
+mimic-fn@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
+
+minimalistic-assert@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3"
+
+minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
+
+"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+
+minimist@^1.1.3, minimist@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
+
+mississippi@^1.3.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-1.3.1.tgz#2a8bb465e86550ac8b36a7b6f45599171d78671e"
+ dependencies:
+ concat-stream "^1.5.0"
+ duplexify "^3.4.2"
+ end-of-stream "^1.1.0"
+ flush-write-stream "^1.0.0"
+ from2 "^2.1.0"
+ parallel-transform "^1.1.0"
+ pump "^1.0.0"
+ pumpify "^1.3.3"
+ stream-each "^1.1.0"
+ through2 "^2.0.0"
+
+mixin-deep@^1.2.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
+ dependencies:
+ for-in "^1.0.2"
+ is-extendable "^1.0.1"
+
+mixin-object@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e"
+ dependencies:
+ for-in "^0.1.3"
+ is-extendable "^0.1.1"
+
+mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+ dependencies:
+ minimist "0.0.8"
+
+move-concurrently@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
+ dependencies:
+ aproba "^1.1.1"
+ copy-concurrently "^1.0.0"
+ fs-write-stream-atomic "^1.0.8"
+ mkdirp "^0.5.1"
+ rimraf "^2.5.4"
+ run-queue "^1.0.3"
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+
+multicast-dns-service-types@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
+
+multicast-dns@^6.0.1:
+ version "6.2.3"
+ resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229"
+ dependencies:
+ dns-packet "^1.3.1"
+ thunky "^1.0.2"
+
+nan@^2.3.0, nan@^2.3.2:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
+
+nanomatch@^1.2.5:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.7.tgz#53cd4aa109ff68b7f869591fdc9d10daeeea3e79"
+ dependencies:
+ arr-diff "^4.0.0"
+ array-unique "^0.3.2"
+ define-property "^1.0.0"
+ extend-shallow "^2.0.1"
+ fragment-cache "^0.2.1"
+ is-odd "^1.0.0"
+ kind-of "^5.0.2"
+ object.pick "^1.3.0"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.1"
+
+negotiator@0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
+
+node-forge@0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300"
+
+node-gyp@^3.3.1:
+ version "3.6.2"
+ resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60"
+ dependencies:
+ fstream "^1.0.0"
+ glob "^7.0.3"
+ graceful-fs "^4.1.2"
+ minimatch "^3.0.2"
+ mkdirp "^0.5.0"
+ nopt "2 || 3"
+ npmlog "0 || 1 || 2 || 3 || 4"
+ osenv "0"
+ request "2"
+ rimraf "2"
+ semver "~5.3.0"
+ tar "^2.0.0"
+ which "1"
+
+node-libs-browser@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df"
+ dependencies:
+ assert "^1.1.1"
+ browserify-zlib "^0.2.0"
+ buffer "^4.3.0"
+ console-browserify "^1.1.0"
+ constants-browserify "^1.0.0"
+ crypto-browserify "^3.11.0"
+ domain-browser "^1.1.1"
+ events "^1.0.0"
+ https-browserify "^1.0.0"
+ os-browserify "^0.3.0"
+ path-browserify "0.0.0"
+ process "^0.11.10"
+ punycode "^1.2.4"
+ querystring-es3 "^0.2.0"
+ readable-stream "^2.3.3"
+ stream-browserify "^2.0.1"
+ stream-http "^2.7.2"
+ string_decoder "^1.0.0"
+ timers-browserify "^2.0.4"
+ tty-browserify "0.0.0"
+ url "^0.11.0"
+ util "^0.10.3"
+ vm-browserify "0.0.4"
+
+node-pre-gyp@^0.6.39:
+ version "0.6.39"
+ resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649"
+ dependencies:
+ detect-libc "^1.0.2"
+ hawk "3.1.3"
+ mkdirp "^0.5.1"
+ nopt "^4.0.1"
+ npmlog "^4.0.2"
+ rc "^1.1.7"
+ request "2.81.0"
+ rimraf "^2.6.1"
+ semver "^5.3.0"
+ tar "^2.2.1"
+ tar-pack "^3.4.0"
+
+node-sass@^4.7.2:
+ version "4.7.2"
+ resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e"
+ dependencies:
+ async-foreach "^0.1.3"
+ chalk "^1.1.1"
+ cross-spawn "^3.0.0"
+ gaze "^1.0.0"
+ get-stdin "^4.0.1"
+ glob "^7.0.3"
+ in-publish "^2.0.0"
+ lodash.assign "^4.2.0"
+ lodash.clonedeep "^4.3.2"
+ lodash.mergewith "^4.6.0"
+ meow "^3.7.0"
+ mkdirp "^0.5.1"
+ nan "^2.3.2"
+ node-gyp "^3.3.1"
+ npmlog "^4.0.0"
+ request "~2.79.0"
+ sass-graph "^2.2.4"
+ stdout-stream "^1.4.0"
+ "true-case-path" "^1.0.2"
+
+"nopt@2 || 3":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
+ dependencies:
+ abbrev "1"
+
+nopt@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
+ dependencies:
+ abbrev "1"
+ osenv "^0.1.4"
+
+normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
+ dependencies:
+ hosted-git-info "^2.1.4"
+ is-builtin-module "^1.0.0"
+ semver "2 || 3 || 4 || 5"
+ validate-npm-package-license "^3.0.1"
+
+normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
+ dependencies:
+ remove-trailing-separator "^1.0.1"
+
+normalize-range@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
+
+normalize-url@^1.4.0:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
+ dependencies:
+ object-assign "^4.0.1"
+ prepend-http "^1.0.0"
+ query-string "^4.1.0"
+ sort-keys "^1.0.0"
+
+npm-run-path@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
+ dependencies:
+ path-key "^2.0.0"
+
+"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
+ dependencies:
+ are-we-there-yet "~1.1.2"
+ console-control-strings "~1.1.0"
+ gauge "~2.7.3"
+ set-blocking "~2.0.0"
+
+num2fraction@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
+
+number-is-nan@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+
+oauth-sign@~0.8.1, oauth-sign@~0.8.2:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
+
+object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+
+object-copy@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
+ dependencies:
+ copy-descriptor "^0.1.0"
+ define-property "^0.2.5"
+ kind-of "^3.0.3"
+
+object-keys@^1.0.8:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
+
+object-visit@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
+ dependencies:
+ isobject "^3.0.0"
+
+object.omit@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
+ dependencies:
+ for-own "^0.1.4"
+ is-extendable "^0.1.1"
+
+object.pick@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
+ dependencies:
+ isobject "^3.0.1"
+
+obuf@^1.0.0, obuf@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e"
+
+on-finished@~2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+ dependencies:
+ ee-first "1.1.1"
+
+on-headers@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
+
+once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ dependencies:
+ wrappy "1"
+
+onecolor@^3.0.4:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/onecolor/-/onecolor-3.0.5.tgz#36eff32201379efdf1180fb445e51a8e2425f9f6"
+
+opn@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225"
+ dependencies:
+ is-wsl "^1.1.0"
+
+original@>=0.0.5:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b"
+ dependencies:
+ url-parse "1.0.x"
+
+os-browserify@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
+
+os-homedir@^1.0.0, os-homedir@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
+
+os-locale@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
+ dependencies:
+ lcid "^1.0.0"
+
+os-locale@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
+ dependencies:
+ execa "^0.7.0"
+ lcid "^1.0.0"
+ mem "^1.1.0"
+
+os-tmpdir@^1.0.0, os-tmpdir@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+
+osenv@0, osenv@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.0"
+
+p-finally@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+
+p-limit@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
+ dependencies:
+ p-try "^1.0.0"
+
+p-locate@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
+ dependencies:
+ p-limit "^1.1.0"
+
+p-map@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
+
+p-try@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
+
+pako@~1.0.5:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
+
+parallel-transform@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
+ dependencies:
+ cyclist "~0.2.2"
+ inherits "^2.0.3"
+ readable-stream "^2.1.5"
+
+parse-asn1@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712"
+ dependencies:
+ asn1.js "^4.0.0"
+ browserify-aes "^1.0.0"
+ create-hash "^1.1.0"
+ evp_bytestokey "^1.0.0"
+ pbkdf2 "^3.0.3"
+
+parse-glob@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
+ dependencies:
+ glob-base "^0.3.0"
+ is-dotfile "^1.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.0"
+
+parse-json@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
+ dependencies:
+ error-ex "^1.2.0"
+
+parseurl@~1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
+
+pascalcase@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
+
+path-browserify@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
+
+path-complete-extname@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/path-complete-extname/-/path-complete-extname-0.1.0.tgz#c454702669f31452f8193aa6168915fa31692f4a"
+
+path-dirname@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
+
+path-exists@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
+ dependencies:
+ pinkie-promise "^2.0.0"
+
+path-exists@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+
+path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+
+path-is-inside@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
+
+path-key@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
+
+path-parse@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
+
+path-to-regexp@0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+
+path-type@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
+ dependencies:
+ graceful-fs "^4.1.2"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+path-type@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
+ dependencies:
+ pify "^2.0.0"
+
+pbkdf2@^3.0.3:
+ version "3.0.14"
+ resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade"
+ dependencies:
+ create-hash "^1.1.2"
+ create-hmac "^1.1.4"
+ ripemd160 "^2.0.1"
+ safe-buffer "^5.0.1"
+ sha.js "^2.4.8"
+
+performance-now@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
+
+performance-now@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+
+pify@^2.0.0, pify@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+
+pify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+
+pinkie-promise@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
+ dependencies:
+ pinkie "^2.0.0"
+
+pinkie@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
+
+pixrem@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/pixrem/-/pixrem-4.0.1.tgz#2da4a1de6ec4423c5fc3794e930b81d4490ec686"
+ dependencies:
+ browserslist "^2.0.0"
+ postcss "^6.0.0"
+ reduce-css-calc "^1.2.7"
+
+pkg-dir@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
+ dependencies:
+ find-up "^2.1.0"
+
+pleeease-filters@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/pleeease-filters/-/pleeease-filters-4.0.0.tgz#6632b2fb05648d2758d865384fbced79e1ccaec7"
+ dependencies:
+ onecolor "^3.0.4"
+ postcss "^6.0.1"
+
+portfinder@^1.0.9:
+ version "1.0.13"
+ resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
+ dependencies:
+ async "^1.5.2"
+ debug "^2.2.0"
+ mkdirp "0.5.x"
+
+posix-character-classes@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
+
+postcss-apply@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/postcss-apply/-/postcss-apply-0.8.0.tgz#14e544bbb5cb6f1c1e048857965d79ae066b1343"
+ dependencies:
+ babel-runtime "^6.23.0"
+ balanced-match "^0.4.2"
+ postcss "^6.0.0"
+
+postcss-attribute-case-insensitive@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-2.0.0.tgz#94dc422c8f90997f16bd33a3654bbbec084963b4"
+ dependencies:
+ postcss "^6.0.0"
+ postcss-selector-parser "^2.2.3"
+
+postcss-calc@^5.2.0:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e"
+ dependencies:
+ postcss "^5.0.2"
+ postcss-message-helpers "^2.0.0"
+ reduce-css-calc "^1.2.6"
+
+postcss-calc@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-6.0.1.tgz#3d24171bbf6e7629d422a436ebfe6dd9511f4330"
+ dependencies:
+ css-unit-converter "^1.1.1"
+ postcss "^6.0.0"
+ postcss-selector-parser "^2.2.2"
+ reduce-css-calc "^2.0.0"
+
+postcss-color-function@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-color-function/-/postcss-color-function-4.0.1.tgz#402b3f2cebc3f6947e618fb6be3654fbecef6444"
+ dependencies:
+ css-color-function "~1.3.3"
+ postcss "^6.0.1"
+ postcss-message-helpers "^2.0.0"
+ postcss-value-parser "^3.3.0"
+
+postcss-color-gray@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-4.1.0.tgz#e5581ed57eaa826fb652ca11b1e2b7b136a9f9df"
+ dependencies:
+ color "^2.0.1"
+ postcss "^6.0.14"
+ postcss-message-helpers "^2.0.0"
+ reduce-function-call "^1.0.2"
+
+postcss-color-hex-alpha@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-3.0.0.tgz#1e53e6c8acb237955e8fd08b7ecdb1b8b8309f95"
+ dependencies:
+ color "^1.0.3"
+ postcss "^6.0.1"
+ postcss-message-helpers "^2.0.0"
+
+postcss-color-hsl@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-hsl/-/postcss-color-hsl-2.0.0.tgz#12703666fa310430e3f30a454dac1386317d5844"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-value-parser "^3.3.0"
+ units-css "^0.4.0"
+
+postcss-color-hwb@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-hwb/-/postcss-color-hwb-3.0.0.tgz#3402b19ef4d8497540c1fb5072be9863ca95571e"
+ dependencies:
+ color "^1.0.3"
+ postcss "^6.0.1"
+ postcss-message-helpers "^2.0.0"
+ reduce-function-call "^1.0.2"
+
+postcss-color-rebeccapurple@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-3.0.0.tgz#eebaf03d363b4300b96792bd3081c19ed66513d3"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-value-parser "^3.3.0"
+
+postcss-color-rgb@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-rgb/-/postcss-color-rgb-2.0.0.tgz#14539c8a7131494b482e0dd1cc265ff6514b5263"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-value-parser "^3.3.0"
+
+postcss-color-rgba-fallback@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-color-rgba-fallback/-/postcss-color-rgba-fallback-3.0.0.tgz#37d5c9353a07a09270912a82606bb42a0d702c04"
+ dependencies:
+ postcss "^6.0.6"
+ postcss-value-parser "^3.3.0"
+ rgb-hex "^2.1.0"
+
+postcss-colormin@^2.1.8:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b"
+ dependencies:
+ colormin "^1.0.5"
+ postcss "^5.0.13"
+ postcss-value-parser "^3.2.3"
+
+postcss-convert-values@^2.3.4:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d"
+ dependencies:
+ postcss "^5.0.11"
+ postcss-value-parser "^3.1.2"
+
+postcss-cssnext@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-cssnext/-/postcss-cssnext-3.1.0.tgz#927dc29341a938254cde38ea60a923b9dfedead9"
+ dependencies:
+ autoprefixer "^7.1.1"
+ caniuse-api "^2.0.0"
+ chalk "^2.0.1"
+ pixrem "^4.0.0"
+ pleeease-filters "^4.0.0"
+ postcss "^6.0.5"
+ postcss-apply "^0.8.0"
+ postcss-attribute-case-insensitive "^2.0.0"
+ postcss-calc "^6.0.0"
+ postcss-color-function "^4.0.0"
+ postcss-color-gray "^4.0.0"
+ postcss-color-hex-alpha "^3.0.0"
+ postcss-color-hsl "^2.0.0"
+ postcss-color-hwb "^3.0.0"
+ postcss-color-rebeccapurple "^3.0.0"
+ postcss-color-rgb "^2.0.0"
+ postcss-color-rgba-fallback "^3.0.0"
+ postcss-custom-media "^6.0.0"
+ postcss-custom-properties "^6.1.0"
+ postcss-custom-selectors "^4.0.1"
+ postcss-font-family-system-ui "^3.0.0"
+ postcss-font-variant "^3.0.0"
+ postcss-image-set-polyfill "^0.3.5"
+ postcss-initial "^2.0.0"
+ postcss-media-minmax "^3.0.0"
+ postcss-nesting "^4.0.1"
+ postcss-pseudo-class-any-link "^4.0.0"
+ postcss-pseudoelements "^5.0.0"
+ postcss-replace-overflow-wrap "^2.0.0"
+ postcss-selector-matches "^3.0.1"
+ postcss-selector-not "^3.0.1"
+
+postcss-custom-media@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-6.0.0.tgz#be532784110ecb295044fb5395a18006eb21a737"
+ dependencies:
+ postcss "^6.0.1"
+
+postcss-custom-properties@^6.1.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-6.2.0.tgz#5d929a7f06e9b84e0f11334194c0ba9a30acfbe9"
+ dependencies:
+ balanced-match "^1.0.0"
+ postcss "^6.0.13"
+
+postcss-custom-selectors@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-4.0.1.tgz#781382f94c52e727ef5ca4776ea2adf49a611382"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-selector-matches "^3.0.0"
+
+postcss-discard-comments@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d"
+ dependencies:
+ postcss "^5.0.14"
+
+postcss-discard-duplicates@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932"
+ dependencies:
+ postcss "^5.0.4"
+
+postcss-discard-empty@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5"
+ dependencies:
+ postcss "^5.0.14"
+
+postcss-discard-overridden@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58"
+ dependencies:
+ postcss "^5.0.16"
+
+postcss-discard-unused@^2.2.1:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433"
+ dependencies:
+ postcss "^5.0.14"
+ uniqs "^2.0.0"
+
+postcss-filter-plugins@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c"
+ dependencies:
+ postcss "^5.0.4"
+ uniqid "^4.0.0"
+
+postcss-font-family-system-ui@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-font-family-system-ui/-/postcss-font-family-system-ui-3.0.0.tgz#675fe7a9e029669f05f8dba2e44c2225ede80623"
+ dependencies:
+ postcss "^6.0"
+
+postcss-font-variant@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-3.0.0.tgz#08ccc88f6050ba82ed8ef2cc76c0c6a6b41f183e"
+ dependencies:
+ postcss "^6.0.1"
+
+postcss-image-set-polyfill@^0.3.5:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/postcss-image-set-polyfill/-/postcss-image-set-polyfill-0.3.5.tgz#0f193413700cf1f82bd39066ef016d65a4a18181"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-media-query-parser "^0.2.3"
+
+postcss-import@^11.0.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-11.1.0.tgz#55c9362c9192994ec68865d224419df1db2981f0"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-value-parser "^3.2.3"
+ read-cache "^1.0.0"
+ resolve "^1.1.7"
+
+postcss-initial@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-2.0.0.tgz#72715f7336e0bb79351d99ee65c4a253a8441ba4"
+ dependencies:
+ lodash.template "^4.2.4"
+ postcss "^6.0.1"
+
+postcss-load-config@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-1.2.0.tgz#539e9afc9ddc8620121ebf9d8c3673e0ce50d28a"
+ dependencies:
+ cosmiconfig "^2.1.0"
+ object-assign "^4.1.0"
+ postcss-load-options "^1.2.0"
+ postcss-load-plugins "^2.3.0"
+
+postcss-load-options@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-load-options/-/postcss-load-options-1.2.0.tgz#b098b1559ddac2df04bc0bb375f99a5cfe2b6d8c"
+ dependencies:
+ cosmiconfig "^2.1.0"
+ object-assign "^4.1.0"
+
+postcss-load-plugins@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz#745768116599aca2f009fad426b00175049d8d92"
+ dependencies:
+ cosmiconfig "^2.1.1"
+ object-assign "^4.1.0"
+
+postcss-loader@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.0.tgz#038c2d6d59753fef4667827fd3ae03f5dc5e6a7a"
+ dependencies:
+ loader-utils "^1.1.0"
+ postcss "^6.0.0"
+ postcss-load-config "^1.2.0"
+ schema-utils "^0.4.0"
+
+postcss-media-minmax@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-3.0.0.tgz#675256037a43ef40bc4f0760bfd06d4dc69d48d2"
+ dependencies:
+ postcss "^6.0.1"
+
+postcss-media-query-parser@^0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244"
+
+postcss-merge-idents@^2.1.5:
+ version "2.1.7"
+ resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270"
+ dependencies:
+ has "^1.0.1"
+ postcss "^5.0.10"
+ postcss-value-parser "^3.1.1"
+
+postcss-merge-longhand@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658"
+ dependencies:
+ postcss "^5.0.4"
+
+postcss-merge-rules@^2.0.3:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721"
+ dependencies:
+ browserslist "^1.5.2"
+ caniuse-api "^1.5.2"
+ postcss "^5.0.4"
+ postcss-selector-parser "^2.2.2"
+ vendors "^1.0.0"
+
+postcss-message-helpers@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e"
+
+postcss-minify-font-values@^1.0.2:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69"
+ dependencies:
+ object-assign "^4.0.1"
+ postcss "^5.0.4"
+ postcss-value-parser "^3.0.2"
+
+postcss-minify-gradients@^1.0.1:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1"
+ dependencies:
+ postcss "^5.0.12"
+ postcss-value-parser "^3.3.0"
+
+postcss-minify-params@^1.0.4:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3"
+ dependencies:
+ alphanum-sort "^1.0.1"
+ postcss "^5.0.2"
+ postcss-value-parser "^3.0.2"
+ uniqs "^2.0.0"
+
+postcss-minify-selectors@^2.0.4:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf"
+ dependencies:
+ alphanum-sort "^1.0.2"
+ has "^1.0.1"
+ postcss "^5.0.14"
+ postcss-selector-parser "^2.0.0"
+
+postcss-modules-extract-imports@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85"
+ dependencies:
+ postcss "^6.0.1"
+
+postcss-modules-local-by-default@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069"
+ dependencies:
+ css-selector-tokenizer "^0.7.0"
+ postcss "^6.0.1"
+
+postcss-modules-scope@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90"
+ dependencies:
+ css-selector-tokenizer "^0.7.0"
+ postcss "^6.0.1"
+
+postcss-modules-values@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20"
+ dependencies:
+ icss-replace-symbols "^1.1.0"
+ postcss "^6.0.1"
+
+postcss-nesting@^4.0.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-4.2.1.tgz#0483bce338b3f0828ced90ff530b29b98b00300d"
+ dependencies:
+ postcss "^6.0.11"
+
+postcss-normalize-charset@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1"
+ dependencies:
+ postcss "^5.0.5"
+
+postcss-normalize-url@^3.0.7:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222"
+ dependencies:
+ is-absolute-url "^2.0.0"
+ normalize-url "^1.4.0"
+ postcss "^5.0.14"
+ postcss-value-parser "^3.2.3"
+
+postcss-ordered-values@^2.1.0:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d"
+ dependencies:
+ postcss "^5.0.4"
+ postcss-value-parser "^3.0.1"
+
+postcss-pseudo-class-any-link@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-4.0.0.tgz#9152a0613d3450720513e8892854bae42d0ee68e"
+ dependencies:
+ postcss "^6.0.1"
+ postcss-selector-parser "^2.2.3"
+
+postcss-pseudoelements@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-pseudoelements/-/postcss-pseudoelements-5.0.0.tgz#eef194e8d524645ca520a949e95e518e812402cb"
+ dependencies:
+ postcss "^6.0.0"
+
+postcss-reduce-idents@^2.2.2:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3"
+ dependencies:
+ postcss "^5.0.4"
+ postcss-value-parser "^3.0.2"
+
+postcss-reduce-initial@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea"
+ dependencies:
+ postcss "^5.0.4"
+
+postcss-reduce-transforms@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1"
+ dependencies:
+ has "^1.0.1"
+ postcss "^5.0.8"
+ postcss-value-parser "^3.0.1"
+
+postcss-replace-overflow-wrap@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-2.0.0.tgz#794db6faa54f8db100854392a93af45768b4e25b"
+ dependencies:
+ postcss "^6.0.1"
+
+postcss-selector-matches@^3.0.0, postcss-selector-matches@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-3.0.1.tgz#e5634011e13950881861bbdd58c2d0111ffc96ab"
+ dependencies:
+ balanced-match "^0.4.2"
+ postcss "^6.0.1"
+
+postcss-selector-not@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-3.0.1.tgz#2e4db2f0965336c01e7cec7db6c60dff767335d9"
+ dependencies:
+ balanced-match "^0.4.2"
+ postcss "^6.0.1"
+
+postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2, postcss-selector-parser@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90"
+ dependencies:
+ flatten "^1.0.2"
+ indexes-of "^1.0.1"
+ uniq "^1.0.1"
+
+postcss-svgo@^2.1.1:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d"
+ dependencies:
+ is-svg "^2.0.0"
+ postcss "^5.0.14"
+ postcss-value-parser "^3.2.3"
+ svgo "^0.7.0"
+
+postcss-unique-selectors@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d"
+ dependencies:
+ alphanum-sort "^1.0.1"
+ postcss "^5.0.4"
+ uniqs "^2.0.0"
+
+postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15"
+
+postcss-zindex@^2.0.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22"
+ dependencies:
+ has "^1.0.1"
+ postcss "^5.0.4"
+ uniqs "^2.0.0"
+
+postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16:
+ version "5.2.18"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5"
+ dependencies:
+ chalk "^1.1.3"
+ js-base64 "^2.1.9"
+ source-map "^0.5.6"
+ supports-color "^3.2.3"
+
+postcss@^6.0, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.13, postcss@^6.0.14, postcss@^6.0.17, postcss@^6.0.5, postcss@^6.0.6:
+ version "6.0.17"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.17.tgz#e259a051ca513f81e9afd0c21f7f82eda50c65c5"
+ dependencies:
+ chalk "^2.3.0"
+ source-map "^0.6.1"
+ supports-color "^5.1.0"
+
+prepend-http@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
+
+preserve@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
+
+private@^0.1.6, private@^0.1.7:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
+
+process-nextick-args@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
+
+process@^0.11.10:
+ version "0.11.10"
+ resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
+
+promise-inflight@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
+
+proxy-addr@~2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
+ dependencies:
+ forwarded "~0.1.2"
+ ipaddr.js "1.5.2"
+
+prr@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
+
+pseudomap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+
+public-encrypt@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6"
+ dependencies:
+ bn.js "^4.1.0"
+ browserify-rsa "^4.0.0"
+ create-hash "^1.1.0"
+ parse-asn1 "^5.0.0"
+ randombytes "^2.0.1"
+
+pump@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954"
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+pump@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+pumpify@^1.3.3:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb"
+ dependencies:
+ duplexify "^3.5.3"
+ inherits "^2.0.3"
+ pump "^2.0.0"
+
+punycode@1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
+
+punycode@^1.2.4, punycode@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+
+q@^1.1.2:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
+
+qs@6.5.1, qs@~6.5.1:
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
+
+qs@~6.3.0:
+ version "6.3.2"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
+
+qs@~6.4.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
+
+query-string@^4.1.0:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
+ dependencies:
+ object-assign "^4.1.0"
+ strict-uri-encode "^1.0.0"
+
+querystring-es3@^0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
+
+querystring@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
+
+querystringify@0.0.x:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c"
+
+querystringify@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb"
+
+randomatic@^1.1.3:
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
+ dependencies:
+ is-number "^3.0.0"
+ kind-of "^4.0.0"
+
+randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80"
+ dependencies:
+ safe-buffer "^5.1.0"
+
+randomfill@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.3.tgz#b96b7df587f01dd91726c418f30553b1418e3d62"
+ dependencies:
+ randombytes "^2.0.5"
+ safe-buffer "^5.1.0"
+
+range-parser@^1.0.3, range-parser@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+
+raw-body@2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
+ dependencies:
+ bytes "3.0.0"
+ http-errors "1.6.2"
+ iconv-lite "0.4.19"
+ unpipe "1.0.0"
+
+rc@^1.1.7:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.5.tgz#275cd687f6e3b36cc756baa26dfee80a790301fd"
+ dependencies:
+ deep-extend "~0.4.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
+read-cache@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
+ dependencies:
+ pify "^2.3.0"
+
+read-pkg-up@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
+ dependencies:
+ find-up "^1.0.0"
+ read-pkg "^1.0.0"
+
+read-pkg-up@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
+ dependencies:
+ find-up "^2.0.0"
+ read-pkg "^2.0.0"
+
+read-pkg@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
+ dependencies:
+ load-json-file "^1.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^1.0.0"
+
+read-pkg@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
+ dependencies:
+ load-json-file "^2.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^2.0.0"
+
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3:
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.0.3"
+ util-deprecate "~1.0.1"
+
+readdirp@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
+ dependencies:
+ graceful-fs "^4.1.2"
+ minimatch "^3.0.2"
+ readable-stream "^2.0.2"
+ set-immediate-shim "^1.0.1"
+
+redent@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
+ dependencies:
+ indent-string "^2.1.0"
+ strip-indent "^1.0.1"
+
+reduce-css-calc@^1.2.6, reduce-css-calc@^1.2.7:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716"
+ dependencies:
+ balanced-match "^0.4.2"
+ math-expression-evaluator "^1.2.14"
+ reduce-function-call "^1.0.1"
+
+reduce-css-calc@^2.0.0:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.4.tgz#c20e9cda8445ad73d4ff4bea960c6f8353791708"
+ dependencies:
+ css-unit-converter "^1.1.1"
+ postcss-value-parser "^3.3.0"
+
+reduce-function-call@^1.0.1, reduce-function-call@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99"
+ dependencies:
+ balanced-match "^0.4.2"
+
+regenerate@^1.2.1:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
+
+regenerator-runtime@^0.10.5:
+ version "0.10.5"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
+
+regenerator-runtime@^0.11.0:
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
+
+regenerator-transform@^0.10.0:
+ version "0.10.1"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
+ dependencies:
+ babel-runtime "^6.18.0"
+ babel-types "^6.19.0"
+ private "^0.1.6"
+
+regex-cache@^0.4.2:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
+ dependencies:
+ is-equal-shallow "^0.1.3"
+
+regex-not@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.0.tgz#42f83e39771622df826b02af176525d6a5f157f9"
+ dependencies:
+ extend-shallow "^2.0.1"
+
+regexpu-core@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
+ dependencies:
+ regenerate "^1.2.1"
+ regjsgen "^0.2.0"
+ regjsparser "^0.1.4"
+
+regexpu-core@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240"
+ dependencies:
+ regenerate "^1.2.1"
+ regjsgen "^0.2.0"
+ regjsparser "^0.1.4"
+
+regjsgen@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
+
+regjsparser@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
+ dependencies:
+ jsesc "~0.5.0"
+
+remove-trailing-separator@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
+
+repeat-element@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
+
+repeat-string@^1.5.2, repeat-string@^1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+
+repeating@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
+ dependencies:
+ is-finite "^1.0.0"
+
+request@2:
+ version "2.83.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
+ dependencies:
+ aws-sign2 "~0.7.0"
+ aws4 "^1.6.0"
+ caseless "~0.12.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.1"
+ forever-agent "~0.6.1"
+ form-data "~2.3.1"
+ har-validator "~5.0.3"
+ hawk "~6.0.2"
+ http-signature "~1.2.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.17"
+ oauth-sign "~0.8.2"
+ performance-now "^2.1.0"
+ qs "~6.5.1"
+ safe-buffer "^5.1.1"
+ stringstream "~0.0.5"
+ tough-cookie "~2.3.3"
+ tunnel-agent "^0.6.0"
+ uuid "^3.1.0"
+
+request@2.81.0:
+ version "2.81.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
+ dependencies:
+ aws-sign2 "~0.6.0"
+ aws4 "^1.2.1"
+ caseless "~0.12.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.0"
+ forever-agent "~0.6.1"
+ form-data "~2.1.1"
+ har-validator "~4.2.1"
+ hawk "~3.1.3"
+ http-signature "~1.1.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.7"
+ oauth-sign "~0.8.1"
+ performance-now "^0.2.0"
+ qs "~6.4.0"
+ safe-buffer "^5.0.1"
+ stringstream "~0.0.4"
+ tough-cookie "~2.3.0"
+ tunnel-agent "^0.6.0"
+ uuid "^3.0.0"
+
+request@~2.79.0:
+ version "2.79.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
+ dependencies:
+ aws-sign2 "~0.6.0"
+ aws4 "^1.2.1"
+ caseless "~0.11.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.0"
+ forever-agent "~0.6.1"
+ form-data "~2.1.1"
+ har-validator "~2.0.6"
+ hawk "~3.1.3"
+ http-signature "~1.1.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.7"
+ oauth-sign "~0.8.1"
+ qs "~6.3.0"
+ stringstream "~0.0.4"
+ tough-cookie "~2.3.0"
+ tunnel-agent "~0.4.1"
+ uuid "^3.0.0"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+
+require-from-string@^1.1.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418"
+
+require-main-filename@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
+
+requires-port@1.0.x, requires-port@1.x.x, requires-port@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+
+resolve-cwd@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
+ dependencies:
+ resolve-from "^3.0.0"
+
+resolve-from@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
+
+resolve-url@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
+
+resolve@^1.1.7:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
+ dependencies:
+ path-parse "^1.0.5"
+
+rgb-hex@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/rgb-hex/-/rgb-hex-2.1.0.tgz#c773c5fe2268a25578d92539a82a7a5ce53beda6"
+
+rgb@~0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/rgb/-/rgb-0.1.0.tgz#be27b291e8feffeac1bd99729721bfa40fc037b5"
+
+right-align@^0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
+ dependencies:
+ align-text "^0.1.1"
+
+rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
+ dependencies:
+ glob "^7.0.5"
+
+ripemd160@^2.0.0, ripemd160@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7"
+ dependencies:
+ hash-base "^2.0.0"
+ inherits "^2.0.1"
+
+run-queue@^1.0.0, run-queue@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
+ dependencies:
+ aproba "^1.1.1"
+
+safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
+
+sass-graph@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
+ dependencies:
+ glob "^7.0.0"
+ lodash "^4.0.0"
+ scss-tokenizer "^0.2.3"
+ yargs "^7.0.0"
+
+sass-loader@^6.0.6:
+ version "6.0.6"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.6.tgz#e9d5e6c1f155faa32a4b26d7a9b7107c225e40f9"
+ dependencies:
+ async "^2.1.5"
+ clone-deep "^0.3.0"
+ loader-utils "^1.0.1"
+ lodash.tail "^4.1.1"
+ pify "^3.0.0"
+
+sax@~1.2.1:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+
+schema-utils@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"
+ dependencies:
+ ajv "^5.0.0"
+
+schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.3:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.3.tgz#e2a594d3395834d5e15da22b48be13517859458e"
+ dependencies:
+ ajv "^5.0.0"
+ ajv-keywords "^2.1.0"
+
+scss-tokenizer@^0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
+ dependencies:
+ js-base64 "^2.1.8"
+ source-map "^0.4.2"
+
+select-hose@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
+
+selfsigned@^1.9.1:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.2.tgz#b4449580d99929b65b10a48389301a6592088758"
+ dependencies:
+ node-forge "0.7.1"
+
+"semver@2 || 3 || 4 || 5", semver@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
+
+semver@~5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
+
+send@0.16.1:
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
+ dependencies:
+ debug "2.6.9"
+ depd "~1.1.1"
+ destroy "~1.0.4"
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ fresh "0.5.2"
+ http-errors "~1.6.2"
+ mime "1.4.1"
+ ms "2.0.0"
+ on-finished "~2.3.0"
+ range-parser "~1.2.0"
+ statuses "~1.3.1"
+
+serialize-javascript@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005"
+
+serve-index@^1.7.2:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
+ dependencies:
+ accepts "~1.3.4"
+ batch "0.6.1"
+ debug "2.6.9"
+ escape-html "~1.0.3"
+ http-errors "~1.6.2"
+ mime-types "~2.1.17"
+ parseurl "~1.3.2"
+
+serve-static@1.13.1:
+ version "1.13.1"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719"
+ dependencies:
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ parseurl "~1.3.2"
+ send "0.16.1"
+
+set-blocking@^2.0.0, set-blocking@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+
+set-getter@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376"
+ dependencies:
+ to-object-path "^0.3.0"
+
+set-immediate-shim@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
+
+set-value@^0.4.3:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1"
+ dependencies:
+ extend-shallow "^2.0.1"
+ is-extendable "^0.1.1"
+ is-plain-object "^2.0.1"
+ to-object-path "^0.3.0"
+
+set-value@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274"
+ dependencies:
+ extend-shallow "^2.0.1"
+ is-extendable "^0.1.1"
+ is-plain-object "^2.0.3"
+ split-string "^3.0.1"
+
+setimmediate@^1.0.4:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
+
+setprototypeof@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
+
+setprototypeof@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
+
+sha.js@^2.4.0, sha.js@^2.4.8:
+ version "2.4.10"
+ resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.10.tgz#b1fde5cd7d11a5626638a07c604ab909cfa31f9b"
+ dependencies:
+ inherits "^2.0.1"
+ safe-buffer "^5.0.1"
+
+shallow-clone@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060"
+ dependencies:
+ is-extendable "^0.1.1"
+ kind-of "^2.0.1"
+ lazy-cache "^0.2.3"
+ mixin-object "^2.0.1"
+
+shebang-command@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+ dependencies:
+ shebang-regex "^1.0.0"
+
+shebang-regex@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+
+signal-exit@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+
+simple-swizzle@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
+ dependencies:
+ is-arrayish "^0.3.1"
+
+slash@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
+
+snapdragon-node@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
+ dependencies:
+ define-property "^1.0.0"
+ isobject "^3.0.0"
+ snapdragon-util "^3.0.1"
+
+snapdragon-util@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2"
+ dependencies:
+ kind-of "^3.2.0"
+
+snapdragon@^0.8.1:
+ version "0.8.1"
+ resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370"
+ dependencies:
+ base "^0.11.1"
+ debug "^2.2.0"
+ define-property "^0.2.5"
+ extend-shallow "^2.0.1"
+ map-cache "^0.2.2"
+ source-map "^0.5.6"
+ source-map-resolve "^0.5.0"
+ use "^2.0.0"
+
+sntp@1.x.x:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
+ dependencies:
+ hoek "2.x.x"
+
+sntp@2.x.x:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
+ dependencies:
+ hoek "4.x.x"
+
+sockjs-client@1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12"
+ dependencies:
+ debug "^2.6.6"
+ eventsource "0.1.6"
+ faye-websocket "~0.11.0"
+ inherits "^2.0.1"
+ json3 "^3.3.2"
+ url-parse "^1.1.8"
+
+sockjs@0.3.19:
+ version "0.3.19"
+ resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d"
+ dependencies:
+ faye-websocket "^0.10.0"
+ uuid "^3.0.1"
+
+sort-keys@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
+ dependencies:
+ is-plain-obj "^1.0.0"
+
+source-list-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
+
+source-map-resolve@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a"
+ dependencies:
+ atob "^2.0.0"
+ decode-uri-component "^0.2.0"
+ resolve-url "^0.2.1"
+ source-map-url "^0.4.0"
+ urix "^0.1.0"
+
+source-map-support@^0.4.15:
+ version "0.4.18"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
+ dependencies:
+ source-map "^0.5.6"
+
+source-map-url@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
+
+source-map@^0.4.2:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
+ dependencies:
+ amdefine ">=0.0.4"
+
+source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+
+source-map@^0.6.1, source-map@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+
+spdx-correct@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
+ dependencies:
+ spdx-license-ids "^1.0.2"
+
+spdx-expression-parse@~1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c"
+
+spdx-license-ids@^1.0.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
+
+spdy-transport@^2.0.18:
+ version "2.0.20"
+ resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.20.tgz#735e72054c486b2354fe89e702256004a39ace4d"
+ dependencies:
+ debug "^2.6.8"
+ detect-node "^2.0.3"
+ hpack.js "^2.1.6"
+ obuf "^1.1.1"
+ readable-stream "^2.2.9"
+ safe-buffer "^5.0.1"
+ wbuf "^1.7.2"
+
+spdy@^3.4.1:
+ version "3.4.7"
+ resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc"
+ dependencies:
+ debug "^2.6.8"
+ handle-thing "^1.2.5"
+ http-deceiver "^1.2.7"
+ safe-buffer "^5.0.1"
+ select-hose "^2.0.0"
+ spdy-transport "^2.0.18"
+
+split-string@^3.0.1, split-string@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
+ dependencies:
+ extend-shallow "^3.0.0"
+
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+
+sshpk@^1.7.0:
+ version "1.13.1"
+ resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
+ dependencies:
+ asn1 "~0.2.3"
+ assert-plus "^1.0.0"
+ dashdash "^1.12.0"
+ getpass "^0.1.1"
+ optionalDependencies:
+ bcrypt-pbkdf "^1.0.0"
+ ecc-jsbn "~0.1.1"
+ jsbn "~0.1.0"
+ tweetnacl "~0.14.0"
+
+ssri@^5.0.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.2.1.tgz#8b6eb873688759bd3c75a88dee74593d179bb73c"
+ dependencies:
+ safe-buffer "^5.1.1"
+
+static-extend@^0.1.1:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
+ dependencies:
+ define-property "^0.2.5"
+ object-copy "^0.1.0"
+
+"statuses@>= 1.3.1 < 2":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
+
+statuses@~1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
+
+stdout-stream@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b"
+ dependencies:
+ readable-stream "^2.0.1"
+
+stream-browserify@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
+ dependencies:
+ inherits "~2.0.1"
+ readable-stream "^2.0.2"
+
+stream-each@^1.1.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd"
+ dependencies:
+ end-of-stream "^1.1.0"
+ stream-shift "^1.0.0"
+
+stream-http@^2.7.2:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.0.tgz#fd86546dac9b1c91aff8fc5d287b98fafb41bc10"
+ dependencies:
+ builtin-status-codes "^3.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.3.3"
+ to-arraybuffer "^1.0.0"
+ xtend "^4.0.0"
+
+stream-shift@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
+
+strict-uri-encode@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
+
+string-width@^1.0.1, string-width@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+ dependencies:
+ code-point-at "^1.0.0"
+ is-fullwidth-code-point "^1.0.0"
+ strip-ansi "^3.0.0"
+
+string-width@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+ dependencies:
+ is-fullwidth-code-point "^2.0.0"
+ strip-ansi "^4.0.0"
+
+string_decoder@^1.0.0, string_decoder@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
+ dependencies:
+ safe-buffer "~5.1.0"
+
+stringstream@~0.0.4, stringstream@~0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
+
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+strip-ansi@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+ dependencies:
+ ansi-regex "^3.0.0"
+
+strip-bom@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
+ dependencies:
+ is-utf8 "^0.2.0"
+
+strip-bom@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+
+strip-eof@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
+
+strip-indent@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
+ dependencies:
+ get-stdin "^4.0.1"
+
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+
+style-loader@^0.20.1:
+ version "0.20.1"
+ resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.20.1.tgz#33ac2bf4d5c65a8906bc586ad253334c246998d0"
+ dependencies:
+ loader-utils "^1.1.0"
+ schema-utils "^0.4.3"
+
+supports-color@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
+
+supports-color@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
+ dependencies:
+ has-flag "^1.0.0"
+
+supports-color@^4.2.1:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"
+ dependencies:
+ has-flag "^2.0.0"
+
+supports-color@^5.1.0, supports-color@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a"
+ dependencies:
+ has-flag "^3.0.0"
+
+svgo@^0.7.0:
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
+ dependencies:
+ coa "~1.0.1"
+ colors "~1.1.2"
+ csso "~2.3.1"
+ js-yaml "~3.7.0"
+ mkdirp "~0.5.1"
+ sax "~1.2.1"
+ whet.extend "~0.9.9"
+
+tapable@^0.2.7:
+ version "0.2.8"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"
+
+tar-pack@^3.4.0:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
+ dependencies:
+ debug "^2.2.0"
+ fstream "^1.0.10"
+ fstream-ignore "^1.0.5"
+ once "^1.3.3"
+ readable-stream "^2.1.4"
+ rimraf "^2.5.1"
+ tar "^2.2.1"
+ uid-number "^0.0.6"
+
+tar@^2.0.0, tar@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
+ dependencies:
+ block-stream "*"
+ fstream "^1.0.2"
+ inherits "2"
+
+through2@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
+ dependencies:
+ readable-stream "^2.1.5"
+ xtend "~4.0.1"
+
+thunky@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.2.tgz#a862e018e3fb1ea2ec3fce5d55605cf57f247371"
+
+time-stamp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357"
+
+timers-browserify@^2.0.4:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.6.tgz#241e76927d9ca05f4d959819022f5b3664b64bae"
+ dependencies:
+ setimmediate "^1.0.4"
+
+to-arraybuffer@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
+
+to-fast-properties@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
+
+to-object-path@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
+ dependencies:
+ kind-of "^3.0.2"
+
+to-regex-range@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38"
+ dependencies:
+ is-number "^3.0.0"
+ repeat-string "^1.6.1"
+
+to-regex@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.1.tgz#15358bee4a2c83bd76377ba1dc049d0f18837aae"
+ dependencies:
+ define-property "^0.2.5"
+ extend-shallow "^2.0.1"
+ regex-not "^1.0.0"
+
+tough-cookie@~2.3.0, tough-cookie@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
+ dependencies:
+ punycode "^1.4.1"
+
+trim-newlines@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
+
+trim-right@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
+
+trix@^0.11.1:
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/trix/-/trix-0.11.1.tgz#ffe54f2757c2c2385b8424fd5c5d2ab712a09acc"
+
+"true-case-path@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62"
+ dependencies:
+ glob "^6.0.4"
+
+tty-browserify@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
+
+tunnel-agent@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
+ dependencies:
+ safe-buffer "^5.0.1"
+
+tunnel-agent@~0.4.1:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
+
+tweetnacl@^0.14.3, tweetnacl@~0.14.0:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
+
+type-is@~1.6.15:
+ version "1.6.15"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.15"
+
+typedarray@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+
+uglify-es@^3.3.4:
+ version "3.3.10"
+ resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.10.tgz#8b0b7992cebe20edc26de1bf325cef797b8f3fa5"
+ dependencies:
+ commander "~2.14.1"
+ source-map "~0.6.1"
+
+uglify-js@^2.8.29:
+ version "2.8.29"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
+ dependencies:
+ source-map "~0.5.1"
+ yargs "~3.10.0"
+ optionalDependencies:
+ uglify-to-browserify "~1.0.0"
+
+uglify-to-browserify@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
+
+uglifyjs-webpack-plugin@^0.4.6:
+ version "0.4.6"
+ resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309"
+ dependencies:
+ source-map "^0.5.6"
+ uglify-js "^2.8.29"
+ webpack-sources "^1.0.1"
+
+uglifyjs-webpack-plugin@^1.1.8:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.1.8.tgz#1302fb9471a7daf3d0a5174da6d65f0f415e75ad"
+ dependencies:
+ cacache "^10.0.1"
+ find-cache-dir "^1.0.0"
+ schema-utils "^0.4.2"
+ serialize-javascript "^1.4.0"
+ source-map "^0.6.1"
+ uglify-es "^3.3.4"
+ webpack-sources "^1.1.0"
+ worker-farm "^1.5.2"
+
+uid-number@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
+
+underscore.string@2.3.x:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-2.3.3.tgz#71c08bf6b428b1133f37e78fa3a21c82f7329b0d"
+
+union-value@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
+ dependencies:
+ arr-union "^3.1.0"
+ get-value "^2.0.6"
+ is-extendable "^0.1.1"
+ set-value "^0.4.3"
+
+uniq@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
+
+uniqid@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1"
+ dependencies:
+ macaddress "^0.2.8"
+
+uniqs@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
+
+unique-filename@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3"
+ dependencies:
+ unique-slug "^2.0.0"
+
+unique-slug@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab"
+ dependencies:
+ imurmurhash "^0.1.4"
+
+units-css@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/units-css/-/units-css-0.4.0.tgz#d6228653a51983d7c16ff28f8b9dc3b1ffed3a07"
+ dependencies:
+ isnumeric "^0.2.0"
+ viewport-dimensions "^0.2.0"
+
+unpipe@1.0.0, unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+
+unset-value@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"
+ dependencies:
+ has-value "^0.3.1"
+ isobject "^3.0.0"
+
+upath@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.0.tgz#b4706b9461ca8473adf89133d235689ca17f3656"
+ dependencies:
+ lodash "3.x"
+ underscore.string "2.3.x"
+
+urix@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
+
+url-parse@1.0.x:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
+ dependencies:
+ querystringify "0.0.x"
+ requires-port "1.0.x"
+
+url-parse@^1.1.8:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986"
+ dependencies:
+ querystringify "~1.0.0"
+ requires-port "~1.0.0"
+
+url@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
+ dependencies:
+ punycode "1.3.2"
+ querystring "0.2.0"
+
+use@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8"
+ dependencies:
+ define-property "^0.2.5"
+ isobject "^3.0.0"
+ lazy-cache "^2.0.2"
+
+util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+
+util@0.10.3, util@^0.10.3:
+ version "0.10.3"
+ resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
+ dependencies:
+ inherits "2.0.1"
+
+utils-merge@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+
+uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
+
+validate-npm-package-license@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
+ dependencies:
+ spdx-correct "~1.0.0"
+ spdx-expression-parse "~1.0.0"
+
+vary@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+
+vendors@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
+
+verror@1.10.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
+ dependencies:
+ assert-plus "^1.0.0"
+ core-util-is "1.0.2"
+ extsprintf "^1.2.0"
+
+viewport-dimensions@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz#de740747db5387fd1725f5175e91bac76afdf36c"
+
+vm-browserify@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
+ dependencies:
+ indexof "0.0.1"
+
+watchpack@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac"
+ dependencies:
+ async "^2.1.2"
+ chokidar "^1.7.0"
+ graceful-fs "^4.1.2"
+
+wbuf@^1.1.0, wbuf@^1.7.2:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.2.tgz#d697b99f1f59512df2751be42769c1580b5801fe"
+ dependencies:
+ minimalistic-assert "^1.0.0"
+
+webpack-dev-middleware@1.12.2:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e"
+ dependencies:
+ memory-fs "~0.4.1"
+ mime "^1.5.0"
+ path-is-absolute "^1.0.0"
+ range-parser "^1.0.3"
+ time-stamp "^2.0.0"
+
+webpack-dev-server@^2.11.1:
+ version "2.11.1"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.11.1.tgz#6f9358a002db8403f016e336816f4485384e5ec0"
+ dependencies:
+ ansi-html "0.0.7"
+ array-includes "^3.0.3"
+ bonjour "^3.5.0"
+ chokidar "^2.0.0"
+ compression "^1.5.2"
+ connect-history-api-fallback "^1.3.0"
+ debug "^3.1.0"
+ del "^3.0.0"
+ express "^4.16.2"
+ html-entities "^1.2.0"
+ http-proxy-middleware "~0.17.4"
+ import-local "^1.0.0"
+ internal-ip "1.2.0"
+ ip "^1.1.5"
+ killable "^1.0.0"
+ loglevel "^1.4.1"
+ opn "^5.1.0"
+ portfinder "^1.0.9"
+ selfsigned "^1.9.1"
+ serve-index "^1.7.2"
+ sockjs "0.3.19"
+ sockjs-client "1.1.4"
+ spdy "^3.4.1"
+ strip-ansi "^3.0.0"
+ supports-color "^5.1.0"
+ webpack-dev-middleware "1.12.2"
+ yargs "6.6.0"
+
+webpack-manifest-plugin@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-1.3.2.tgz#5ea8ee5756359ddc1d98814324fe43496349a7d4"
+ dependencies:
+ fs-extra "^0.30.0"
+ lodash ">=3.5 <5"
+
+webpack-sources@^1.0.1, webpack-sources@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54"
+ dependencies:
+ source-list-map "^2.0.0"
+ source-map "~0.6.1"
+
+webpack@^3.10.0:
+ version "3.11.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.11.0.tgz#77da451b1d7b4b117adaf41a1a93b5742f24d894"
+ dependencies:
+ acorn "^5.0.0"
+ acorn-dynamic-import "^2.0.0"
+ ajv "^6.1.0"
+ ajv-keywords "^3.1.0"
+ async "^2.1.2"
+ enhanced-resolve "^3.4.0"
+ escope "^3.6.0"
+ interpret "^1.0.0"
+ json-loader "^0.5.4"
+ json5 "^0.5.1"
+ loader-runner "^2.3.0"
+ loader-utils "^1.1.0"
+ memory-fs "~0.4.1"
+ mkdirp "~0.5.0"
+ node-libs-browser "^2.0.0"
+ source-map "^0.5.3"
+ supports-color "^4.2.1"
+ tapable "^0.2.7"
+ uglifyjs-webpack-plugin "^0.4.6"
+ watchpack "^1.4.0"
+ webpack-sources "^1.0.1"
+ yargs "^8.0.2"
+
+websocket-driver@>=0.5.1:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb"
+ dependencies:
+ http-parser-js ">=0.4.0"
+ websocket-extensions ">=0.1.1"
+
+websocket-extensions@>=0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
+
+whet.extend@~0.9.9:
+ version "0.9.9"
+ resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
+
+which-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
+
+which-module@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+
+which@1, which@^1.2.9:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
+ dependencies:
+ isexe "^2.0.0"
+
+wide-align@^1.1.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
+ dependencies:
+ string-width "^1.0.2"
+
+window-size@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
+
+wordwrap@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
+
+worker-farm@^1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.2.tgz#32b312e5dc3d5d45d79ef44acc2587491cd729ae"
+ dependencies:
+ errno "^0.1.4"
+ xtend "^4.0.1"
+
+wrap-ansi@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+
+xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
+
+y18n@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
+
+yallist@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+
+yargs-parser@^4.2.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"
+ dependencies:
+ camelcase "^3.0.0"
+
+yargs-parser@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
+ dependencies:
+ camelcase "^3.0.0"
+
+yargs-parser@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9"
+ dependencies:
+ camelcase "^4.1.0"
+
+yargs@6.6.0:
+ version "6.6.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
+ dependencies:
+ camelcase "^3.0.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^1.4.0"
+ read-pkg-up "^1.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^1.0.2"
+ which-module "^1.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^4.2.0"
+
+yargs@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
+ dependencies:
+ camelcase "^3.0.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^1.4.0"
+ read-pkg-up "^1.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^1.0.2"
+ which-module "^1.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^5.0.0"
+
+yargs@^8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"
+ dependencies:
+ camelcase "^4.1.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^2.0.0"
+ read-pkg-up "^2.0.0"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^2.0.0"
+ which-module "^2.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^7.0.0"
+
+yargs@~3.10.0:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
+ dependencies:
+ camelcase "^1.0.2"
+ cliui "^2.1.0"
+ decamelize "^1.0.0"
+ window-size "0.1.0"
diff --git a/actiontext/test/fixtures/files/racecar.jpg b/actiontext/test/fixtures/files/racecar.jpg
new file mode 100644
index 0000000000..934b4caa22
--- /dev/null
+++ b/actiontext/test/fixtures/files/racecar.jpg
Binary files differ
diff --git a/actiontext/test/template/form_helper_test.rb b/actiontext/test/template/form_helper_test.rb
new file mode 100644
index 0000000000..a8c7a4dae2
--- /dev/null
+++ b/actiontext/test/template/form_helper_test.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionText::FormHelperTest < ActionView::TestCase
+ tests ActionText::TagHelper
+
+ test "form with rich text area" do
+ form_with model: Message.new, scope: :message do |form|
+ form.rich_text_area :content
+ end
+
+ assert_dom_equal \
+ '<form action="/messages" accept-charset="UTF-8" data-remote="true" method="post">' \
+ '<input type="hidden" name="message[content]" id="message_content_trix_input_message" />' \
+ '<trix-editor id="message_content" input="message_content_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/:signed_id/:filename">' \
+ "</trix-editor>" \
+ "</form>",
+ output_buffer
+ end
+
+ test "form with rich text area having class" do
+ form_with model: Message.new, scope: :message do |form|
+ form.rich_text_area :content, class: "custom-class"
+ end
+
+ assert_dom_equal \
+ '<form action="/messages" accept-charset="UTF-8" data-remote="true" method="post">' \
+ '<input type="hidden" name="message[content]" id="message_content_trix_input_message" />' \
+ '<trix-editor id="message_content" input="message_content_trix_input_message" class="custom-class" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/:signed_id/:filename">' \
+ "</trix-editor>" \
+ "</form>",
+ output_buffer
+ end
+
+ test "form with rich text area for non-attribute" do
+ form_with model: Message.new, scope: :message do |form|
+ form.rich_text_area :not_an_attribute
+ end
+
+ assert_dom_equal \
+ '<form action="/messages" accept-charset="UTF-8" data-remote="true" method="post">' \
+ '<input type="hidden" name="message[not_an_attribute]" id="message_not_an_attribute_trix_input_message" />' \
+ '<trix-editor id="message_not_an_attribute" input="message_not_an_attribute_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/:signed_id/:filename">' \
+ "</trix-editor>" \
+ "</form>",
+ output_buffer
+ end
+
+ test "modelless form with rich text area" do
+ form_with url: "/messages", scope: :message do |form|
+ form.rich_text_area :content
+ end
+
+ assert_dom_equal \
+ '<form action="/messages" accept-charset="UTF-8" data-remote="true" method="post">' \
+ '<input type="hidden" name="message[content]" id="trix_input_1" />' \
+ '<trix-editor id="message_content" input="trix_input_1" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/:signed_id/:filename">' \
+ "</trix-editor>" \
+ "</form>",
+ output_buffer
+ end
+
+ def form_with(*)
+ @output_buffer = super
+ end
+end
diff --git a/actiontext/test/test_helper.rb b/actiontext/test/test_helper.rb
new file mode 100644
index 0000000000..fd1c859349
--- /dev/null
+++ b/actiontext/test/test_helper.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+# Configure Rails Environment
+ENV["RAILS_ENV"] = "test"
+
+require_relative "../test/dummy/config/environment"
+ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)]
+require "rails/test_help"
+
+# Filter out Minitest backtrace while allowing backtrace from other libraries
+# to be shown.
+Minitest.backtrace_filter = Minitest::BacktraceFilter.new
+
+require "rails/test_unit/reporter"
+Rails::TestUnitReporter.executable = "bin/test"
+
+# Load fixtures from the engine
+if ActiveSupport::TestCase.respond_to?(:fixture_path=)
+ ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
+ ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
+ ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
+ ActiveSupport::TestCase.fixtures :all
+end
+
+class ActiveSupport::TestCase
+ private
+ def create_file_blob(filename:, content_type:, metadata: nil)
+ ActiveStorage::Blob.create_after_upload! io: file_fixture(filename).open, filename: filename, content_type: content_type, metadata: metadata
+ end
+end
diff --git a/actiontext/test/unit/attachment_test.rb b/actiontext/test/unit/attachment_test.rb
new file mode 100644
index 0000000000..65e9d69e84
--- /dev/null
+++ b/actiontext/test/unit/attachment_test.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionText::AttachmentTest < ActiveSupport::TestCase
+ test "from_attachable" do
+ attachment = ActionText::Attachment.from_attachable(attachable, caption: "Captioned")
+ assert_equal attachable, attachment.attachable
+ assert_equal "Captioned", attachment.caption
+ end
+
+ test "proxies missing methods to attachable" do
+ attachable.instance_eval { def proxied; "proxied"; end }
+ attachment = ActionText::Attachment.from_attachable(attachable)
+ assert_equal "proxied", attachment.proxied
+ end
+
+ test "proxies #to_param to attachable" do
+ attachment = ActionText::Attachment.from_attachable(attachable)
+ assert_equal attachable.to_param, attachment.to_param
+ end
+
+ test "converts to TrixAttachment" do
+ attachment = attachment_from_html(%Q(<action-text-attachment sgid="#{attachable.attachable_sgid}" caption="Captioned"></action-text-attachment>))
+
+ trix_attachment = attachment.to_trix_attachment
+ assert_kind_of ActionText::TrixAttachment, trix_attachment
+
+ assert_equal attachable.attachable_sgid, trix_attachment.attributes["sgid"]
+ assert_equal attachable.attachable_content_type, trix_attachment.attributes["contentType"]
+ assert_equal attachable.filename.to_s, trix_attachment.attributes["filename"]
+ assert_equal attachable.byte_size, trix_attachment.attributes["filesize"]
+ assert_equal "Captioned", trix_attachment.attributes["caption"]
+
+ assert_nil trix_attachment.attributes["content"]
+ end
+
+ test "converts to TrixAttachment with content" do
+ attachable = Person.create! name: "Javan"
+ attachment = attachment_from_html(%Q(<action-text-attachment sgid="#{attachable.attachable_sgid}"></action-text-attachment>))
+
+ trix_attachment = attachment.to_trix_attachment
+ assert_kind_of ActionText::TrixAttachment, trix_attachment
+
+ assert_equal attachable.attachable_sgid, trix_attachment.attributes["sgid"]
+ assert_equal attachable.attachable_content_type, trix_attachment.attributes["contentType"]
+
+ assert_not_nil attachable.to_trix_content_attachment_partial_path
+ assert_not_nil trix_attachment.attributes["content"]
+ end
+
+ private
+ def attachment_from_html(html)
+ ActionText::Content.new(html).attachments.first
+ end
+
+ def attachable
+ @attachment ||= create_file_blob(filename: "racecar.jpg", content_type: "image/jpg")
+ end
+end
diff --git a/actiontext/test/unit/content_test.rb b/actiontext/test/unit/content_test.rb
new file mode 100644
index 0000000000..f77f48df14
--- /dev/null
+++ b/actiontext/test/unit/content_test.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionText::ContentTest < ActiveSupport::TestCase
+ test "equality" do
+ html = "<div>test</div>"
+ content = content_from_html(html)
+ assert_equal content, content_from_html(html)
+ assert_not_equal content, html
+ end
+
+ test "marshal serialization" do
+ content = content_from_html("Hello!")
+ assert_equal content, Marshal.load(Marshal.dump(content))
+ end
+
+ test "roundtrips HTML without additional newlines" do
+ html = "<div>a<br></div>"
+ content = content_from_html(html)
+ assert_equal html, content.to_html
+ end
+
+ test "extracts links" do
+ html = '<a href="http://example.com/1">1</a><br><a href="http://example.com/1">1</a>'
+ content = content_from_html(html)
+ assert_equal ["http://example.com/1"], content.links
+ end
+
+ test "extracts attachables" do
+ attachable = create_file_blob(filename: "racecar.jpg", content_type: "image/jpg")
+ html = %Q(<action-text-attachment sgid="#{attachable.attachable_sgid}" caption="Captioned"></action-text-attachment>)
+
+ content = content_from_html(html)
+ assert_equal 1, content.attachments.size
+
+ attachment = content.attachments.first
+ assert_equal "Captioned", attachment.caption
+ assert_equal attachable, attachment.attachable
+ end
+
+ test "extracts remote image attachables" do
+ html = '<action-text-attachment content-type="image" url="http://example.com/cat.jpg" width="100" height="100" caption="Captioned"></action-text-attachment>'
+
+ content = content_from_html(html)
+ assert_equal 1, content.attachments.size
+
+ attachment = content.attachments.first
+ assert_equal "Captioned", attachment.caption
+
+ attachable = attachment.attachable
+ assert_kind_of ActionText::Attachables::RemoteImage, attachable
+ assert_equal "http://example.com/cat.jpg", attachable.url
+ assert_equal "100", attachable.width
+ assert_equal "100", attachable.height
+ end
+
+ test "identifies destroyed attachables as missing" do
+ attachable = create_file_blob(filename: "racecar.jpg", content_type: "image/jpg")
+ html = %Q(<action-text-attachment sgid="#{attachable.attachable_sgid}"></action-text-attachment>)
+ attachable.destroy!
+ content = content_from_html(html)
+ assert_equal 1, content.attachments.size
+ assert_equal ActionText::Attachables::MissingAttachable, content.attachments.first.attachable
+ end
+
+ test "extracts missing attachables" do
+ html = '<action-text-attachment sgid="missing"></action-text-attachment>'
+ content = content_from_html(html)
+ assert_equal 1, content.attachments.size
+ assert_equal ActionText::Attachables::MissingAttachable, content.attachments.first.attachable
+ end
+
+ test "converts Trix-formatted attachments" do
+ html = %Q(<figure data-trix-attachment='{"sgid":"123","contentType":"text/plain","width":100,"height":100}' data-trix-attributes='{"caption":"Captioned"}'></figure>)
+ content = content_from_html(html)
+ assert_equal 1, content.attachments.size
+ assert_equal '<action-text-attachment sgid="123" content-type="text/plain" width="100" height="100" caption="Captioned"></action-text-attachment>', content.to_html
+ end
+
+ test "ignores Trix-formatted attachments with malformed JSON" do
+ html = %Q(<div data-trix-attachment='{"sgid":"garbage...'></div>)
+ content = content_from_html(html)
+ assert_equal 0, content.attachments.size
+ end
+
+ test "minifies attachment markup" do
+ html = '<action-text-attachment sgid="123"><div>HTML</div></action-text-attachment>'
+ assert_equal '<action-text-attachment sgid="123"></action-text-attachment>', content_from_html(html).to_html
+ end
+
+ test "canonicalizes attachment gallery markup" do
+ attachment_html = '<action-text-attachment sgid="1" presentation="gallery"></action-text-attachment><action-text-attachment sgid="2" presentation="gallery"></action-text-attachment>'
+ html = %Q(<div class="attachment-gallery attachment-gallery--2">#{attachment_html}</div>)
+ assert_equal "<div>#{attachment_html}</div>", content_from_html(html).to_html
+ end
+
+ test "canonicalizes attachment gallery markup with whitespace" do
+ attachment_html = %Q(\n <action-text-attachment sgid="1" presentation="gallery"></action-text-attachment>\n <action-text-attachment sgid="2" presentation="gallery"></action-text-attachment>\n)
+ html = %Q(<div class="attachment-gallery attachment-gallery--2">#{attachment_html}</div>)
+ assert_equal "<div>#{attachment_html}</div>", content_from_html(html).to_html
+ end
+
+ test "canonicalizes nested attachment gallery markup" do
+ attachment_html = '<action-text-attachment sgid="1" presentation="gallery"></action-text-attachment><action-text-attachment sgid="2" presentation="gallery"></action-text-attachment>'
+ html = %Q(<blockquote><div class="attachment-gallery attachment-gallery--2">#{attachment_html}</div></blockquote>)
+ assert_equal "<blockquote><div>#{attachment_html}</div></blockquote>", content_from_html(html).to_html
+ end
+
+ private
+ def content_from_html(html)
+ ActionText::Content.new(html).tap do |content|
+ assert_nothing_raised { content.to_s }
+ end
+ end
+end
diff --git a/actiontext/test/unit/model_test.rb b/actiontext/test/unit/model_test.rb
new file mode 100644
index 0000000000..122a20700b
--- /dev/null
+++ b/actiontext/test/unit/model_test.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionText::ModelTest < ActiveSupport::TestCase
+ test "html conversion" do
+ message = Message.new(subject: "Greetings", content: "<h1>Hello world</h1>")
+ assert_equal %Q(<div class="trix-content">\n <h1>Hello world</h1>\n</div>\n), "#{message.content}"
+ end
+
+ test "plain text conversion" do
+ message = Message.new(subject: "Greetings", content: "<h1>Hello world</h1>")
+ assert_equal "Hello world", message.content.to_plain_text
+ end
+
+ test "without content" do
+ message = Message.create!(subject: "Greetings")
+ assert message.content.nil?
+ assert message.content.blank?
+ assert message.content.empty?
+ assert_not message.content.present?
+ end
+
+ test "with blank content" do
+ message = Message.create!(subject: "Greetings", content: "")
+ assert_not message.content.nil?
+ assert message.content.blank?
+ assert message.content.empty?
+ assert_not message.content.present?
+ end
+
+ test "embed extraction" do
+ blob = create_file_blob(filename: "racecar.jpg", content_type: "image/jpg")
+ message = Message.create!(subject: "Greetings", content: ActionText::Content.new("Hello world").append_attachables(blob))
+ assert_equal "racecar.jpg", message.content.embeds.first.filename.to_s
+ end
+
+ test "saving content" do
+ message = Message.create!(subject: "Greetings", content: "<h1>Hello world</h1>")
+ assert_equal "Hello world", message.content.to_plain_text
+ end
+
+ test "save body" do
+ message = Message.create(subject: "Greetings", body: "<h1>Hello world</h1>")
+ assert_equal "Hello world", message.body.to_plain_text
+ end
+end
diff --git a/actiontext/test/unit/plain_text_conversion_test.rb b/actiontext/test/unit/plain_text_conversion_test.rb
new file mode 100644
index 0000000000..53a1029caf
--- /dev/null
+++ b/actiontext/test/unit/plain_text_conversion_test.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionText::PlainTextConversionTest < ActiveSupport::TestCase
+ test "<p> tags are separated by two new lines" do
+ assert_converted_to(
+ "Hello world!\n\nHow are you?",
+ "<p>Hello world!</p><p>How are you?</p>"
+ )
+ end
+
+ test "<blockquote> tags are separated by two new lines" do
+ assert_converted_to(
+ "“Hello world!”\n\n“How are you?”",
+ "<blockquote>Hello world!</blockquote><blockquote>How are you?</blockquote>"
+ )
+ end
+
+ test "<ol> tags are separated by two new lines" do
+ assert_converted_to(
+ "Hello world!\n\n1. list1\n\n1. list2\n\nHow are you?",
+ "<p>Hello world!</p><ol><li>list1</li></ol><ol><li>list2</li></ol><p>How are you?</p>"
+ )
+ end
+
+ test "<ul> tags are separated by two new lines" do
+ assert_converted_to(
+ "Hello world!\n\n• list1\n\n• list2\n\nHow are you?",
+ "<p>Hello world!</p><ul><li>list1</li></ul><ul><li>list2</li></ul><p>How are you?</p>"
+ )
+ end
+
+ test "<h1> tags are separated by two new lines" do
+ assert_converted_to(
+ "Hello world!\n\nHow are you?",
+ "<h1>Hello world!</h1><div>How are you?</div>"
+ )
+ end
+
+ test "<li> tags are separated by one new line" do
+ assert_converted_to(
+ "• one\n• two\n• three",
+ "<ul><li>one</li><li>two</li><li>three</li></ul>"
+ )
+ end
+
+ test "<li> tags without a parent list" do
+ assert_converted_to(
+ "• one\n• two\n• three",
+ "<li>one</li><li>two</li><li>three</li>"
+ )
+ end
+
+ test "<br> tags are separated by one new line" do
+ assert_converted_to(
+ "Hello world!\none\ntwo\nthree",
+ "<p>Hello world!<br>one<br>two<br>three</p>"
+ )
+ end
+
+ test "<div> tags are separated by one new line" do
+ assert_converted_to(
+ "Hello world!\nHow are you?",
+ "<div>Hello world!</div><div>How are you?</div>"
+ )
+ end
+
+ test "<action-text-attachment> tags are converted to their plain-text representation" do
+ assert_converted_to(
+ "Hello world! [Cat]",
+ 'Hello world! <action-text-attachment url="http://example.com/cat.jpg" content-type="image" caption="Cat"></action-text-attachment>'
+ )
+ end
+
+ test "preserves non-linebreak whitespace after text" do
+ assert_converted_to(
+ "Hello world!",
+ "<div><strong>Hello </strong>world!</div>"
+ )
+ end
+
+ test "preserves trailing linebreaks after text" do
+ assert_converted_to(
+ "Hello\nHow are you?",
+ "<strong>Hello<br></strong>How are you?"
+ )
+ end
+
+ private
+ def assert_converted_to(plain_text, html)
+ assert_equal plain_text, ActionText::Content.new(html).to_plain_text
+ end
+end
diff --git a/actiontext/test/unit/trix_attachment_test.rb b/actiontext/test/unit/trix_attachment_test.rb
new file mode 100644
index 0000000000..81d015750e
--- /dev/null
+++ b/actiontext/test/unit/trix_attachment_test.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActionText::TrixAttachmentTest < ActiveSupport::TestCase
+ test "from_attributes" do
+ attributes = {
+ "data-trix-attachment" => {
+ "sgid" => "123",
+ "contentType" => "text/plain",
+ "href" => "http://example.com/",
+ "filename" => "example.txt",
+ "filesize" => 12345,
+ "previewable" => true
+ },
+ "data-trix-attributes" => {
+ "caption" => "hello"
+ }
+ }
+
+ attachment = attachment(
+ sgid: "123",
+ content_type: "text/plain",
+ href: "http://example.com/",
+ filename: "example.txt",
+ filesize: "12345",
+ previewable: "true",
+ caption: "hello"
+ )
+
+ assert_attachment_json_attributes(attachment, attributes)
+ end
+
+ test "previewable is typecast" do
+ assert_attachment_attribute(attachment(previewable: ""), "previewable", false)
+ assert_attachment_attribute(attachment(previewable: false), "previewable", false)
+ assert_attachment_attribute(attachment(previewable: "false"), "previewable", false)
+ assert_attachment_attribute(attachment(previewable: "garbage"), "previewable", false)
+ assert_attachment_attribute(attachment(previewable: true), "previewable", true)
+ assert_attachment_attribute(attachment(previewable: "true"), "previewable", true)
+ end
+
+ test "filesize is typecast when integer-like" do
+ assert_attachment_attribute(attachment(filesize: 123), "filesize", 123)
+ assert_attachment_attribute(attachment(filesize: "123"), "filesize", 123)
+ assert_attachment_attribute(attachment(filesize: "3.5 MB"), "filesize", "3.5 MB")
+ assert_attachment_attribute(attachment(filesize: nil), "filesize", nil)
+ assert_attachment_attribute(attachment(filesize: ""), "filesize", "")
+ end
+
+ test "#attributes strips unmappable attributes" do
+ attributes = {
+ "sgid" => "123",
+ "caption" => "hello"
+ }
+
+ attachment = attachment(sgid: "123", caption: "hello", nonexistent: "garbage")
+ assert_attachment_attributes(attachment, attributes)
+ end
+
+ def assert_attachment_attribute(attachment, name, value)
+ if value.nil?
+ assert_nil(attachment.attributes[name])
+ else
+ assert_equal(value, attachment.attributes[name])
+ end
+ end
+
+ def assert_attachment_attributes(attachment, attributes)
+ assert_equal(attributes, attachment.attributes)
+ end
+
+ def assert_attachment_json_attributes(attachment, attributes)
+ attributes.each do |name, expected|
+ actual = JSON.parse(attachment.node[name])
+ assert_equal(expected, actual)
+ end
+ end
+
+ def attachment(**attributes)
+ ActionText::TrixAttachment.from_attributes(attributes)
+ end
+end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index df4036a5a7..16361fd2eb 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,8 +1,19 @@
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Remove deprecated `image_alt` helper.
+
+ *Rafael Mendonça França*
+
+* Fix the need of `#protect_against_forgery?` method defined in
+ `ActionView::Base` subclasses. This prevents the use of forms and buttons.
+
+ *Genadi Samokovarov*
+
* Fix UJS permanently showing disabled text in a[data-remote][data-disable-with] elements within forms.
- Fixes #33889
- *Wolfgang Hobmaier*
+ Fixes #33889.
+ *Wolfgang Hobmaier*
* Prevent non-primary mouse keys from triggering Rails UJS click handlers.
Firefox fires click events even if the click was triggered by non-primary mouse keys such as right- or scroll-wheel-clicks.
@@ -12,11 +23,10 @@
<%= link_to 'Remote', remote_path, class: 'remote', remote: true, data: { type: :json } %>
```
- Fixes #34541
+ Fixes #34541.
*Wolfgang Hobmaier*
-
* Prevent `ActionView::TextHelper#word_wrap` from unexpectedly stripping white space from the _left_ side of lines.
For example, given input like this:
@@ -30,11 +40,10 @@
Calling `word_wrap` should not trim the indents on the first and last lines.
- Fixes #34487
+ Fixes #34487.
*Lyle Mullican*
-
* Add allocations to template rendering instrumentation.
Adds the allocations for template and partial rendering to the server output on render.
diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE
index 1cb3add0fc..ab7c27c209 100644
--- a/actionview/MIT-LICENSE
+++ b/actionview/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2018 David Heinemeier Hansson
+Copyright (c) 2004-2019 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/app/assets/javascripts/MIT-LICENSE b/actionview/app/assets/javascripts/MIT-LICENSE
index 28e1b12496..03319ea365 100644
--- a/actionview/app/assets/javascripts/MIT-LICENSE
+++ b/actionview/app/assets/javascripts/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2007-2018 Rails Core team
+Copyright (c) 2007-2019 Rails Core team
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md
index b74fa1afad..b9682b61e2 100644
--- a/actionview/app/assets/javascripts/README.md
+++ b/actionview/app/assets/javascripts/README.md
@@ -17,11 +17,11 @@ Note that the `data` attributes this library adds are a feature of HTML5. If you
### NPM
- npm install rails-ujs --save
-
+ npm install @rails/ujs --save
+
### Yarn
-
- yarn add rails-ujs
+
+ yarn add @rails/ujs
Ensure that `.yarnclean` does not include `assets` if you use [yarn autoclean](https://yarnpkg.com/lang/en/docs/cli/autoclean/).
@@ -40,8 +40,7 @@ In a conventional Rails application that uses the asset pipeline, require `rails
If you're using the Webpacker gem or some other JavaScript bundler, add the following to your main JS file:
```javascript
-import Rails from 'rails-ujs';
-Rails.start()
+require("@rails/ujs").start()
```
## How to run tests
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index c1eeda75f5..c4a614fa6d 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2018 David Heinemeier Hansson
+# Copyright (c) 2004-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index d41fe2a608..6cb342a0a9 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -10,6 +10,10 @@ require "action_view/template"
require "action_view/lookup_context"
module ActionView #:nodoc:
+ module CompiledTemplates #:nodoc:
+ # holds compiled template code
+ end
+
# = Action View Base
#
# Action View templates can be written in several ways.
@@ -141,6 +145,8 @@ module ActionView #:nodoc:
class Base
include Helpers, ::ERB::Util, Context
+ include CompiledTemplates
+
# Specify the proc used to decorate input tags that refer to attributes with errors.
cattr_accessor :field_error_proc, default: Proc.new { |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
@@ -210,6 +216,18 @@ module ActionView #:nodoc:
_prepare_context
end
+ def run(method, locals, buffer, &block)
+ _old_output_buffer, _old_virtual_path = @output_buffer, @virtual_path
+ @output_buffer = buffer
+ send(method, locals, buffer, &block)
+ ensure
+ @output_buffer, @virtual_path = _old_output_buffer, _old_virtual_path
+ end
+
+ def compiled_method_container
+ CompiledTemplates
+ end
+
ActiveSupport.run_load_hooks(:action_view, self)
end
end
diff --git a/actionview/lib/action_view/context.rb b/actionview/lib/action_view/context.rb
index 3c605c3ee3..2b22c30a3a 100644
--- a/actionview/lib/action_view/context.rb
+++ b/actionview/lib/action_view/context.rb
@@ -1,10 +1,6 @@
# frozen_string_literal: true
module ActionView
- module CompiledTemplates #:nodoc:
- # holds compiled template code
- end
-
# = Action View Context
#
# Action View contexts are supplied to Action Controller to render a template.
@@ -16,7 +12,6 @@ module ActionView
# object that includes this module (although you can call _prepare_context
# defined below).
module Context
- include CompiledTemplates
attr_accessor :output_buffer, :view_flow
# Prepares the context by setting the appropriate instance variables.
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index 77ae444a58..02de3eeec2 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -10,7 +10,7 @@ module ActionView
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 3d7c8dae75..59d70a1dc4 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -329,14 +329,14 @@ module ActionView
# image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
# # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
#
- # Active Storage (images that are uploaded by the users of your app):
+ # Active Storage blobs (images that are uploaded by the users of your app):
#
# image_tag(user.avatar)
# # => <img src="/rails/active_storage/blobs/.../tiger.jpg" />
- # image_tag(user.avatar.variant(resize_to_fit: [100, 100]))
- # # => <img src="/rails/active_storage/variants/.../tiger.jpg" />
- # image_tag(user.avatar.variant(resize_to_fit: [100, 100]), size: '100')
- # # => <img width="100" height="100" src="/rails/active_storage/variants/.../tiger.jpg" />
+ # image_tag(user.avatar.variant(resize_to_limit: [100, 100]))
+ # # => <img src="/rails/active_storage/representations/.../tiger.jpg" />
+ # image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100')
+ # # => <img width="100" height="100" src="/rails/active_storage/representations/.../tiger.jpg" />
def image_tag(source, options = {})
options = options.symbolize_keys
check_for_image_tag_errors(options)
@@ -355,29 +355,6 @@ module ActionView
tag("img", options)
end
- # Returns a string suitable for an HTML image tag alt attribute.
- # The +src+ argument is meant to be an image file path.
- # The method removes the basename of the file path and the digest,
- # if any. It also removes hyphens and underscores from file names and
- # replaces them with spaces, returning a space-separated, titleized
- # string.
- #
- # ==== Examples
- #
- # image_alt('rails.png')
- # # => Rails
- #
- # image_alt('hyphenated-file-name.png')
- # # => Hyphenated file name
- #
- # image_alt('underscored_file_name.png')
- # # => Underscored file name
- def image_alt(src)
- ActiveSupport::Deprecation.warn("image_alt is deprecated and will be removed from Rails 6.0. You must explicitly set alt text on images.")
-
- File.basename(src, ".*").sub(/-[[:xdigit:]]{32,64}\z/, "").tr("-_", " ").capitalize
- end
-
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
# a single video tag will be returned. If +sources+ is an array, a video
# tag with nested source tags for each source will be returned. The
diff --git a/actionview/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb
index 69c59844a6..c0422c6ff5 100644
--- a/actionview/lib/action_view/helpers/csrf_helper.rb
+++ b/actionview/lib/action_view/helpers/csrf_helper.rb
@@ -20,7 +20,7 @@ module ActionView
# "X-CSRF-Token" HTTP header. If you are using rails-ujs this happens automatically.
#
def csrf_meta_tags
- if protect_against_forgery?
+ if defined?(protect_against_forgery?) && protect_against_forgery?
[
tag("meta", name: "csrf-param", content: request_forgery_protection_token),
tag("meta", name: "csrf-token", content: form_authenticity_token)
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index c2caa77afb..c5a736bfb4 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1680,6 +1680,227 @@ module ActionView
@index = options[:index] || options[:child_index]
end
+ ##
+ # :method: text_field
+ #
+ # :call-seq: text_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#text_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.text_field :name %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: password_field
+ #
+ # :call-seq: password_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#password_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.password_field :password %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: text_area
+ #
+ # :call-seq: text_area(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#text_area for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.text_area :detail %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: color_field
+ #
+ # :call-seq: color_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#color_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.color_field :favorite_color %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: search_field
+ #
+ # :call-seq: search_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#search_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.search_field :name %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: telephone_field
+ #
+ # :call-seq: telephone_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#telephone_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.telephone_field :phone %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: phone_field
+ #
+ # :call-seq: phone_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#phone_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.phone_field :phone %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: date_field
+ #
+ # :call-seq: date_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#date_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.date_field :born_on %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: time_field
+ #
+ # :call-seq: time_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#time_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.time_field :borned_at %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: datetime_field
+ #
+ # :call-seq: datetime_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#datetime_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.datetime_field :graduation_day %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: datetime_local_field
+ #
+ # :call-seq: datetime_local_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#datetime_local_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.datetime_local_field :graduation_day %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: month_field
+ #
+ # :call-seq: month_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#month_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.month_field :birthday_month %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: week_field
+ #
+ # :call-seq: week_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#week_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.week_field :birthday_week %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: url_field
+ #
+ # :call-seq: url_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#url_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.url_field :homepage %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: email_field
+ #
+ # :call-seq: email_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#email_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.email_field :address %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: number_field
+ #
+ # :call-seq: number_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#number_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.number_field :age %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: range_field
+ #
+ # :call-seq: range_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#range_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.range_field :age %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{selector}(method, options = {}) # def text_field(method, options = {})
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index ebdd96f570..a7747456a4 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -654,7 +654,7 @@ module ActionView
#
# ==== Gotcha
#
- # The HTML specification says when nothing is select on a collection of radio buttons
+ # The HTML specification says when nothing is selected on a collection of radio buttons
# web browsers do not send any value to server.
# Unfortunately this introduces a gotcha:
# if a +User+ model has a +category_id+ field and in the form no category is selected, no +category_id+ parameter is sent. So,
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 948dd1551f..d63ada3890 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -618,7 +618,7 @@ module ActionView
end
def token_tag(token = nil, form_options: {})
- if token != false && protect_against_forgery?
+ if token != false && defined?(protect_against_forgery?) && protect_against_forgery?
token ||= form_authenticity_token(form_options: form_options)
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
else
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index cb850d75ee..f175c30aa1 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -363,7 +363,7 @@ module ActionView
@options = options
@block = block
- @locals = options[:locals] ? options[:locals].symbolize_keys : {}
+ @locals = options[:locals] || {}
@details = extract_details(options)
prepend_formats(options[:formats])
diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
index bb9db21e32..f414620923 100644
--- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb
+++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
@@ -43,14 +43,14 @@ module ActionView
# For streaming, instead of rendering a given a template, we return a Body
# object that responds to each. This object is initialized with a block
# that knows how to render the template.
- def render_template(template, layout_name = nil, locals = {}) #:nodoc:
+ def render_template(view, template, layout_name = nil, locals = {}) #:nodoc:
return [super] unless layout_name && template.supports_streaming?
locals ||= {}
layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])
Body.new do |buffer|
- delayed_render(buffer, template, layout, @view, locals)
+ delayed_render(buffer, template, layout, view, locals)
end
end
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index ce8908924a..fb5539e0db 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -5,7 +5,6 @@ require "active_support/core_ext/object/try"
module ActionView
class TemplateRenderer < AbstractRenderer #:nodoc:
def render(context, options)
- @view = context
@details = extract_details(options)
template = determine_template(options)
@@ -13,7 +12,7 @@ module ActionView
@lookup_context.rendered_format ||= (template.formats.first || formats.first)
- render_template(template, options[:layout], options[:locals])
+ render_template(context, template, options[:layout], options[:locals])
end
private
@@ -46,22 +45,21 @@ module ActionView
# Renders the given template. A string representing the layout can be
# supplied as well.
- def render_template(template, layout_name = nil, locals = nil)
- view, locals = @view, locals || {}
+ def render_template(view, template, layout_name = nil, locals = nil)
+ locals ||= {}
- render_with_layout(layout_name, locals) do |layout|
+ render_with_layout(view, layout_name, locals) do |layout|
instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
template.render(view, locals) { |*name| view._layout_for(*name) }
end
end
end
- def render_with_layout(path, locals)
+ def render_with_layout(view, path, locals)
layout = path && find_layout(path, locals.keys, [formats.first])
content = yield(layout)
if layout
- view = @view
view.view_flow.set(:layout, content)
layout.render(view, locals) { |*name| view._layout_for(*name) }
else
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index cb4327cf16..205665a8c6 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -55,10 +55,8 @@ module ActionView
end
end
- attr_internal_writer :view_context_class
-
def view_context_class
- @_view_context_class ||= self.class.view_context_class
+ self.class.view_context_class
end
# An instance of a view class. The default view class is ActionView::Base.
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 070d82cf17..8ce8053011 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -155,10 +155,10 @@ module ActionView
# This method is instrumented as "!render_template.action_view". Notice that
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
- def render(view, locals, buffer = nil, &block)
+ def render(view, locals, buffer = ActionView::OutputBuffer.new, &block)
instrument_render_template do
compile!(view)
- view.send(method_name, locals, buffer, &block)
+ view.run(method_name, locals, buffer, &block)
end
rescue => e
handle_render_error(view, e)
@@ -264,11 +264,7 @@ module ActionView
# re-compilation
return if @compiled
- if view.is_a?(ActionView::CompiledTemplates)
- mod = ActionView::CompiledTemplates
- else
- mod = view.singleton_class
- end
+ mod = view.compiled_method_container
instrument("!compile_template") do
compile(mod)
@@ -301,9 +297,7 @@ module ActionView
# encoding of the code
source = +<<-end_src
def #{method_name}(local_assigns, output_buffer)
- _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
- ensure
- @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
+ @virtual_path = #{@virtual_path.inspect};#{locals_code};#{code}
end
end_src
diff --git a/actionview/lib/action_view/template/handlers/erb/erubi.rb b/actionview/lib/action_view/template/handlers/erb/erubi.rb
index db75f028ed..e155bae89d 100644
--- a/actionview/lib/action_view/template/handlers/erb/erubi.rb
+++ b/actionview/lib/action_view/template/handlers/erb/erubi.rb
@@ -13,7 +13,7 @@ module ActionView
# Dup properties so that we don't modify argument
properties = Hash[properties]
- properties[:preamble] = "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
+ properties[:preamble] = ""
properties[:postamble] = "@output_buffer.to_s"
properties[:bufvar] = "@output_buffer"
properties[:escapefunc] = ""
@@ -22,8 +22,12 @@ module ActionView
end
def evaluate(action_view_erb_handler_context)
- pr = eval("proc { #{@src} }", binding, @filename || "(erubi)")
- action_view_erb_handler_context.instance_eval(&pr)
+ src = @src
+ view = Class.new(ActionView::Base) {
+ include action_view_erb_handler_context._routes.url_helpers
+ class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", @filename || "(erubi)", 0)
+ }.new(action_view_erb_handler_context)
+ view.run(:_template, {}, ActionView::OutputBuffer.new)
end
private
diff --git a/actionview/package.json b/actionview/package.json
index 1f74df79d3..eb2a9f879e 100644
--- a/actionview/package.json
+++ b/actionview/package.json
@@ -1,6 +1,6 @@
{
- "name": "rails-ujs",
- "version": "6.0.0-alpha",
+ "name": "@rails/ujs",
+ "version": "6.0.0-beta1",
"description": "Ruby on Rails unobtrusive scripting adapter",
"main": "lib/assets/compiled/rails-ujs.js",
"files": [
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index f90626ad9e..d71944e938 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -61,7 +61,7 @@ module RenderERBUtils
ActionView::Template::Handlers::ERB,
{})
- template.render(self, {}).strip
+ template.render(ActionView::Base.new, {}).strip
end
end
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 204903c60c..727d3fbc1a 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -485,8 +485,8 @@ class TestController < ActionController::Base
render partial: "customer", locals: { customer: Customer.new("david") }
end
- def partial_with_string_locals
- render partial: "customer", locals: { "customer" => Customer.new("david") }
+ def partial_with_hashlike_locals
+ render partial: "customer", locals: ActionController::Parameters.new(customer: Customer.new("david"))
end
def partial_with_form_builder
@@ -691,7 +691,7 @@ class RenderTest < ActionController::TestCase
get :partial_with_locals, to: "test#partial_with_locals"
get :partial_with_nested_object, to: "test#partial_with_nested_object"
get :partial_with_nested_object_shorthand, to: "test#partial_with_nested_object_shorthand"
- get :partial_with_string_locals, to: "test#partial_with_string_locals"
+ get :partial_with_hashlike_locals, to: "test#partial_with_hashlike_locals"
get :partials_list, to: "test#partials_list"
get :render_action_hello_world, to: "test#render_action_hello_world"
get :render_action_hello_world_as_string, to: "test#render_action_hello_world_as_string"
@@ -1292,8 +1292,8 @@ class RenderTest < ActionController::TestCase
assert_equal "Hello: david", @response.body
end
- def test_partial_with_string_locals
- get :partial_with_string_locals
+ def test_partial_with_hashlike_locals
+ get :partial_with_hashlike_locals
assert_equal "Hello: david", @response.body
end
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index e68f03d1f4..e371a87614 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -512,26 +512,6 @@ class AssetTagHelperTest < ActionView::TestCase
UrlToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
- def test_image_alt
- [nil, "/", "/foo/bar/", "foo/bar/"].each do |prefix|
- assert_deprecated do
- assert_equal "Rails", image_alt("#{prefix}rails.png")
- end
- assert_deprecated do
- assert_equal "Rails", image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png")
- end
- assert_deprecated do
- assert_equal "Rails", image_alt("#{prefix}rails-f56ef62bc41b040664e801a38f068082a75d506d9048307e8096737463503d0b.png")
- end
- assert_deprecated do
- assert_equal "Long file name with hyphens", image_alt("#{prefix}long-file-name-with-hyphens.png")
- end
- assert_deprecated do
- assert_equal "Long file name with underscores", image_alt("#{prefix}long_file_name_with_underscores.png")
- end
- end
- end
-
def test_image_tag
ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
diff --git a/actionview/test/template/csrf_helper_test.rb b/actionview/test/template/csrf_helper_test.rb
new file mode 100644
index 0000000000..dd9821eb6c
--- /dev/null
+++ b/actionview/test/template/csrf_helper_test.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+class CsrfHelperTest < ActiveSupport::TestCase
+ cattr_accessor :request_forgery, default: false
+
+ include ActionView::Helpers::CsrfHelper
+ include ActionView::Helpers::TagHelper
+ include Rails::Dom::Testing::Assertions::DomAssertions
+
+ def test_csrf_meta_tags_without_request_forgery_protection
+ assert_dom_equal "", csrf_meta_tags
+ end
+
+ def test_csrf_meta_tags_with_request_forgery_protection
+ self.request_forgery = true
+
+ assert_dom_equal <<~DOM.chomp, csrf_meta_tags
+ <meta name="csrf-param" content="form_token" />
+ <meta name="csrf-token" content="secret" />
+ DOM
+ ensure
+ self.request_forgery = false
+ end
+
+ def test_csrf_meta_tags_without_protect_against_forgery_method
+ self.class.undef_method(:protect_against_forgery?)
+
+ assert_dom_equal "", csrf_meta_tags
+ ensure
+ self.class.define_method(:protect_against_forgery?) { request_forgery }
+ end
+
+ def protect_against_forgery?
+ request_forgery
+ end
+
+ def form_authenticity_token(*args)
+ "secret"
+ end
+
+ def request_forgery_protection_token
+ "form_token"
+ end
+end
diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb
index b348d1f17b..8257d97b7c 100644
--- a/actionview/test/template/template_test.rb
+++ b/actionview/test/template/template_test.rb
@@ -18,7 +18,7 @@ class TestERBTemplate < ActiveSupport::TestCase
attr_accessor :formats
end
- class Context
+ class Context < ActionView::Base
def initialize
@output_buffer = "original"
@virtual_path = nil
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 1ab28e4749..632b32f09f 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -119,6 +119,16 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
+ def test_button_to_without_protect_against_forgery_method
+ self.class.undef_method(:protect_against_forgery?)
+ assert_dom_equal(
+ %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>},
+ button_to("Hello", "http://www.example.com")
+ )
+ ensure
+ self.class.define_method(:protect_against_forgery?) { request_forgery }
+ end
+
def test_button_to_with_straight_url
assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com")
end
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 31253855d7..0ed937f4cc 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,5 @@
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
* Return false instead of the job instance when `enqueue` is aborted.
This will be the behavior in Rails 6.1 but it can be controlled now with
diff --git a/activejob/MIT-LICENSE b/activejob/MIT-LICENSE
index 274211f710..aedc21bca2 100644
--- a/activejob/MIT-LICENSE
+++ b/activejob/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2018 David Heinemeier Hansson
+Copyright (c) 2014-2019 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index 01fab4d918..5f20ef9b9f 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2014-2018 David Heinemeier Hansson
+# Copyright (c) 2014-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index 4ab62f89b0..487cdd6d38 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -81,7 +81,7 @@ module ActiveJob
@queue_name = self.class.queue_name
@priority = self.class.priority
@executions = 0
- @exception_executions = Hash.new(0)
+ @exception_executions = {}
end
# Returns a hash with the job data that can safely be passed to the
diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb
index 53984a4e49..9e00942a1c 100644
--- a/activejob/lib/active_job/exceptions.rb
+++ b/activejob/lib/active_job/exceptions.rb
@@ -30,21 +30,28 @@ module ActiveJob
# class RemoteServiceJob < ActiveJob::Base
# retry_on CustomAppException # defaults to 3s wait, 5 attempts
# retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
+ #
+ # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
+ # retry_on Net::OpenTimeout, Timeout::Error, wait: :exponentially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
+ # # To retry at most 10 times for each individual exception:
+ # # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
+ # # retry_on Timeout::Error, wait: :exponentially_longer, attempts: 10
+ #
# retry_on(YetAnotherCustomAppException) do |job, error|
# ExceptionNotifier.caught(error)
# end
- # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
- # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
#
# def perform(*args)
# # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific
# # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
- # # Might raise Net::OpenTimeout when the remote service is down
+ # # Might raise Net::OpenTimeout or Timeout::Error when the remote service is down
# end
# end
def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
rescue_from(*exceptions) do |error|
- exception_executions[exceptions.to_s] += 1
+ # Guard against jobs that were persisted before we started having individual executions counters per retry_on
+ self.exception_executions ||= {}
+ self.exception_executions[exceptions.to_s] = (exception_executions[exceptions.to_s] || 0) + 1
if exception_executions[exceptions.to_s] < attempts
retry_job wait: determine_delay(wait), queue: queue, priority: priority, error: error
diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb
index 770f70dc5e..2b894397e3 100644
--- a/activejob/lib/active_job/gem_version.rb
+++ b/activejob/lib/active_job/gem_version.rb
@@ -10,7 +10,7 @@ module ActiveJob
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activejob/test/cases/exceptions_test.rb b/activejob/test/cases/exceptions_test.rb
index 67327e0bbd..c88162bf58 100644
--- a/activejob/test/cases/exceptions_test.rb
+++ b/activejob/test/cases/exceptions_test.rb
@@ -4,157 +4,167 @@ require "helper"
require "jobs/retry_job"
require "models/person"
-class ExceptionsTest < ActiveJob::TestCase
+class ExceptionsTest < ActiveSupport::TestCase
setup do
JobBuffer.clear
- skip if ActiveJob::Base.queue_adapter.is_a?(ActiveJob::QueueAdapters::InlineAdapter)
+ skip if adapter_skips_scheduling?(ActiveJob::Base.queue_adapter)
end
test "successfully retry job throwing exception against defaults" do
- perform_enqueued_jobs do
- RetryJob.perform_later "DefaultsError", 5
+ RetryJob.perform_later "DefaultsError", 5
- assert_equal [
- "Raised DefaultsError for the 1st time",
- "Raised DefaultsError for the 2nd time",
- "Raised DefaultsError for the 3rd time",
- "Raised DefaultsError for the 4th time",
- "Successfully completed job" ], JobBuffer.values
- end
+ assert_equal [
+ "Raised DefaultsError for the 1st time",
+ "Raised DefaultsError for the 2nd time",
+ "Raised DefaultsError for the 3rd time",
+ "Raised DefaultsError for the 4th time",
+ "Successfully completed job" ], JobBuffer.values
end
test "successfully retry job throwing exception against higher limit" do
- perform_enqueued_jobs do
- RetryJob.perform_later "ShortWaitTenAttemptsError", 9
- assert_equal 9, JobBuffer.values.count
- end
+ RetryJob.perform_later "ShortWaitTenAttemptsError", 9
+ assert_equal 9, JobBuffer.values.count
end
- test "keeps the same attempts counter when several exceptions are listed in the same declaration" do
+ test "keeps the same attempts counter for several exceptions listed in the same retry_on declaration" do
exceptions_to_raise = %w(FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo
SecondRetryableErrorOfTwo SecondRetryableErrorOfTwo)
assert_raises SecondRetryableErrorOfTwo do
- perform_enqueued_jobs do
- ExceptionRetryJob.perform_later(exceptions_to_raise)
- end
+ RetryJob.perform_later(exceptions_to_raise, 5)
+
+ assert_equal [
+ "Raised FirstRetryableErrorOfTwo for the 1st time",
+ "Raised FirstRetryableErrorOfTwo for the 2nd time",
+ "Raised FirstRetryableErrorOfTwo for the 3rd time",
+ "Raised SecondRetryableErrorOfTwo for the 4th time",
+ "Raised SecondRetryableErrorOfTwo for the 5th time",
+ ], JobBuffer.values
end
end
- test "keeps a separate attempts counter for each individual declaration" do
- exceptions_to_raise = %w(FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo
- DefaultsError DefaultsError)
+ test "keeps a separate attempts counter for each individual retry_on declaration" do
+ exceptions_to_raise = %w(DefaultsError DefaultsError DefaultsError DefaultsError
+ FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo)
assert_nothing_raised do
- perform_enqueued_jobs do
- ExceptionRetryJob.perform_later(exceptions_to_raise)
- end
+ RetryJob.perform_later(exceptions_to_raise, 10)
+
+ assert_equal [
+ "Raised DefaultsError for the 1st time",
+ "Raised DefaultsError for the 2nd time",
+ "Raised DefaultsError for the 3rd time",
+ "Raised DefaultsError for the 4th time",
+ "Raised FirstRetryableErrorOfTwo for the 5th time",
+ "Raised FirstRetryableErrorOfTwo for the 6th time",
+ "Raised FirstRetryableErrorOfTwo for the 7th time",
+ "Successfully completed job"
+ ], JobBuffer.values
end
end
test "failed retry job when exception kept occurring against defaults" do
- perform_enqueued_jobs do
- RetryJob.perform_later "DefaultsError", 6
- assert_equal "Raised DefaultsError for the 5th time", JobBuffer.last_value
- rescue DefaultsError
- pass
- end
+ RetryJob.perform_later "DefaultsError", 6
+ assert_equal "Raised DefaultsError for the 5th time", JobBuffer.last_value
+ rescue DefaultsError
+ pass
end
test "failed retry job when exception kept occurring against higher limit" do
- perform_enqueued_jobs do
- RetryJob.perform_later "ShortWaitTenAttemptsError", 11
- assert_equal "Raised ShortWaitTenAttemptsError for the 10th time", JobBuffer.last_value
- rescue ShortWaitTenAttemptsError
- pass
- end
+ RetryJob.perform_later "ShortWaitTenAttemptsError", 11
+ assert_equal "Raised ShortWaitTenAttemptsError for the 10th time", JobBuffer.last_value
+ rescue ShortWaitTenAttemptsError
+ pass
end
test "discard job" do
- perform_enqueued_jobs do
- RetryJob.perform_later "DiscardableError", 2
- assert_equal "Raised DiscardableError for the 1st time", JobBuffer.last_value
- end
+ RetryJob.perform_later "DiscardableError", 2
+ assert_equal "Raised DiscardableError for the 1st time", JobBuffer.last_value
end
test "custom handling of discarded job" do
- perform_enqueued_jobs do
- RetryJob.perform_later "CustomDiscardableError", 2
- assert_equal "Dealt with a job that was discarded in a custom way. Message: CustomDiscardableError", JobBuffer.last_value
- end
+ RetryJob.perform_later "CustomDiscardableError", 2
+ assert_equal "Dealt with a job that was discarded in a custom way. Message: CustomDiscardableError", JobBuffer.last_value
end
test "custom handling of job that exceeds retry attempts" do
- perform_enqueued_jobs do
- RetryJob.perform_later "CustomCatchError", 6
- assert_equal "Dealt with a job that failed to retry in a custom way after 6 attempts. Message: CustomCatchError", JobBuffer.last_value
- end
+ RetryJob.perform_later "CustomCatchError", 6
+ assert_equal "Dealt with a job that failed to retry in a custom way after 6 attempts. Message: CustomCatchError", JobBuffer.last_value
end
test "long wait job" do
travel_to Time.now
- perform_enqueued_jobs do
- assert_performed_with at: (Time.now + 3600.seconds).to_i do
- RetryJob.perform_later "LongWaitError", 5
- end
- end
+ RetryJob.perform_later "LongWaitError", 2, :log_scheduled_at
+
+ assert_equal [
+ "Raised LongWaitError for the 1st time",
+ "Next execution scheduled at #{(Time.now + 3600.seconds).to_f}",
+ "Successfully completed job"
+ ], JobBuffer.values
end
test "exponentially retrying job" do
travel_to Time.now
- perform_enqueued_jobs do
- assert_performed_with at: (Time.now + 3.seconds).to_i do
- assert_performed_with at: (Time.now + 18.seconds).to_i do
- assert_performed_with at: (Time.now + 83.seconds).to_i do
- assert_performed_with at: (Time.now + 258.seconds).to_i do
- RetryJob.perform_later "ExponentialWaitTenAttemptsError", 5
- end
- end
- end
- end
- end
+ RetryJob.perform_later "ExponentialWaitTenAttemptsError", 5, :log_scheduled_at
+
+ assert_equal [
+ "Raised ExponentialWaitTenAttemptsError for the 1st time",
+ "Next execution scheduled at #{(Time.now + 3.seconds).to_f}",
+ "Raised ExponentialWaitTenAttemptsError for the 2nd time",
+ "Next execution scheduled at #{(Time.now + 18.seconds).to_f}",
+ "Raised ExponentialWaitTenAttemptsError for the 3rd time",
+ "Next execution scheduled at #{(Time.now + 83.seconds).to_f}",
+ "Raised ExponentialWaitTenAttemptsError for the 4th time",
+ "Next execution scheduled at #{(Time.now + 258.seconds).to_f}",
+ "Successfully completed job"
+ ], JobBuffer.values
end
test "custom wait retrying job" do
travel_to Time.now
- perform_enqueued_jobs do
- assert_performed_with at: (Time.now + 2.seconds).to_i do
- assert_performed_with at: (Time.now + 4.seconds).to_i do
- assert_performed_with at: (Time.now + 6.seconds).to_i do
- assert_performed_with at: (Time.now + 8.seconds).to_i do
- RetryJob.perform_later "CustomWaitTenAttemptsError", 5
- end
- end
- end
- end
- end
+ RetryJob.perform_later "CustomWaitTenAttemptsError", 5, :log_scheduled_at
+
+ assert_equal [
+ "Raised CustomWaitTenAttemptsError for the 1st time",
+ "Next execution scheduled at #{(Time.now + 2.seconds).to_f}",
+ "Raised CustomWaitTenAttemptsError for the 2nd time",
+ "Next execution scheduled at #{(Time.now + 4.seconds).to_f}",
+ "Raised CustomWaitTenAttemptsError for the 3rd time",
+ "Next execution scheduled at #{(Time.now + 6.seconds).to_f}",
+ "Raised CustomWaitTenAttemptsError for the 4th time",
+ "Next execution scheduled at #{(Time.now + 8.seconds).to_f}",
+ "Successfully completed job"
+ ], JobBuffer.values
end
test "successfully retry job throwing one of two retryable exceptions" do
- perform_enqueued_jobs do
- RetryJob.perform_later "SecondRetryableErrorOfTwo", 3
+ RetryJob.perform_later "SecondRetryableErrorOfTwo", 3
- assert_equal [
- "Raised SecondRetryableErrorOfTwo for the 1st time",
- "Raised SecondRetryableErrorOfTwo for the 2nd time",
- "Successfully completed job" ], JobBuffer.values
- end
+ assert_equal [
+ "Raised SecondRetryableErrorOfTwo for the 1st time",
+ "Raised SecondRetryableErrorOfTwo for the 2nd time",
+ "Successfully completed job" ], JobBuffer.values
end
test "discard job throwing one of two discardable exceptions" do
- perform_enqueued_jobs do
- RetryJob.perform_later "SecondDiscardableErrorOfTwo", 2
- assert_equal [ "Raised SecondDiscardableErrorOfTwo for the 1st time" ], JobBuffer.values
- end
+ RetryJob.perform_later "SecondDiscardableErrorOfTwo", 2
+ assert_equal [ "Raised SecondDiscardableErrorOfTwo for the 1st time" ], JobBuffer.values
end
test "successfully retry job throwing DeserializationError" do
- perform_enqueued_jobs do
- RetryJob.perform_later Person.new(404), 5
- assert_equal ["Raised ActiveJob::DeserializationError for the 5 time"], JobBuffer.values
- end
+ RetryJob.perform_later Person.new(404), 5
+ assert_equal ["Raised ActiveJob::DeserializationError for the 5 time"], JobBuffer.values
end
+
+ private
+ def adapter_skips_scheduling?(queue_adapter)
+ [
+ ActiveJob::QueueAdapters::InlineAdapter,
+ ActiveJob::QueueAdapters::AsyncAdapter,
+ ActiveJob::QueueAdapters::SneakersAdapter
+ ].include?(queue_adapter.class)
+ end
end
diff --git a/activejob/test/jobs/retry_job.rb b/activejob/test/jobs/retry_job.rb
index 2d19d4c41e..112d672006 100644
--- a/activejob/test/jobs/retry_job.rb
+++ b/activejob/test/jobs/retry_job.rb
@@ -18,7 +18,7 @@ class CustomDiscardableError < StandardError; end
class RetryJob < ActiveJob::Base
retry_on DefaultsError
- retry_on FirstRetryableErrorOfTwo, SecondRetryableErrorOfTwo
+ retry_on FirstRetryableErrorOfTwo, SecondRetryableErrorOfTwo, attempts: 4
retry_on LongWaitError, wait: 1.hour, attempts: 10
retry_on ShortWaitTenAttemptsError, wait: 1.second, attempts: 10
retry_on ExponentialWaitTenAttemptsError, wait: :exponentially_longer, attempts: 10
@@ -30,8 +30,15 @@ class RetryJob < ActiveJob::Base
discard_on FirstDiscardableErrorOfTwo, SecondDiscardableErrorOfTwo
discard_on(CustomDiscardableError) { |job, error| JobBuffer.add("Dealt with a job that was discarded in a custom way. Message: #{error.message}") }
- def perform(raising, attempts)
- if executions < attempts
+ before_enqueue do |job|
+ if job.arguments.include?(:log_scheduled_at) && job.scheduled_at
+ JobBuffer.add("Next execution scheduled at #{job.scheduled_at}")
+ end
+ end
+
+ def perform(raising, attempts, *)
+ raising = raising.shift if raising.is_a?(Array)
+ if raising && executions < attempts
JobBuffer.add("Raised #{raising} for the #{executions.ordinalize} time")
raise raising.constantize
else
@@ -39,12 +46,3 @@ class RetryJob < ActiveJob::Base
end
end
end
-
-class ExceptionRetryJob < ActiveJob::Base
- retry_on FirstRetryableErrorOfTwo, SecondRetryableErrorOfTwo, attempts: 4
- retry_on DefaultsError
-
- def perform(exceptions)
- raise exceptions.shift.constantize.new unless exceptions.empty?
- end
-end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 84567dcc18..912b307683 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,37 @@
+* Fix year value when casting a multiparameter time hash
+
+ When assigning a hash to a time attribute that's missing a year component
+ (e.g. a `time_select` with `:ignore_date` set to `true`) then the year
+ defaults to 1970 instead of the expected 2000. This results in the attribute
+ changing as a result of the save.
+
+ Before:
+ ```
+ event = Event.new(start_time: { 4 => 20, 5 => 30 })
+ event.start_time # => 1970-01-01 20:30:00 UTC
+ event.save
+ event.reload
+ event.start_time # => 2000-01-01 20:30:00 UTC
+ ```
+
+ After:
+ ```
+ event = Event.new(start_time: { 4 => 20, 5 => 30 })
+ event.start_time # => 2000-01-01 20:30:00 UTC
+ event.save
+ event.reload
+ event.start_time # => 2000-01-01 20:30:00 UTC
+ ```
+
+ *Andrew White*
+
+
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Add `ActiveModel::Errors#of_kind?`.
+
+ *bogdanvlviv*, *Rafael Mendonça França*
+
* Fix numericality equality validation of `BigDecimal` and `Float`
by casting to `BigDecimal` on both ends of the validation.
diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE
index 1cb3add0fc..ab7c27c209 100644
--- a/activemodel/MIT-LICENSE
+++ b/activemodel/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2018 David Heinemeier Hansson
+Copyright (c) 2004-2019 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index bc10d6b4b9..c9140dc582 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2018 David Heinemeier Hansson
+# Copyright (c) 2004-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index fde3381df2..ea2ed7dff7 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/hash/keys"
module ActiveModel
# == Active \Model \Callbacks
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 969effdc20..9fd6f2d89c 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -328,15 +328,15 @@ module ActiveModel
# person.errors.added? :name, :blank # => true
# person.errors.added? :name, "can't be blank" # => true
#
- # If the error message requires an option, then it returns +true+ with
- # the correct option, or +false+ with an incorrect or missing option.
- #
- # person.errors.add :name, :too_long, { count: 25 }
- # person.errors.added? :name, :too_long, count: 25 # => true
- # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
- # person.errors.added? :name, :too_long, count: 24 # => false
- # person.errors.added? :name, :too_long # => false
- # person.errors.added? :name, "is too long" # => false
+ # If the error message requires options, then it returns +true+ with
+ # the correct options, or +false+ with incorrect or missing options.
+ #
+ # person.errors.add :name, :too_long, { count: 25 }
+ # person.errors.added? :name, :too_long, count: 25 # => true
+ # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
+ # person.errors.added? :name, :too_long, count: 24 # => false
+ # person.errors.added? :name, :too_long # => false
+ # person.errors.added? :name, "is too long" # => false
def added?(attribute, message = :invalid, options = {})
message = message.call if message.respond_to?(:call)
@@ -347,6 +347,27 @@ module ActiveModel
end
end
+ # Returns +true+ if an error on the attribute with the given message is
+ # present, or +false+ otherwise. +message+ is treated the same as for +add+.
+ #
+ # person.errors.add :age
+ # person.errors.add :name, :too_long, { count: 25 }
+ # person.errors.of_kind? :age # => true
+ # person.errors.of_kind? :name # => false
+ # person.errors.of_kind? :name, :too_long # => true
+ # person.errors.of_kind? :name, "is too long (maximum is 25 characters)" # => true
+ # person.errors.of_kind? :name, :not_too_long # => false
+ # person.errors.of_kind? :name, "is too long" # => false
+ def of_kind?(attribute, message = :invalid)
+ message = message.call if message.respond_to?(:call)
+
+ if message.is_a? Symbol
+ details[attribute.to_sym].map { |e| e[:error] }.include? message
+ else
+ self[attribute].include? message
+ end
+ end
+
# Returns all the full error messages in an array.
#
# class Person
diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb
index cef5441e4a..b0322f62c7 100644
--- a/activemodel/lib/active_model/gem_version.rb
+++ b/activemodel/lib/active_model/gem_version.rb
@@ -10,7 +10,7 @@ module ActiveModel
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activemodel/lib/active_model/type/time.rb b/activemodel/lib/active_model/type/time.rb
index b3056b1333..8fba01b86a 100644
--- a/activemodel/lib/active_model/type/time.rb
+++ b/activemodel/lib/active_model/type/time.rb
@@ -5,7 +5,7 @@ module ActiveModel
class Time < Value # :nodoc:
include Helpers::TimeValue
include Helpers::AcceptsMultiparameterTime.new(
- defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
+ defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
)
def type
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index c5997283ea..9cb8b543b0 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -10,7 +10,6 @@ module ActiveModel
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
INTEGER_REGEX = /\A[+-]?\d+\z/
- DECIMAL_REGEX = /\A[+-]?\d+\.?\d*(e|e[+-])?\d+\z/
def check_validity!
keys = CHECKS.keys - [:odd, :even]
@@ -92,8 +91,8 @@ module ActiveModel
raw_value
elsif is_integer?(raw_value)
raw_value.to_i
- elsif is_decimal?(raw_value) && !is_hexadecimal_literal?(raw_value)
- BigDecimal(raw_value)
+ elsif !is_hexadecimal_literal?(raw_value)
+ Kernel.Float(raw_value).to_d
end
end
@@ -101,12 +100,8 @@ module ActiveModel
INTEGER_REGEX.match?(raw_value.to_s)
end
- def is_decimal?(raw_value)
- DECIMAL_REGEX.match?(raw_value.to_s)
- end
-
def is_hexadecimal_literal?(raw_value)
- /\A0[xX]/.match?(raw_value)
+ /\A0[xX]/.match?(raw_value.to_s)
end
def filtered_options(value)
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index f9015b869d..947f9bf99b 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -209,6 +209,8 @@ class ErrorsTest < ActiveModel::TestCase
person.errors.add(:name, "cannot be blank")
person.errors.add(:name, "is invalid")
assert person.errors.added?(:name, "cannot be blank")
+ assert person.errors.added?(:name, "is invalid")
+ assert_not person.errors.added?(:name, "incorrect")
end
test "added? returns false when no errors are present" do
@@ -222,7 +224,7 @@ class ErrorsTest < ActiveModel::TestCase
assert_not person.errors.added?(:name, "cannot be blank")
end
- test "added? returns false when checking for an error, but not providing message arguments" do
+ test "added? returns false when checking for an error, but not providing message argument" do
person = Person.new
person.errors.add(:name, "cannot be blank")
assert_not person.errors.added?(:name)
@@ -233,6 +235,7 @@ class ErrorsTest < ActiveModel::TestCase
person.errors.add :name, :too_long, count: 25
assert person.errors.added? :name, :too_long, count: 25
+ assert person.errors.added? :name, "is too long (maximum is 25 characters)"
assert_not person.errors.added? :name, :too_long, count: 24
assert_not person.errors.added? :name, :too_long
assert_not person.errors.added? :name, "is too long"
@@ -243,6 +246,81 @@ class ErrorsTest < ActiveModel::TestCase
person = Person.new
person.errors.add(:name, :wrong)
assert_not person.errors.added?(:name, :used)
+ assert person.errors.added?(:name, :wrong)
+ end
+
+ test "of_kind? returns false when checking for an error, but not providing message argument" do
+ person = Person.new
+ person.errors.add(:name, "cannot be blank")
+ assert_not person.errors.of_kind?(:name)
+ end
+
+ test "of_kind? returns false when checking a nonexisting error and other errors are present for the given attribute" do
+ person = Person.new
+ person.errors.add(:name, "is invalid")
+ assert_not person.errors.of_kind?(:name, "cannot be blank")
+ end
+
+ test "of_kind? returns false when no errors are present" do
+ person = Person.new
+ assert_not person.errors.of_kind?(:name)
+ end
+
+ test "of_kind? matches the given message when several errors are present for the same attribute" do
+ person = Person.new
+ person.errors.add(:name, "cannot be blank")
+ person.errors.add(:name, "is invalid")
+ assert person.errors.of_kind?(:name, "cannot be blank")
+ assert person.errors.of_kind?(:name, "is invalid")
+ assert_not person.errors.of_kind?(:name, "incorrect")
+ end
+
+ test "of_kind? defaults message to :invalid" do
+ person = Person.new
+ person.errors.add(:name)
+ assert person.errors.of_kind?(:name)
+ end
+
+ test "of_kind? handles proc messages" do
+ person = Person.new
+ message = Proc.new { "cannot be blank" }
+ person.errors.add(:name, message)
+ assert person.errors.of_kind?(:name, message)
+ end
+
+ test "of_kind? returns true when string attribute is used with a symbol message" do
+ person = Person.new
+ person.errors.add(:name, :blank)
+ assert person.errors.of_kind?("name", :blank)
+ end
+
+ test "of_kind? handles symbol message" do
+ person = Person.new
+ person.errors.add(:name, :blank)
+ assert person.errors.of_kind?(:name, :blank)
+ end
+
+ test "of_kind? detects indifferent if a specific error was added to the object" do
+ person = Person.new
+ person.errors.add(:name, "cannot be blank")
+ assert person.errors.of_kind?(:name, "cannot be blank")
+ assert person.errors.of_kind?("name", "cannot be blank")
+ end
+
+ test "of_kind? ignores options" do
+ person = Person.new
+ person.errors.add :name, :too_long, count: 25
+
+ assert person.errors.of_kind? :name, :too_long
+ assert person.errors.of_kind? :name, "is too long (maximum is 25 characters)"
+ end
+
+ test "of_kind? returns false when checking for an error by symbol and a different error with same message is present" do
+ I18n.backend.store_translations("en", errors: { attributes: { name: { wrong: "is wrong", used: "is wrong" } } })
+ person = Person.new
+ person.errors.add(:name, :wrong)
+ assert_not person.errors.of_kind?(:name, :used)
+ assert person.errors.of_kind?(:name, :wrong)
end
test "size calculates the number of error messages" do
diff --git a/activemodel/test/cases/type/time_test.rb b/activemodel/test/cases/type/time_test.rb
index 3fbae1a169..5c6271241d 100644
--- a/activemodel/test/cases/type/time_test.rb
+++ b/activemodel/test/cases/type/time_test.rb
@@ -16,6 +16,7 @@ module ActiveModel
assert_equal ::Time.utc(2000, 1, 1, 16, 45, 54), type.cast("2015-06-13T19:45:54+03:00")
assert_equal ::Time.utc(1999, 12, 31, 21, 7, 8), type.cast("06:07:08+09:00")
+ assert_equal ::Time.utc(2000, 1, 1, 16, 45, 54), type.cast(4 => 16, 5 => 45, 6 => 54)
end
def test_user_input_in_time_zone
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index eb4b02df93..fcdf123062 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -281,6 +281,19 @@ class NumericalityValidationTest < ActiveModel::TestCase
assert_predicate topic, :invalid?
end
+ def test_validates_numericalty_with_object_acting_as_numeric
+ klass = Class.new do
+ def to_f
+ 123.54
+ end
+ end
+
+ Topic.validates_numericality_of :price
+ topic = Topic.new(price: klass.new)
+
+ assert_predicate topic, :valid?
+ end
+
def test_validates_numericality_with_invalid_args
assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, greater_than_or_equal_to: "foo" }
assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, less_than_or_equal_to: "foo" }
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 1aa756cc7e..2a0cd81be5 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,75 @@
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Remove deprecated `#set_state` from the transaction object.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `#supports_statement_cache?` from the database adapters.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `#insert_fixtures` from the database adapters.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `ActiveRecord::ConnectionAdapters::SQLite3Adapter#valid_alter_table_type?`.
+
+ *Rafael Mendonça França*
+
+* Do not allow passing the column name to `sum` when a block is passed.
+
+ *Rafael Mendonça França*
+
+* Do not allow passing the column name to `count` when a block is passed.
+
+ *Rafael Mendonça França*
+
+* Remove delegation of missing methods in a relation to arel.
+
+ *Rafael Mendonça França*
+
+* Remove delegation of missing methods in a relation to private methods of the class.
+
+ *Rafael Mendonça França*
+
+* Deprecate `config.activerecord.sqlite3.represent_boolean_as_integer`.
+
+ *Rafael Mendonça França*
+
+* Change `SQLite3Adapter` to always represent boolean values as integers.
+
+ *Rafael Mendonça França*
+
+* Remove ability to specify a timestamp name for `#cache_key`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `ActiveRecord::Migrator.migrations_path=`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `expand_hash_conditions_for_aggregates`.
+
+ *Rafael Mendonça França*
+
+* Set polymorphic type column to NULL on `dependent: :nullify` strategy.
+
+ On polymorphic associations both the foreign key and the foreign type columns will be set to NULL.
+
+ *Laerti Papa*
+
+* Allow permitted instance of `ActionController::Parameters` as argument of `ActiveRecord::Relation#exists?`.
+
+ *Gannon McGibbon*
+
+* Add support for endless ranges introduces in Ruby 2.6.
+
+ *Greg Navis*
+
+* Deprecate passing `migrations_paths` to `connection.assume_migrated_upto_version`.
+
+ *Ryuta Kamizono*
+
* MySQL: `ROW_FORMAT=DYNAMIC` create table option by default.
Since MySQL 5.7.9, the `innodb_default_row_format` option defines the default row
diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE
index 04ba107c48..79e52c53af 100644
--- a/activerecord/MIT-LICENSE
+++ b/activerecord/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2018 David Heinemeier Hansson
+Copyright (c) 2004-2019 David Heinemeier Hansson
Arel originally copyright (c) 2007-2016 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index d43378c64f..de94f9693f 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2018 David Heinemeier Hansson
+# Copyright (c) 2004-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index fb1df00dc8..7bdbd8ce69 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1293,7 +1293,8 @@ module ActiveRecord
#
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
# * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
- # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
+ # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Polymorphic type will also be nullified
+ # on polymorphic associations. Callbacks are not executed.
# * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there are any associated records.
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
#
@@ -1436,7 +1437,8 @@ module ActiveRecord
#
# * <tt>:destroy</tt> causes the associated object to also be destroyed
# * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
- # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
+ # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Polymorphic type column is also nullified
+ # on polymorphic associations. Callbacks are not executed.
# * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there is an associated record
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
#
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index bf4942aac8..5d0927f17d 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -179,6 +179,20 @@ module ActiveRecord
end
private
+ def find_target
+ scope = self.scope
+ return scope.to_a if skip_statement_cache?(scope)
+
+ conn = klass.connection
+ sc = reflection.association_scope_cache(conn, owner) do |params|
+ as = AssociationScope.create { params.bind }
+ target_scope.merge!(as.scope(self))
+ end
+
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
+ sc.execute(binds, conn) { |record| set_inverse_instance(record) } || []
+ end
+
# The scope for this association.
#
# Note that the association_scope is merged into the target_scope only when the
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 0a90a6104a..9e38380611 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -26,7 +26,9 @@ module ActiveRecord
chain = get_chain(reflection, association, scope.alias_tracker)
scope.extending! reflection.extensions
- add_constraints(scope, owner, chain)
+ scope = add_constraints(scope, owner, chain)
+ scope.limit!(1) unless reflection.collection?
+ scope
end
def self.get_bind_values(owner, chain)
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index c4741c9fe6..4a25567c9d 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -303,22 +303,6 @@ module ActiveRecord
end
private
- def find_target
- scope = self.scope
- return scope.to_a if skip_statement_cache?(scope)
-
- conn = klass.connection
- sc = reflection.association_scope_cache(conn, owner) do |params|
- as = AssociationScope.create { params.bind }
- target_scope.merge!(as.scope(self))
- end
-
- binds = AssociationScope.get_bind_values(owner, reflection.chain)
- sc.execute(binds, conn) do |record|
- set_inverse_instance(record)
- end
- end
-
# We have some records loaded from the database (persisted) and some that are
# in-memory (memory). The same record may be represented in the persisted array
# and in the memory array.
diff --git a/activerecord/lib/active_record/associations/foreign_association.rb b/activerecord/lib/active_record/associations/foreign_association.rb
index 40010cde03..59af6f54c3 100644
--- a/activerecord/lib/active_record/associations/foreign_association.rb
+++ b/activerecord/lib/active_record/associations/foreign_association.rb
@@ -9,5 +9,12 @@ module ActiveRecord::Associations
false
end
end
+
+ def nullified_owner_attributes
+ Hash.new.tap do |attrs|
+ attrs[reflection.foreign_key] = nil
+ attrs[reflection.type] = nil if reflection.type.present?
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index f6fdbcde54..eb22db838c 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -92,7 +92,7 @@ module ActiveRecord
if method == :delete_all
scope.delete_all
else
- scope.update_all(reflection.foreign_key => nil)
+ scope.update_all(nullified_owner_attributes)
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 390bfd8b08..99971286a3 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -33,7 +33,7 @@ module ActiveRecord
target.destroy
throw(:abort) unless target.destroyed?
when :nullify
- target.update_columns(reflection.foreign_key => nil) if target.persisted?
+ target.update_columns(nullified_owner_attributes) if target.persisted?
end
end
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 8e50cce102..a92932fa4b 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -36,21 +36,7 @@ module ActiveRecord
end
def find_target
- scope = self.scope
- return scope.take if skip_statement_cache?(scope)
-
- conn = klass.connection
- sc = reflection.association_scope_cache(conn, owner) do |params|
- as = AssociationScope.create { params.bind }
- target_scope.merge!(as.scope(self)).limit(1)
- end
-
- binds = AssociationScope.get_bind_values(owner, reflection.chain)
- sc.execute(binds, conn) do |record|
- set_inverse_instance record
- end.first
- rescue ::RangeError
- nil
+ super.first
end
def replace(record)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 2299fc0214..aa2ecee74a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -123,7 +123,7 @@ module ActiveRecord
# +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
- sql, binds = sql_for_insert(sql, pk, nil, sequence_name, binds)
+ sql, binds = sql_for_insert(sql, pk, sequence_name, binds)
exec_query(sql, name, binds)
end
@@ -173,13 +173,6 @@ module ActiveRecord
exec_delete(sql, name, binds)
end
- # Returns +true+ when the connection adapter supports prepared statement
- # caching, otherwise returns +false+
- def supports_statement_cache? # :nodoc:
- true
- end
- deprecate :supports_statement_cache?
-
# Runs the given block in a database transaction, and returns the result
# of the block.
#
@@ -336,7 +329,7 @@ module ActiveRecord
# Inserts the given fixture into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
- # Most of adapters should implement `insert_fixtures` that leverages bulk SQL insert.
+ # Most of adapters should implement `insert_fixtures_set` that leverages bulk SQL insert.
# We keep this method to provide fallback
# for databases like sqlite that do not support bulk inserts.
def insert_fixture(fixture, table_name)
@@ -365,18 +358,6 @@ module ActiveRecord
execute manager.to_sql, "Fixture Insert"
end
- # Inserts a set of fixtures into the table. Overridden in adapters that require
- # something beyond a simple insert (eg. Oracle).
- def insert_fixtures(fixtures, table_name)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `insert_fixtures` is deprecated and will be removed in the next version of Rails.
- Consider using `insert_fixtures_set` for performance improvement.
- MSG
- return if fixtures.empty?
-
- execute(build_fixture_sql(fixtures, table_name), "Fixtures Insert")
- end
-
def insert_fixtures_set(fixture_set, tables_to_delete = [])
fixture_inserts = fixture_set.map do |table_name, fixtures|
next if fixtures.empty?
@@ -464,7 +445,7 @@ module ActiveRecord
exec_query(sql, name, binds, prepare: true)
end
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ def sql_for_insert(sql, pk, sequence_name, binds)
[sql, binds]
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 07e86afe9a..eefe621feb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -157,13 +157,9 @@ module ActiveRecord
end
end
- def types_which_need_no_typecasting
- [nil, Numeric, String]
- end
-
def _quote(value)
case value
- when String, ActiveSupport::Multibyte::Chars
+ when String, Symbol, ActiveSupport::Multibyte::Chars
"'#{quote_string(value.to_s)}'"
when true then quoted_true
when false then quoted_false
@@ -174,7 +170,6 @@ module ActiveRecord
when Type::Binary::Data then quoted_binary(value)
when Type::Time::Value then "'#{quoted_time(value)}'"
when Date, Time then "'#{quoted_date(value)}'"
- when Symbol then "'#{quote_string(value.to_s)}'"
when Class then "'#{value}'"
else raise TypeError, "can't quote #{value.class.name}"
end
@@ -188,10 +183,9 @@ module ActiveRecord
when false then unquoted_false
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s("F")
+ when nil, Numeric, String then value
when Type::Time::Value then quoted_time(value)
when Date, Time then quoted_date(value)
- when *types_which_need_no_typecasting
- value
else raise TypeError
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 38cfc3a241..90a130320b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -2,6 +2,7 @@
require "active_record/migration/join_table"
require "active_support/core_ext/string/access"
+require "active_support/deprecation"
require "digest/sha2"
module ActiveRecord
@@ -133,7 +134,7 @@ module ActiveRecord
column_name = column_name.to_s
checks = []
checks << lambda { |c| c.name == column_name }
- checks << lambda { |c| c.type == type } if type
+ checks << lambda { |c| c.type == type.to_sym rescue nil } if type
column_options_keys.each do |attr|
checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
end
@@ -1050,15 +1051,18 @@ module ActiveRecord
{ primary_key: true }
end
- def assume_migrated_upto_version(version, migrations_paths)
- migrations_paths = Array(migrations_paths)
+ def assume_migrated_upto_version(version, migrations_paths = nil)
+ unless migrations_paths.nil?
+ ActiveSupport::Deprecation.warn(<<~MSG)
+ Passing migrations_paths to #assume_migrated_upto_version is deprecated and will be removed in Rails 6.1.
+ MSG
+ end
+
version = version.to_i
sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
- migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i)
- versions = migration_context.migration_files.map do |file|
- migration_context.parse_migration_filename(file).first.to_i
- end
+ migrated = migration_context.get_all_versions
+ versions = migration_context.migrations.map(&:version)
unless migrated.include?(version)
execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 718910b090..112f376d0a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -40,24 +40,6 @@ module ActiveRecord
committed? || rolledback?
end
- def set_state(state)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The set_state method is deprecated and will be removed in
- Rails 6.0. Please use rollback! or commit! to set transaction
- state directly.
- MSG
- case state
- when :rolledback
- rollback!
- when :committed
- commit!
- when nil
- nullify!
- else
- raise ArgumentError, "Invalid transaction state: #{state}"
- end
- end
-
def rollback!
@children.each { |c| c.rollback! }
@state = :rolledback
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 1243236c09..d1ff32df3f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -6,6 +6,7 @@ require "active_record/connection_adapters/sql_type_metadata"
require "active_record/connection_adapters/abstract/schema_dumper"
require "active_record/connection_adapters/abstract/schema_creation"
require "active_support/concurrency/load_interlock_aware_monitor"
+require "active_support/deprecation"
require "arel/collectors/bind"
require "arel/collectors/composite"
require "arel/collectors/sql_string"
@@ -76,8 +77,11 @@ module ActiveRecord
SIMPLE_INT = /\A\d+\z/
- attr_accessor :visitor, :pool, :prevent_writes
- attr_reader :schema_cache, :owner, :logger, :prepared_statements, :lock
+ attr_writer :visitor
+ deprecate :visitor=
+
+ attr_accessor :pool
+ attr_reader :schema_cache, :visitor, :owner, :logger, :lock, :prepared_statements, :prevent_writes
alias :in_use? :owner
set_callback :checkin, :after, :enable_lazy_transactions!
@@ -117,6 +121,7 @@ module ActiveRecord
@idle_since = Concurrent.monotonic_time
@schema_cache = SchemaCache.new self
@quoted_column_names, @quoted_table_names = {}, {}
+ @prevent_writes = false
@visitor = arel_visitor
@lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
@@ -146,17 +151,16 @@ module ActiveRecord
replica? || prevent_writes
end
- # Prevent writing to the database regardless of role.
+ # Prevent writing to the database regardless of role.
#
# In some cases you may want to prevent writes to the database
# even if you are on a database that can write. `while_preventing_writes`
# will prevent writes to the database for the duration of the block.
def while_preventing_writes
- original = self.prevent_writes
- self.prevent_writes = true
+ original, @prevent_writes = @prevent_writes, true
yield
ensure
- self.prevent_writes = original
+ @prevent_writes = original
end
def migrations_paths # :nodoc:
@@ -504,15 +508,17 @@ module ActiveRecord
@connection
end
- def case_sensitive_comparison(table, attribute, column, value) # :nodoc:
- table[attribute].eq(value)
+ def case_sensitive_comparison(attribute, value) # :nodoc:
+ attribute.eq(value)
end
- def case_insensitive_comparison(table, attribute, column, value) # :nodoc:
+ def case_insensitive_comparison(attribute, value) # :nodoc:
+ column = column_for_attribute(attribute)
+
if can_perform_case_insensitive_comparison_for?(column)
- table[attribute].lower.eq(table.lower(value))
+ attribute.lower.eq(attribute.relation.lower(value))
else
- table[attribute].eq(value)
+ attribute.eq(value)
end
end
@@ -659,6 +665,11 @@ module ActiveRecord
raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}")
end
+ def column_for_attribute(attribute)
+ table_name = attribute.relation.name
+ schema_cache.columns_hash(table_name)[attribute.name.to_s]
+ end
+
def collector
if prepared_statements
Arel::Collectors::Composite.new(
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index dbc6614b93..5479ddab71 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -97,19 +97,11 @@ module ActiveRecord
end
def supports_datetime_with_precision?
- if mariadb?
- version >= "5.3.0"
- else
- version >= "5.6.4"
- end
+ mariadb? || version >= "5.6.4"
end
def supports_virtual_columns?
- if mariadb?
- version >= "5.2.0"
- else
- version >= "5.7.5"
- end
+ mariadb? || version >= "5.7.5"
end
def supports_advisory_locks?
@@ -484,9 +476,11 @@ module ActiveRecord
SQL
end
- def case_sensitive_comparison(table, attribute, column, value) # :nodoc:
+ def case_sensitive_comparison(attribute, value) # :nodoc:
+ column = column_for_attribute(attribute)
+
if column.collation && !column.case_sensitive?
- table[attribute].eq(Arel::Nodes::Bin.new(value))
+ attribute.eq(Arel::Nodes::Bin.new(value))
else
super
end
@@ -587,13 +581,13 @@ module ActiveRecord
m.alias_type %r(bit)i, "binary"
m.register_type(%r(enum)i) do |sql_type|
- limit = sql_type[/^enum\((.+)\)/i, 1]
+ limit = sql_type[/^enum\s*\((.+)\)/i, 1]
.split(",").map { |enum| enum.strip.length - 2 }.max
MysqlString.new(limit: limit)
end
m.register_type(%r(^set)i) do |sql_type|
- limit = sql_type[/^set\((.+)\)/i, 1]
+ limit = sql_type[/^set\s*\((.+)\)/i, 1]
.split(",").map { |set| set.strip.length - 1 }.sum - 1
MysqlString.new(limit: limit)
end
@@ -633,6 +627,7 @@ module ActiveRecord
ER_LOCK_WAIT_TIMEOUT = 1205
ER_QUERY_INTERRUPTED = 1317
ER_QUERY_TIMEOUT = 3024
+ ER_FK_INCOMPATIBLE_COLUMNS = 3780
def translate_exception(exception, message:, sql:, binds:)
case error_number(exception)
@@ -640,7 +635,7 @@ module ActiveRecord
RecordNotUnique.new(message, sql: sql, binds: binds)
when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
InvalidForeignKey.new(message, sql: sql, binds: binds)
- when ER_CANNOT_ADD_FOREIGN
+ when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
mismatched_foreign_key(message, sql: sql, binds: binds)
when ER_CANNOT_CREATE_TABLE
if message.include?("errno: 150")
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index f60d8469cc..9eaf9d9a89 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -248,10 +248,29 @@ module ActiveRecord
if db_config
resolve_connection(db_config.config).merge("name" => pool_name.to_s)
else
- raise(AdapterNotSpecified, "'#{env_name}' database is not configured. Available: #{configurations.configurations.map(&:env_name).join(", ")}")
+ raise AdapterNotSpecified, <<~MSG
+ The `#{env_name}` database is not configured for the `#{ActiveRecord::ConnectionHandling::DEFAULT_ENV.call}` environment.
+
+ Available databases configurations are:
+
+ #{build_configuration_sentence}
+ MSG
end
end
+ def build_configuration_sentence # :nodoc:
+ configs = configurations.configs_for(include_replicas: true)
+
+ configs.group_by(&:env_name).map do |env, config|
+ namespaces = config.map(&:spec_name)
+ if namespaces.size > 1
+ "#{env}: #{namespaces.join(", ")}"
+ else
+ env
+ end
+ end.join("\n")
+ end
+
# Accepts a hash. Expands the "url" key that contains a
# URL database connection to a full connection
# hash and merges with the rest of the hash.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index c70a4fa875..41633872e2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -110,7 +110,7 @@ module ActiveRecord
end
alias :exec_update :exec_delete
- def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
+ def sql_for_insert(sql, pk, sequence_name, binds) # :nodoc:
if pk.nil?
# Extract the table from the insert sql. Yuck.
table_ref = extract_table_ref_from_insert_sql(sql)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
index ceb8b40bd9..84dd28907b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
@@ -17,6 +17,42 @@ module ActiveRecord
"VALIDATE CONSTRAINT #{quote_column_name(name)}"
end
+ def visit_ChangeColumnDefinition(o)
+ column = o.column
+ column.sql_type = type_to_sql(column.type, column.options)
+ quoted_column_name = quote_column_name(o.name)
+
+ change_column_sql = +"ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}"
+
+ options = column_options(column)
+
+ if options[:collation]
+ change_column_sql << " COLLATE \"#{options[:collation]}\""
+ end
+
+ if options[:using]
+ change_column_sql << " USING #{options[:using]}"
+ elsif options[:cast_as]
+ cast_as_type = type_to_sql(options[:cast_as], options)
+ change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
+ end
+
+ if options.key?(:default)
+ if options[:default].nil?
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
+ else
+ quoted_default = quote_default_expression(options[:default], column)
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
+ end
+ end
+
+ if options.key?(:null)
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
+ end
+
+ change_column_sql
+ end
+
def add_column_options!(sql, options)
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 16260fe565..7cf371be68 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -22,8 +22,8 @@ module ActiveRecord
def create_database(name, options = {})
options = { encoding: "utf8" }.merge!(options.symbolize_keys)
- option_string = options.inject("") do |memo, (key, value)|
- memo += case key
+ option_string = options.each_with_object(+"") do |(key, value), memo|
+ memo << case key
when :owner
" OWNER = \"#{value}\""
when :template
@@ -683,38 +683,20 @@ module ActiveRecord
end
end
- def change_column_sql(table_name, column_name, type, options = {})
- quoted_column_name = quote_column_name(column_name)
- sql_type = type_to_sql(type, options)
- sql = +"ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
- if options[:collation]
- sql << " COLLATE \"#{options[:collation]}\""
- end
- if options[:using]
- sql << " USING #{options[:using]}"
- elsif options[:cast_as]
- cast_as_type = type_to_sql(options[:cast_as], options)
- sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
- end
-
- sql
- end
-
def add_column_for_alter(table_name, column_name, type, options = {})
return super unless options.key?(:comment)
[super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
end
def change_column_for_alter(table_name, column_name, type, options = {})
- sqls = [change_column_sql(table_name, column_name, type, options)]
- sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
- sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
+ td = create_table_definition(table_name)
+ cd = td.new_column_definition(column_name, type, options)
+ sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
sqls
end
- # Changes the default value of a table column.
- def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
+ def change_column_default_for_alter(table_name, column_name, default_or_changes)
column = column_for(table_name, column_name)
return unless column
@@ -729,8 +711,8 @@ module ActiveRecord
end
end
- def change_column_null_for_alter(table_name, column_name, null, default = nil) #:nodoc:
- "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
+ def change_column_null_for_alter(table_name, column_name, null, default = nil)
+ "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
end
def add_timestamps_for_alter(table_name, options = {})
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index c29cf1f9a1..c10765f42d 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -77,6 +77,11 @@ module ActiveRecord
}]
end
+ # Checks whether the columns hash is already cached for a table.
+ def columns_hash?(table_name)
+ @columns_hash.key?(table_name)
+ end
+
# Clears out internal caches
def clear!
@columns.clear
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index 29f0e19a98..cb9d32a577 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -30,19 +30,19 @@ module ActiveRecord
end
def quoted_true
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "1" : "'t'"
+ "1"
end
def unquoted_true
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 1 : "t"
+ 1
end
def quoted_false
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "0" : "'f'"
+ "0"
end
def unquoted_false
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 0 : "f"
+ 0
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 44c6e99112..14dbd20bcd 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -76,22 +76,15 @@ module ActiveRecord
json: { name: "json" },
}
- ##
- # :singleton-method:
- # Indicates whether boolean values are stored in sqlite3 databases as 1
- # and 0 or 't' and 'f'. Leaving <tt>ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer</tt>
- # set to false is deprecated. SQLite databases have used 't' and 'f' to
- # serialize boolean values and must have old data converted to 1 and 0
- # (its native boolean serialization) before setting this flag to true.
- # Conversion can be accomplished by setting up a rake task which runs
- #
- # ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1)
- # ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0)
- # for all models and all boolean columns, after which the flag must be set
- # to true by adding the following to your <tt>application.rb</tt> file:
- #
- # Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
- class_attribute :represent_boolean_as_integer, default: false
+ def self.represent_boolean_as_integer=(value) # :nodoc:
+ if value == false
+ raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
+ end
+
+ ActiveSupport::Deprecation.warn(
+ "`.represent_boolean_as_integer=` is now always true, so setting this is deprecated and will be removed in Rails 6.1."
+ )
+ end
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
private
@@ -314,11 +307,6 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
- def valid_alter_table_type?(type, options = {})
- !invalid_alter_table_type?(type, options)
- end
- deprecate :valid_alter_table_type?
-
def add_column(table_name, column_name, type, options = {}) #:nodoc:
if invalid_alter_table_type?(type, options)
alter_table(table_name) do |definition|
@@ -390,14 +378,6 @@ module ActiveRecord
end
end
- def insert_fixtures(rows, table_name)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `insert_fixtures` is deprecated and will be removed in the next version of Rails.
- Consider using `insert_fixtures_set` for performance improvement.
- MSG
- insert_fixtures_set(table_name => rows)
- end
-
def insert_fixtures_set(fixture_set, tables_to_delete = [])
disable_referential_integrity do
transaction(requires_new: true) do
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 8f4d292a4b..369d63e40a 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -169,15 +169,12 @@ module ActiveRecord
where(key => params.bind).limit(1)
}
- record = statement.execute([id], connection).first
+ record = statement.execute([id], connection)&.first
unless record
raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
name, primary_key, id)
end
record
- rescue ::RangeError
- raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'",
- name, primary_key)
end
def find_by(*args) # :nodoc:
@@ -201,11 +198,9 @@ module ActiveRecord
where(wheres).limit(1)
}
begin
- statement.execute(hash.values, connection).first
+ statement.execute(hash.values, connection)&.first
rescue TypeError
raise ActiveRecord::StatementInvalid
- rescue ::RangeError
- nil
end
end
@@ -282,6 +277,10 @@ module ActiveRecord
TypeCaster::Map.new(self)
end
+ def _internal? # :nodoc:
+ false
+ end
+
private
def cached_find_by_statement(key, &block)
@@ -350,9 +349,7 @@ module ActiveRecord
# Initialize an empty model object from +attributes+.
# +attributes+ should be an attributes object, and unlike the
# `initialize` method, no assignment calls are made per attribute.
- #
- # :nodoc:
- def init_with_attributes(attributes, new_record = false)
+ def init_with_attributes(attributes, new_record = false) # :nodoc:
init_internals
@new_record = new_record
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index 72035a986b..16dff9c2b0 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -10,7 +10,7 @@ module ActiveRecord
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 90fb10a1f1..fa6f0d36ec 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -61,23 +61,14 @@ module ActiveRecord
#
# Product.cache_versioning = false
# Product.find(5).cache_key # => "products/5-20071224150000" (updated_at available)
- def cache_key(*timestamp_names)
+ def cache_key
if new_record?
"#{model_name.cache_key}/new"
else
- if cache_version && timestamp_names.none?
+ if cache_version
"#{model_name.cache_key}/#{id}"
else
- timestamp = if timestamp_names.any?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Specifying a timestamp name for #cache_key has been deprecated in favor of
- the explicit #cache_version method that can be overwritten.
- MSG
-
- max_updated_column_timestamp(timestamp_names)
- else
- max_updated_column_timestamp
- end
+ timestamp = max_updated_column_timestamp
if timestamp
timestamp = timestamp.utc.to_s(cache_timestamp_format)
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
index 3626a13d7c..88b0c828ae 100644
--- a/activerecord/lib/active_record/internal_metadata.rb
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -8,6 +8,10 @@ module ActiveRecord
# as which environment migrations were run in.
class InternalMetadata < ActiveRecord::Base # :nodoc:
class << self
+ def _internal?
+ true
+ end
+
def primary_key
"key"
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 24782f8748..4b2e9ed81c 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1087,10 +1087,6 @@ module ActiveRecord
migrations.last || NullMigration.new
end
- def parse_migration_filename(filename) # :nodoc:
- File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
- end
-
def migrations
migrations = migration_files.map do |file|
version, name, scope = parse_migration_filename(file)
@@ -1122,11 +1118,6 @@ module ActiveRecord
(db_list + file_list).sort_by { |_, version, _| version }
end
- def migration_files
- paths = Array(migrations_paths)
- Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
- end
-
def current_environment
ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
end
@@ -1145,6 +1136,15 @@ module ActiveRecord
end
private
+ def migration_files
+ paths = Array(migrations_paths)
+ Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
+ end
+
+ def parse_migration_filename(filename)
+ File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
+ end
+
def move(direction, steps)
migrator = Migrator.new(direction, migrations)
@@ -1169,13 +1169,6 @@ module ActiveRecord
class << self
attr_accessor :migrations_paths
- def migrations_path=(path)
- ActiveSupport::Deprecation.warn \
- "`ActiveRecord::Migrator.migrations_path=` is now deprecated and will be removed in Rails 6.0. " \
- "You can set the `migrations_paths` on the `connection` instead through the `database.yml`."
- self.migrations_paths = [path]
- end
-
# For cases where a table doesn't exist like loading from schema cache
def current_version
MigrationContext.new(migrations_paths).current_version
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 8f6fcfcaea..608182e363 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -36,9 +36,7 @@ module ActiveRecord
class V5_1 < V5_2
def change_column(table_name, column_name, type, options = {})
if adapter_name == "PostgreSQL"
- clear_cache!
- sql = connection.send(:change_column_sql, table_name, column_name, type, options)
- execute "ALTER TABLE #{quote_table_name(table_name)} #{sql}"
+ super(table_name, column_name, type, options.except(:default, :null, :comment))
change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 7bf8d568df..2213fbefb4 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -96,11 +96,13 @@ module ActiveRecord
# When running callbacks is not needed for each record update,
# it is preferred to use {update_all}[rdoc-ref:Relation#update_all]
# for updating all records in a single query.
- def update(id, attributes)
+ def update(id = :all, attributes)
if id.is_a?(Array)
id.map { |one_id| find(one_id) }.each_with_index { |object, idx|
object.update(attributes[idx])
}
+ elsif id == :all
+ all.each { |record| record.update(attributes) }
else
if ActiveRecord::Base === id
raise ArgumentError,
@@ -434,7 +436,7 @@ module ActiveRecord
end
alias update_attributes update
- deprecate :update_attributes
+ deprecate update_attributes: "please, use update instead"
# Updates its receiver just like #update but calls #save! instead
# of +save+, so an exception is raised if the record is invalid and saving will fail.
@@ -448,7 +450,7 @@ module ActiveRecord
end
alias update_attributes! update!
- deprecate :update_attributes!
+ deprecate update_attributes!: "please, use update! instead"
# Equivalent to <code>update_columns(name => value)</code>.
def update_column(name, value)
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 538659d6bd..6346a95d57 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -140,7 +140,19 @@ end_error
initializer "active_record.define_attribute_methods" do |app|
config.after_initialize do
ActiveSupport.on_load(:active_record) do
- descendants.each(&:define_attribute_methods) if app.config.eager_load
+ if app.config.eager_load
+ descendants.each do |model|
+ # SchemaMigration and InternalMetadata both override `table_exists?`
+ # to bypass the schema cache, so skip them to avoid the extra queries.
+ next if model._internal?
+
+ # If there's no connection yet, or the schema cache doesn't have the columns
+ # hash for the model cached, `define_attribute_methods` would trigger a query.
+ next unless model.connected? && model.connection.schema_cache.columns_hash?(model.table_name)
+
+ model.define_attribute_methods
+ end
+ end
end
end
end
@@ -155,8 +167,18 @@ end_error
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
- configs = app.config.active_record.dup
+ configs = app.config.active_record
+
+ represent_boolean_as_integer = configs.sqlite3.delete(:represent_boolean_as_integer)
+
+ unless represent_boolean_as_integer.nil?
+ ActiveSupport.on_load(:active_record_sqlite3adapter) do
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = represent_boolean_as_integer
+ end
+ end
+
configs.delete(:sqlite3)
+
configs.each do |k, v|
send "#{k}=", v
end
@@ -224,35 +246,6 @@ end_error
end
end
- initializer "active_record.check_represent_sqlite3_boolean_as_integer" do
- config.after_initialize do
- ActiveSupport.on_load(:active_record_sqlite3adapter) do
- represent_boolean_as_integer = Rails.application.config.active_record.sqlite3.delete(:represent_boolean_as_integer)
- unless represent_boolean_as_integer.nil?
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = represent_boolean_as_integer
- end
-
- unless ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer
- ActiveSupport::Deprecation.warn <<-MSG
-Leaving `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer`
-set to false is deprecated. SQLite databases have used 't' and 'f' to serialize
-boolean values and must have old data converted to 1 and 0 (its native boolean
-serialization) before setting this flag to true. Conversion can be accomplished
-by setting up a rake task which runs
-
- ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1)
- ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0)
-
-for all models and all boolean columns, after which the flag must be set to
-true by adding the following to your application.rb file:
-
- Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
-MSG
- end
- end
- end
- end
-
initializer "active_record.set_filter_attributes" do
ActiveSupport.on_load(:active_record) do
self.filter_attributes += Rails.application.config.filter_parameters
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d24324ecce..8de06e8466 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -407,6 +407,10 @@ namespace :railties do
if railtie.respond_to?(:paths) && (path = railtie.paths["db/migrate"].first)
railties[railtie.railtie_name] = path
end
+
+ unless ENV["MIGRATIONS_PATH"].blank?
+ railties[railtie.railtie_name] = railtie.root + ENV["MIGRATIONS_PATH"]
+ end
end
on_skip = Proc.new do |name, migration|
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index ba221a333b..a863227276 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -44,6 +44,11 @@ module ActiveRecord
end
def bind_attribute(name, value) # :nodoc:
+ if reflection = klass._reflect_on_association(name)
+ name = reflection.foreign_key
+ value = value.read_attribute(reflection.klass.primary_key) unless value.nil?
+ end
+
attr = arel_attribute(name)
bind = predicate_builder.build_bind_attribute(attr.name, value)
yield attr, bind
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 0fa5ba2e50..cef31bea94 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -41,15 +41,13 @@ module ActiveRecord
def count(column_name = nil)
if block_given?
unless column_name.nil?
- ActiveSupport::Deprecation.warn \
- "When `count' is called with a block, it ignores other arguments. " \
- "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0."
+ raise ArgumentError, "Column name argument is not supported when a block is passed."
end
- return super()
+ super()
+ else
+ calculate(:count, column_name)
end
-
- calculate(:count, column_name)
end
# Calculates the average value on a given column. Returns +nil+ if there's
@@ -86,15 +84,13 @@ module ActiveRecord
def sum(column_name = nil)
if block_given?
unless column_name.nil?
- ActiveSupport::Deprecation.warn \
- "When `sum' is called with a block, it ignores other arguments. " \
- "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0."
+ raise ArgumentError, "Column name argument is not supported when a block is passed."
end
- return super()
+ super()
+ else
+ calculate(:sum, column_name)
end
-
- calculate(:sum, column_name)
end
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
@@ -245,7 +241,7 @@ module ActiveRecord
if distinct && (group_values.any? || select_values.empty? && order_values.empty?)
column_name = primary_key
end
- elsif /\s*DISTINCT[\s(]+/i.match?(column_name.to_s)
+ elsif column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
distinct = nil
end
end
@@ -401,7 +397,7 @@ module ActiveRecord
case operation
when "count" then value.to_i
when "sum" then type.deserialize(value || 0)
- when "average" then value.respond_to?(:to_d) ? value.to_d : value
+ when "average" then value&.respond_to?(:to_d) ? value.to_d : value
else type.deserialize(value)
end
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 6f67dd3784..6e8a1fcad4 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -112,15 +112,6 @@ module ActiveRecord
if @klass.respond_to?(method)
self.class.delegate_to_scoped_klass(method)
scoping { @klass.public_send(method, *args, &block) }
- elsif @delegate_to_klass && @klass.respond_to?(method, true)
- ActiveSupport::Deprecation.warn \
- "Delegating missing #{method} method to #{@klass}. " \
- "Accessibility of private/protected class methods in :scope is deprecated and will be removed in Rails 6.0."
- @klass.send(method, *args, &block)
- elsif arel.respond_to?(method)
- ActiveSupport::Deprecation.warn \
- "Delegating #{method} to arel is deprecated and will be removed in Rails 6.0."
- arel.public_send(method, *args, &block)
else
super
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index dc03b196f4..8f1065c1e7 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -79,17 +79,12 @@ module ActiveRecord
# Post.find_by "published_at < ?", 2.weeks.ago
def find_by(arg, *args)
where(arg, *args).take
- rescue ::RangeError
- nil
end
# Like #find_by, except that if no record is found, raises
# an ActiveRecord::RecordNotFound error.
def find_by!(arg, *args)
where(arg, *args).take!
- rescue ::RangeError
- raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
- @klass.name, @klass.primary_key)
end
# Gives a record (or N records if a parameter is supplied) without any implied
@@ -312,6 +307,8 @@ module ActiveRecord
return false if !conditions || limit_value == 0
+ conditions = sanitize_forbidden_attributes(conditions)
+
if eager_loading?
relation = apply_join_dependency(eager_loading: false)
return relation.exists?(conditions)
@@ -320,8 +317,6 @@ module ActiveRecord
relation = construct_relation_for_exists(conditions)
skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false
- rescue ::RangeError
- false
end
# This method is called whenever no records are found with either a single
@@ -432,9 +427,6 @@ module ActiveRecord
else
find_some(ids)
end
- rescue ::RangeError
- error_message = "Couldn't find #{model_name} with an out of range ID"
- raise RecordNotFound.new(error_message, model_name, primary_key, ids)
end
def find_one(id)
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index b59ff912fe..240de3bb69 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -90,16 +90,21 @@ module ActiveRecord
queries.reduce(&:or)
elsif table.aggregated_with?(key)
mapping = table.reflect_on_aggregation(key).mapping
- queries = Array.wrap(value).map do |object|
- mapping.map do |field_attr, aggregate_attr|
- if mapping.size == 1 && !object.respond_to?(aggregate_attr)
- build(table.arel_attribute(field_attr), object)
- else
- build(table.arel_attribute(field_attr), object.send(aggregate_attr))
- end
- end.reduce(&:and)
+ values = value.nil? ? [nil] : Array.wrap(value)
+ if mapping.length == 1 || values.empty?
+ column_name, aggr_attr = mapping.first
+ values = values.map do |object|
+ object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
+ end
+ build(table.arel_attribute(column_name), values)
+ else
+ queries = values.map do |object|
+ mapping.map do |field_attr, aggregate_attr|
+ build(table.arel_attribute(field_attr), object.try!(aggregate_attr))
+ end.reduce(&:and)
+ end
+ queries.reduce(&:or)
end
- queries.reduce(&:or)
else
build(table.arel_attribute(key), value)
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
index 44bb2c7ab6..2ea27c8490 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
@@ -3,11 +3,7 @@
module ActiveRecord
class PredicateBuilder
class RangeHandler # :nodoc:
- class RangeWithBinds < Struct.new(:begin, :end)
- def exclude_end?
- false
- end
- end
+ RangeWithBinds = Struct.new(:begin, :end, :exclude_end?)
def initialize(predicate_builder)
@predicate_builder = predicate_builder
@@ -16,22 +12,7 @@ module ActiveRecord
def call(attribute, value)
begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin)
end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end)
-
- if begin_bind.value.infinity?
- if end_bind.value.infinity?
- attribute.not_in([])
- elsif value.exclude_end?
- attribute.lt(end_bind)
- else
- attribute.lteq(end_bind)
- end
- elsif end_bind.value.infinity?
- attribute.gteq(begin_bind)
- elsif value.exclude_end?
- attribute.gteq(begin_bind).and(attribute.lt(end_bind))
- else
- attribute.between(RangeWithBinds.new(begin_bind, end_bind))
- end
+ attribute.between(RangeWithBinds.new(begin_bind, end_bind, value.exclude_end?))
end
private
diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb
index f64bd30d38..1dd6462d8d 100644
--- a/activerecord/lib/active_record/relation/query_attribute.rb
+++ b/activerecord/lib/active_record/relation/query_attribute.rb
@@ -20,22 +20,27 @@ module ActiveRecord
def nil?
!value_before_type_cast.is_a?(StatementCache::Substitute) &&
(value_before_type_cast.nil? || value_for_database.nil?)
+ rescue ::RangeError
end
- def boundable?
- return @_boundable if defined?(@_boundable)
- nil?
- @_boundable = true
+ def infinite?
+ infinity?(value_before_type_cast) || infinity?(value_for_database)
rescue ::RangeError
- @_boundable = false
end
- def infinity?
- _infinity?(value_before_type_cast) || boundable? && _infinity?(value_for_database)
+ def unboundable?
+ if defined?(@_unboundable)
+ @_unboundable
+ else
+ value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
+ @_unboundable = nil
+ end
+ rescue ::RangeError
+ @_unboundable = type.cast(value_before_type_cast) <=> 0
end
private
- def _infinity?(value)
+ def infinity?(value)
value.respond_to?(:infinite?) && value.infinite?
end
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 3485d9e557..e6197752bc 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -134,43 +134,6 @@ module ActiveRecord
end
private
- # Accepts a hash of SQL conditions and replaces those attributes
- # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
- # relationship with their expanded aggregate attribute values.
- #
- # Given:
- #
- # class Person < ActiveRecord::Base
- # composed_of :address, class_name: "Address",
- # mapping: [%w(address_street street), %w(address_city city)]
- # end
- #
- # Then:
- #
- # { address: Address.new("813 abc st.", "chicago") }
- # # => { address_street: "813 abc st.", address_city: "chicago" }
- def expand_hash_conditions_for_aggregates(attrs) # :doc:
- expanded_attrs = {}
- attrs.each do |attr, value|
- if aggregation = reflect_on_aggregation(attr.to_sym)
- mapping = aggregation.mapping
- mapping.each do |field_attr, aggregate_attr|
- expanded_attrs[field_attr] = if value.is_a?(Array)
- value.map { |it| it.send(aggregate_attr) }
- elsif mapping.size == 1 && !value.respond_to?(aggregate_attr)
- value
- else
- value.send(aggregate_attr)
- end
- end
- else
- expanded_attrs[attr] = value
- end
- end
- expanded_attrs
- end
- deprecate :expand_hash_conditions_for_aggregates
-
def replace_bind_variables(statement, values)
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
bound = values.dup
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 216359867c..76bf53387d 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -51,20 +51,11 @@ module ActiveRecord
if info[:version].present?
ActiveRecord::SchemaMigration.create_table
- connection.assume_migrated_upto_version(info[:version], migrations_paths)
+ connection.assume_migrated_upto_version(info[:version])
end
ActiveRecord::InternalMetadata.create_table
ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
end
-
- private
- # Returns the migrations paths.
- #
- # ActiveRecord::Schema.new.migrations_paths
- # # => ["db/migrate"] # Rails migration path by default.
- def migrations_paths
- ActiveRecord::Migrator.migrations_paths
- end
end
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index f2d8b038fa..1fca1a18f6 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -10,6 +10,10 @@ module ActiveRecord
# to be executed the next time.
class SchemaMigration < ActiveRecord::Base # :nodoc:
class << self
+ def _internal?
+ true
+ end
+
def primary_key
"version"
end
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index 1b1736dcab..95984e7ada 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -132,6 +132,8 @@ module ActiveRecord
sql = query_builder.sql_for bind_values, connection
klass.find_by_sql(sql, bind_values, preparable: true, &block)
+ rescue ::RangeError
+ nil
end
def self.unsupported_value?(value)
diff --git a/activerecord/lib/active_record/test_fixtures.rb b/activerecord/lib/active_record/test_fixtures.rb
index 7b7b3f7112..d29fc9f84b 100644
--- a/activerecord/lib/active_record/test_fixtures.rb
+++ b/activerecord/lib/active_record/test_fixtures.rb
@@ -173,10 +173,33 @@ module ActiveRecord
end
def enlist_fixture_connections
+ setup_shared_connection_pool
+
ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
end
private
+
+ # Shares the writing connection pool with connections on
+ # other handlers.
+ #
+ # In an application with a primary and replica the test fixtures
+ # need to share a connection pool so that the reading connection
+ # can see data in the open transaction on the writing connection.
+ def setup_shared_connection_pool
+ writing_handler = ActiveRecord::Base.connection_handler
+
+ ActiveRecord::Base.connection_handlers.values.each do |handler|
+ if handler != writing_handler
+ handler.connection_pool_list.each do |pool|
+ name = pool.spec.name
+ writing_connection = writing_handler.retrieve_connection_pool(name)
+ handler.send(:owner_to_pool)[name] = writing_connection
+ end
+ end
+ end
+ end
+
def load_fixtures(config)
fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config)
Hash[fixtures.map { |f| [f.name, f] }]
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index d32f971ad1..2345db7138 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -56,7 +56,7 @@ module ActiveRecord
def touch_attributes_with_time(*names, time: nil)
attribute_names = timestamp_attributes_for_update_in_model
attribute_names |= names.map(&:to_s)
- attribute_names.index_with(time ||= current_time_from_proper_timezone)
+ attribute_names.index_with(time || current_time_from_proper_timezone)
end
private
@@ -133,11 +133,10 @@ module ActiveRecord
self.class.send(:current_time_from_proper_timezone)
end
- def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update_in_model)
- timestamp_names
- .map { |attr| self[attr] }
+ def max_updated_column_timestamp
+ timestamp_attributes_for_update_in_model
+ .map { |attr| self[attr]&.to_time }
.compact
- .map(&:to_time)
.max
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 5a1dbc8e53..fb745af125 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -56,33 +56,21 @@ module ActiveRecord
end
def build_relation(klass, attribute, value)
- if reflection = klass._reflect_on_association(attribute)
- attribute = reflection.foreign_key
- value = value.attributes[reflection.klass.primary_key] unless value.nil?
- end
-
- if value.nil?
- return klass.unscoped.where!(attribute => value)
- end
-
- # the attribute may be an aliased attribute
- if klass.attribute_alias?(attribute)
- attribute = klass.attribute_alias(attribute)
+ relation = klass.unscoped
+ comparison = relation.bind_attribute(attribute, value) do |attr, bind|
+ return relation.none! if bind.unboundable?
+
+ if bind.nil?
+ attr.eq(bind)
+ elsif options[:case_sensitive]
+ klass.connection.case_sensitive_comparison(attr, bind)
+ else
+ # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
+ klass.connection.case_insensitive_comparison(attr, bind)
+ end
end
- attribute_name = attribute.to_s
- value = klass.predicate_builder.build_bind_attribute(attribute_name, value)
-
- table = klass.arel_table
- column = klass.columns_hash[attribute_name]
-
- comparison = if !options[:case_sensitive]
- # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
- klass.connection.case_insensitive_comparison(table, attribute, column, value)
- else
- klass.connection.case_sensitive_comparison(table, attribute, column, value)
- end
- klass.unscoped.where!(comparison)
+ relation.where!(comparison)
end
def scope_relation(record, relation)
diff --git a/activerecord/lib/arel.rb b/activerecord/lib/arel.rb
index dab785738e..7411b5c41b 100644
--- a/activerecord/lib/arel.rb
+++ b/activerecord/lib/arel.rb
@@ -13,7 +13,6 @@ require "arel/alias_predication"
require "arel/order_predications"
require "arel/table"
require "arel/attributes"
-require "arel/compatibility/wheres"
require "arel/visitors"
require "arel/collectors/sql_string"
diff --git a/activerecord/lib/arel/compatibility/wheres.rb b/activerecord/lib/arel/compatibility/wheres.rb
deleted file mode 100644
index c8a73f0dae..0000000000
--- a/activerecord/lib/arel/compatibility/wheres.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-module Arel # :nodoc: all
- module Compatibility # :nodoc:
- class Wheres # :nodoc:
- include Enumerable
-
- module Value # :nodoc:
- attr_accessor :visitor
- def value
- visitor.accept self
- end
-
- def name
- super.to_sym
- end
- end
-
- def initialize(engine, collection)
- @engine = engine
- @collection = collection
- end
-
- def each
- to_sql = Visitors::ToSql.new @engine
-
- @collection.each { |c|
- c.extend(Value)
- c.visitor = to_sql
- yield c
- }
- end
- end
- end
-end
diff --git a/activerecord/lib/arel/nodes/and.rb b/activerecord/lib/arel/nodes/and.rb
index c530a77bfb..bf516db35f 100644
--- a/activerecord/lib/arel/nodes/and.rb
+++ b/activerecord/lib/arel/nodes/and.rb
@@ -2,7 +2,7 @@
module Arel # :nodoc: all
module Nodes
- class And < Arel::Nodes::Node
+ class And < Arel::Nodes::NodeExpression
attr_reader :children
def initialize(children)
diff --git a/activerecord/lib/arel/nodes/bind_param.rb b/activerecord/lib/arel/nodes/bind_param.rb
index ba8340558a..344e46479f 100644
--- a/activerecord/lib/arel/nodes/bind_param.rb
+++ b/activerecord/lib/arel/nodes/bind_param.rb
@@ -24,8 +24,12 @@ module Arel # :nodoc: all
value.nil?
end
- def boundable?
- !value.respond_to?(:boundable?) || value.boundable?
+ def infinite?
+ value.respond_to?(:infinite?) && value.infinite?
+ end
+
+ def unboundable?
+ value.respond_to?(:unboundable?) && value.unboundable?
end
end
end
diff --git a/activerecord/lib/arel/nodes/case.rb b/activerecord/lib/arel/nodes/case.rb
index 654a54825e..1c4b727bf6 100644
--- a/activerecord/lib/arel/nodes/case.rb
+++ b/activerecord/lib/arel/nodes/case.rb
@@ -2,7 +2,7 @@
module Arel # :nodoc: all
module Nodes
- class Case < Arel::Nodes::Node
+ class Case < Arel::Nodes::NodeExpression
attr_accessor :case, :conditions, :default
def initialize(expression = nil, default = nil)
diff --git a/activerecord/lib/arel/nodes/casted.rb b/activerecord/lib/arel/nodes/casted.rb
index c1e6e97d6d..6e911b717d 100644
--- a/activerecord/lib/arel/nodes/casted.rb
+++ b/activerecord/lib/arel/nodes/casted.rb
@@ -27,6 +27,10 @@ module Arel # :nodoc: all
class Quoted < Arel::Nodes::Unary # :nodoc:
alias :val :value
def nil?; val.nil?; end
+
+ def infinite?
+ value.respond_to?(:infinite?) && value.infinite?
+ end
end
def self.build_quoted(other, attribute = nil)
diff --git a/activerecord/lib/arel/predications.rb b/activerecord/lib/arel/predications.rb
index 77502dd199..7dafde4952 100644
--- a/activerecord/lib/arel/predications.rb
+++ b/activerecord/lib/arel/predications.rb
@@ -35,15 +35,17 @@ module Arel # :nodoc: all
end
def between(other)
- if equals_quoted?(other.begin, -Float::INFINITY)
- if equals_quoted?(other.end, Float::INFINITY)
+ if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1
+ self.in([])
+ elsif open_ended?(other.begin)
+ if other.end.nil? || open_ended?(other.end)
not_in([])
elsif other.exclude_end?
lt(other.end)
else
lteq(other.end)
end
- elsif equals_quoted?(other.end, Float::INFINITY)
+ elsif other.end.nil? || open_ended?(other.end)
gteq(other.begin)
elsif other.exclude_end?
gteq(other.begin).and(lt(other.end))
@@ -81,15 +83,17 @@ Passing a range to `#in` is deprecated. Call `#between`, instead.
end
def not_between(other)
- if equals_quoted?(other.begin, -Float::INFINITY)
- if equals_quoted?(other.end, Float::INFINITY)
+ if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1
+ not_in([])
+ elsif open_ended?(other.begin)
+ if other.end.nil? || open_ended?(other.end)
self.in([])
elsif other.exclude_end?
gteq(other.end)
else
gt(other.end)
end
- elsif equals_quoted?(other.end, Float::INFINITY)
+ elsif other.end.nil? || open_ended?(other.end)
lt(other.begin)
else
left = lt(other.begin)
@@ -238,12 +242,16 @@ Passing a range to `#not_in` is deprecated. Call `#not_between`, instead.
others.map { |v| quoted_node(v) }
end
- def equals_quoted?(maybe_quoted, value)
- if maybe_quoted.is_a?(Nodes::Quoted)
- maybe_quoted.val == value
- else
- maybe_quoted == value
- end
+ def infinity?(value)
+ value.respond_to?(:infinite?) && value.infinite?
+ end
+
+ def unboundable?(value)
+ value.respond_to?(:unboundable?) && value.unboundable?
+ end
+
+ def open_ended?(value)
+ infinity?(value) || unboundable?(value)
end
end
end
diff --git a/activerecord/lib/arel/visitors/informix.rb b/activerecord/lib/arel/visitors/informix.rb
index 0a9713794e..208fa15aef 100644
--- a/activerecord/lib/arel/visitors/informix.rb
+++ b/activerecord/lib/arel/visitors/informix.rb
@@ -15,8 +15,9 @@ module Arel # :nodoc: all
collector << "ORDER BY "
collector = inject_join o.orders, collector, ", "
end
- collector = maybe_visit o.lock, collector
+ maybe_visit o.lock, collector
end
+
def visit_Arel_Nodes_SelectCore(o, collector)
collector = inject_join o.projections, collector, ", "
if o.source && !o.source.empty?
diff --git a/activerecord/lib/arel/visitors/oracle12.rb b/activerecord/lib/arel/visitors/oracle12.rb
index b092aa95e0..9a7fe4d626 100644
--- a/activerecord/lib/arel/visitors/oracle12.rb
+++ b/activerecord/lib/arel/visitors/oracle12.rb
@@ -20,7 +20,7 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_SelectOptions(o, collector)
collector = maybe_visit o.offset, collector
collector = maybe_visit o.limit, collector
- collector = maybe_visit o.lock, collector
+ maybe_visit o.lock, collector
end
def visit_Arel_Nodes_Limit(o, collector)
diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb
index f9fe4404eb..d0dec63860 100644
--- a/activerecord/lib/arel/visitors/to_sql.rb
+++ b/activerecord/lib/arel/visitors/to_sql.rb
@@ -208,14 +208,12 @@ module Arel # :nodoc: all
end
visit_Arel_Nodes_SelectOptions(o, collector)
-
- collector
end
def visit_Arel_Nodes_SelectOptions(o, collector)
collector = maybe_visit o.limit, collector
collector = maybe_visit o.offset, collector
- collector = maybe_visit o.lock, collector
+ maybe_visit o.lock, collector
end
def visit_Arel_Nodes_SelectCore(o, collector)
@@ -578,7 +576,7 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_In(o, collector)
if Array === o.right && !o.right.empty?
- o.right.keep_if { |value| boundable?(value) }
+ o.right.delete_if { |value| unboundable?(value) }
end
if Array === o.right && o.right.empty?
@@ -592,7 +590,7 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_NotIn(o, collector)
if Array === o.right && !o.right.empty?
- o.right.keep_if { |value| boundable?(value) }
+ o.right.delete_if { |value| unboundable?(value) }
end
if Array === o.right && o.right.empty?
@@ -631,6 +629,8 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_Equality(o, collector)
right = o.right
+ return collector << "1=0" if unboundable?(right)
+
collector = visit o.left, collector
if right.nil?
@@ -664,6 +664,8 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_NotEqual(o, collector)
right = o.right
+ return collector << "1=1" if unboundable?(right)
+
collector = visit o.left, collector
if right.nil?
@@ -814,8 +816,8 @@ module Arel # :nodoc: all
}
end
- def boundable?(value)
- !value.respond_to?(:boundable?) || value.boundable?
+ def unboundable?(value)
+ value.respond_to?(:unboundable?) && value.unboundable?
end
def has_join_sources?(o)
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 66e594d771..05d8aa59c4 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -163,6 +163,16 @@ module ActiveRecord
end
end
+ def test_preventing_writes_predicate
+ assert_not_predicate @connection, :preventing_writes?
+
+ @connection.while_preventing_writes do
+ assert_predicate @connection, :preventing_writes?
+ end
+
+ assert_not_predicate @connection, :preventing_writes?
+ end
+
def test_errors_when_an_insert_query_is_called_while_preventing_writes
assert_no_queries do
assert_raises(ActiveRecord::ReadOnlyError) do
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 2d71ee2f15..88c2ac5d0a 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -139,8 +139,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
with_real_execute do
ActiveRecord::Base.connection.create_table :delete_me
ActiveRecord::Base.connection.add_timestamps :delete_me, null: true
- assert column_present?("delete_me", "updated_at", "datetime")
- assert column_present?("delete_me", "created_at", "datetime")
+ assert column_exists?("delete_me", "updated_at", "datetime")
+ assert column_exists?("delete_me", "created_at", "datetime")
ensure
ActiveRecord::Base.connection.drop_table :delete_me rescue nil
end
@@ -152,8 +152,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
t.timestamps null: true
end
ActiveRecord::Base.connection.remove_timestamps :delete_me, null: true
- assert_not column_present?("delete_me", "updated_at", "datetime")
- assert_not column_present?("delete_me", "created_at", "datetime")
+ assert_not column_exists?("delete_me", "updated_at", "datetime")
+ assert_not column_exists?("delete_me", "created_at", "datetime")
ensure
ActiveRecord::Base.connection.drop_table :delete_me rescue nil
end
@@ -194,9 +194,4 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def method_missing(method_symbol, *arguments)
ActiveRecord::Base.connection.send(method_symbol, *arguments)
end
-
- def column_present?(table_name, column_name, type)
- results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'")
- results.first && results.first["Type"] == type
- end
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 42618c2ec3..2e7a4b498f 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -17,7 +17,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
enable_extension!("hstore", @connection)
@connection.transaction do
- @connection.create_table("pg_arrays") do |t|
+ @connection.create_table "pg_arrays", force: true do |t|
t.string "tags", array: true, limit: 255
t.integer "ratings", array: true
t.datetime :datetimes, array: true
@@ -112,6 +112,18 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert_predicate column, :array?
end
+ def test_change_column_from_non_array_to_array
+ @connection.add_column :pg_arrays, :snippets, :string
+ @connection.change_column :pg_arrays, :snippets, :text, array: true, default: [], using: "string_to_array(\"snippets\", ',')"
+
+ PgArray.reset_column_information
+ column = PgArray.columns_hash["snippets"]
+
+ assert_equal :text, column.type
+ assert_equal [], PgArray.column_defaults["snippets"]
+ assert_predicate column, :array?
+ end
+
def test_change_column_cant_make_non_array_column_to_array
@connection.add_column :pg_arrays, :a_string, :string
assert_raises ActiveRecord::StatementInvalid do
@@ -226,14 +238,6 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert_equal(PgArray.last.tags, tag_values)
end
- def test_insert_fixtures
- tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"]
- assert_deprecated do
- @connection.insert_fixtures([{ "tags" => tag_values }], "pg_arrays")
- end
- assert_equal(PgArray.last.tags, tag_values)
- end
-
def test_attribute_for_inspect_for_array_field
record = PgArray.new { |a| a.ratings = (1..10).to_a }
assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]", record.attribute_for_inspect(:ratings))
diff --git a/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb
index 305e033642..79e9efcf06 100644
--- a/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb
@@ -7,22 +7,21 @@ class PostgresqlCaseInsensitiveTest < ActiveRecord::PostgreSQLTestCase
def test_case_insensitiveness
connection = ActiveRecord::Base.connection
- table = Default.arel_table
- column = Default.columns_hash["char1"]
- comparison = connection.case_insensitive_comparison table, :char1, column, nil
+ attr = Default.arel_attribute(:char1)
+ comparison = connection.case_insensitive_comparison(attr, nil)
assert_match(/lower/i, comparison.to_sql)
- column = Default.columns_hash["char2"]
- comparison = connection.case_insensitive_comparison table, :char2, column, nil
+ attr = Default.arel_attribute(:char2)
+ comparison = connection.case_insensitive_comparison(attr, nil)
assert_match(/lower/i, comparison.to_sql)
- column = Default.columns_hash["char3"]
- comparison = connection.case_insensitive_comparison table, :char3, column, nil
+ attr = Default.arel_attribute(:char3)
+ comparison = connection.case_insensitive_comparison(attr, nil)
assert_match(/lower/i, comparison.to_sql)
- column = Default.columns_hash["multiline_default"]
- comparison = connection.case_insensitive_comparison table, :multiline_default, column, nil
+ attr = Default.arel_attribute(:multiline_default)
+ comparison = connection.case_insensitive_comparison(attr, nil)
assert_match(/lower/i, comparison.to_sql)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 40b58e86bf..9d26f32102 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -6,12 +6,8 @@ require "securerandom"
class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
def setup
+ super
@conn = ActiveRecord::Base.connection
- @initial_represent_boolean_as_integer = ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer
- end
-
- def teardown
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = @initial_represent_boolean_as_integer
end
def test_type_cast_binary_encoding_without_logger
@@ -22,18 +18,10 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
end
def test_type_cast_true
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = false
- assert_equal "t", @conn.type_cast(true)
-
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
assert_equal 1, @conn.type_cast(true)
end
def test_type_cast_false
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = false
- assert_equal "f", @conn.type_cast(false)
-
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
assert_equal 0, @conn.type_cast(false)
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 56ceb45040..5c41c14171 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -536,10 +536,6 @@ module ActiveRecord
end
end
- def test_deprecate_valid_alter_table_type
- assert_deprecated { @conn.valid_alter_table_type?(:string) }
- end
-
def test_db_is_not_readonly_when_readonly_option_is_false
conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
diff --git a/activerecord/test/cases/arel/attributes/attribute_test.rb b/activerecord/test/cases/arel/attributes/attribute_test.rb
index 671e273543..c7bd0a053b 100644
--- a/activerecord/test/cases/arel/attributes/attribute_test.rb
+++ b/activerecord/test/cases/arel/attributes/attribute_test.rb
@@ -560,7 +560,7 @@ module Arel
end
end
- describe "with a range" do
+ describe "#between" do
it "can be constructed with a standard range" do
attribute = Attribute.new nil, nil
node = attribute.between(1..3)
@@ -628,7 +628,6 @@ module Arel
node.must_equal Nodes::NotIn.new(attribute, [])
end
-
it "can be constructed with a range ending at Infinity" do
attribute = Attribute.new nil, nil
node = attribute.between(0..::Float::INFINITY)
@@ -639,6 +638,18 @@ module Arel
)
end
+ if Gem::Version.new("2.6.0") <= Gem::Version.new(RUBY_VERSION)
+ it "can be constructed with a range implicitly ending at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(eval("0..")) # Use eval for compatibility with Ruby < 2.6 parser
+
+ node.must_equal Nodes::GreaterThanOrEqual.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ )
+ end
+ end
+
it "can be constructed with a quoted range ending at Infinity" do
attribute = Attribute.new nil, nil
node = attribute.between(quoted_range(0, ::Float::INFINITY, false))
@@ -664,14 +675,6 @@ module Arel
)
])
end
-
- def quoted_range(begin_val, end_val, exclude)
- OpenStruct.new(
- begin: Nodes::Quoted.new(begin_val),
- end: Nodes::Quoted.new(end_val),
- exclude_end?: exclude,
- )
- end
end
describe "#in" do
@@ -753,21 +756,23 @@ module Arel
end
end
- describe "with a range" do
+ describe "#not_between" do
it "can be constructed with a standard range" do
attribute = Attribute.new nil, nil
node = attribute.not_between(1..3)
- node.must_equal Nodes::Grouping.new(Nodes::Or.new(
- Nodes::LessThan.new(
- attribute,
- Nodes::Casted.new(1, attribute)
- ),
- Nodes::GreaterThan.new(
- attribute,
- Nodes::Casted.new(3, attribute)
+ node.must_equal Nodes::Grouping.new(
+ Nodes::Or.new(
+ Nodes::LessThan.new(
+ attribute,
+ Nodes::Casted.new(1, attribute)
+ ),
+ Nodes::GreaterThan.new(
+ attribute,
+ Nodes::Casted.new(3, attribute)
+ )
)
- ))
+ )
end
it "can be constructed with a range starting from -Infinity" do
@@ -780,6 +785,16 @@ module Arel
)
end
+ it "can be constructed with a quoted range starting from -Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(quoted_range(-::Float::INFINITY, 3, false))
+
+ node.must_equal Nodes::GreaterThan.new(
+ attribute,
+ Nodes::Quoted.new(3)
+ )
+ end
+
it "can be constructed with an exclusive range starting from -Infinity" do
attribute = Attribute.new nil, nil
node = attribute.not_between(-::Float::INFINITY...3)
@@ -790,6 +805,16 @@ module Arel
)
end
+ it "can be constructed with a quoted exclusive range starting from -Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(quoted_range(-::Float::INFINITY, 3, true))
+
+ node.must_equal Nodes::GreaterThanOrEqual.new(
+ attribute,
+ Nodes::Quoted.new(3)
+ )
+ end
+
it "can be constructed with an infinite range" do
attribute = Attribute.new nil, nil
node = attribute.not_between(-::Float::INFINITY..::Float::INFINITY)
@@ -797,6 +822,13 @@ module Arel
node.must_equal Nodes::In.new(attribute, [])
end
+ it "can be constructed with a quoted infinite range" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(quoted_range(-::Float::INFINITY, ::Float::INFINITY, false))
+
+ node.must_equal Nodes::In.new(attribute, [])
+ end
+
it "can be constructed with a range ending at Infinity" do
attribute = Attribute.new nil, nil
node = attribute.not_between(0..::Float::INFINITY)
@@ -807,20 +839,44 @@ module Arel
)
end
+ if Gem::Version.new("2.6.0") <= Gem::Version.new(RUBY_VERSION)
+ it "can be constructed with a range implicitly ending at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(eval("0..")) # Use eval for compatibility with Ruby < 2.6 parser
+
+ node.must_equal Nodes::LessThan.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ )
+ end
+ end
+
+ it "can be constructed with a quoted range ending at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(quoted_range(0, ::Float::INFINITY, false))
+
+ node.must_equal Nodes::LessThan.new(
+ attribute,
+ Nodes::Quoted.new(0)
+ )
+ end
+
it "can be constructed with an exclusive range" do
attribute = Attribute.new nil, nil
node = attribute.not_between(0...3)
- node.must_equal Nodes::Grouping.new(Nodes::Or.new(
- Nodes::LessThan.new(
- attribute,
- Nodes::Casted.new(0, attribute)
- ),
- Nodes::GreaterThanOrEqual.new(
- attribute,
- Nodes::Casted.new(3, attribute)
+ node.must_equal Nodes::Grouping.new(
+ Nodes::Or.new(
+ Nodes::LessThan.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ ),
+ Nodes::GreaterThanOrEqual.new(
+ attribute,
+ Nodes::Casted.new(3, attribute)
+ )
)
- ))
+ )
end
end
@@ -1010,6 +1066,15 @@ module Arel
condition.to_sql.must_equal %("foo"."id" = (select 1))
end
end
+
+ private
+ def quoted_range(begin_val, end_val, exclude)
+ OpenStruct.new(
+ begin: Nodes::Quoted.new(begin_val),
+ end: Nodes::Quoted.new(end_val),
+ exclude_end?: exclude,
+ )
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/nodes/and_test.rb b/activerecord/test/cases/arel/nodes/and_test.rb
index eff54abd91..d123ca9fd0 100644
--- a/activerecord/test/cases/arel/nodes/and_test.rb
+++ b/activerecord/test/cases/arel/nodes/and_test.rb
@@ -16,6 +16,15 @@ module Arel
assert_equal 2, array.uniq.size
end
end
+
+ describe "functions as node expression" do
+ it "allows aliasing" do
+ aliased = And.new(["foo", "bar"]).as("baz")
+
+ assert_kind_of As, aliased
+ assert_kind_of SqlLiteral, aliased.right
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/nodes/case_test.rb b/activerecord/test/cases/arel/nodes/case_test.rb
index 89861488df..946c2b0453 100644
--- a/activerecord/test/cases/arel/nodes/case_test.rb
+++ b/activerecord/test/cases/arel/nodes/case_test.rb
@@ -80,6 +80,16 @@ module Arel
assert_equal 2, array.uniq.size
end
end
+
+ describe "#as" do
+ it "allows aliasing" do
+ node = Case.new "foo"
+ as = node.as("bar")
+
+ assert_equal node, as.left
+ assert_kind_of Arel::Nodes::SqlLiteral, as.right
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index acafbe0b4d..a61569420e 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -1293,17 +1293,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_belongs_to_with_out_of_range_value_assigning
- model = Class.new(Comment) do
+ model = Class.new(Author) do
def self.name; "Temp"; end
- validates :post, presence: true
+ validates :author_address, presence: true
end
- comment = model.new
- comment.post_id = 9223372036854775808 # out of range in the bigint
+ author = model.new
+ author.author_address_id = 9223372036854775808 # out of range in the bigint
- assert_nil comment.post
- assert_not_predicate comment, :valid?
- assert_equal [{ error: :blank }], comment.errors.details[:post]
+ assert_nil author.author_address
+ assert_not_predicate author, :valid?
+ assert_equal [{ error: :blank }], author.errors.details[:author_address]
end
def test_polymorphic_with_custom_primary_key
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 5921193374..2f090d9862 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -27,6 +27,7 @@ require "models/categorization"
require "models/minivan"
require "models/speedometer"
require "models/reference"
+require "models/job"
require "models/college"
require "models/student"
require "models/pirate"
@@ -1815,6 +1816,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal num_accounts, Account.count
end
+ def test_depends_and_nullify_on_polymorphic_assoc
+ author = PersonWithPolymorphicDependentNullifyComments.create!(first_name: "Laertis")
+ comment = posts(:welcome).comments.first
+ comment.author = author
+ comment.save!
+
+ assert_equal comment.author_id, author.id
+ assert_equal comment.author_type, author.class.name
+
+ author.destroy
+ comment.reload
+
+ assert_nil comment.author_id
+ assert_nil comment.author_type
+ end
+
def test_restrict_with_exception
firm = RestrictedWithExceptionFirm.create!(name: "restrict")
firm.companies.create(name: "child")
@@ -2910,6 +2927,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_has_many_with_out_of_range_value
+ reference = Reference.create!(id: 2147483648) # out of range in the integer
+ assert_equal [], reference.ideal_jobs
+ end
+
private
def force_signal37_to_load_all_clients_of_firm
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index bf574f6637..3e5b5c1275 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -12,6 +12,9 @@ require "models/bulb"
require "models/author"
require "models/image"
require "models/post"
+require "models/drink_designer"
+require "models/chef"
+require "models/department"
class HasOneAssociationsTest < ActiveRecord::TestCase
self.use_transactional_tests = false unless supports_savepoints?
@@ -114,6 +117,21 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_nil Account.find(old_account_id).firm_id
end
+ def test_nullify_on_polymorphic_association
+ department = Department.create!
+ designer = DrinkDesignerWithPolymorphicDependentNullifyChef.create!
+ chef = department.chefs.create!(employable: designer)
+
+ assert_equal chef.employable_id, designer.id
+ assert_equal chef.employable_type, designer.class.name
+
+ designer.destroy!
+ chef.reload
+
+ assert_nil chef.employable_id
+ assert_nil chef.employable_type
+ end
+
def test_nullification_on_destroyed_association
developer = Developer.create!(name: "Someone")
ship = Ship.create!(name: "Planet Caravan", developer: developer)
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index d21218a997..cf6e280898 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -430,7 +430,7 @@ class EachTest < ActiveRecord::TestCase
assert_kind_of ActiveRecord::Relation, relation
assert_kind_of Post, relation.first
- relation = [not_a_post] * relation.count
+ [not_a_post] * relation.count
end
end
end
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index bd5f157ca1..22a98036f3 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -77,10 +77,6 @@ if ActiveRecord::Base.connection.prepared_statements
assert_logs_binds(binds)
end
- def test_deprecate_supports_statement_cache
- assert_deprecated { ActiveRecord::Base.connection.supports_statement_cache? }
- end
-
private
def assert_logs_binds(binds)
payload = {
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 4d3db912c5..850bc49676 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -57,12 +57,8 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 3, value
end
- def test_should_return_nil_to_d_as_average
- if nil.respond_to?(:to_d)
- assert_equal BigDecimal(0), NumericData.average(:bank_balance)
- else
- assert_nil NumericData.average(:bank_balance)
- end
+ def test_should_return_nil_as_average
+ assert_nil NumericData.average(:bank_balance)
end
def test_should_get_maximum_of_field
@@ -432,6 +428,8 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_count_selected_field_with_include
assert_equal 6, Account.includes(:firm).distinct.count
assert_equal 4, Account.includes(:firm).distinct.select(:credit_limit).count
+ assert_equal 4, Account.includes(:firm).distinct.count("DISTINCT credit_limit")
+ assert_equal 4, Account.includes(:firm).distinct.count("DISTINCT(credit_limit)")
end
def test_should_not_perform_joined_include_by_default
@@ -840,13 +838,13 @@ class CalculationsTest < ActiveRecord::TestCase
def test_pick_one
assert_equal "The First Topic", Topic.order(:id).pick(:heading)
assert_nil Topic.none.pick(:heading)
- assert_nil Topic.where("1=0").pick(:heading)
+ assert_nil Topic.where(id: 9999999999999999999).pick(:heading)
end
def test_pick_two
assert_equal ["David", "david@loudthinking.com"], Topic.order(:id).pick(:author_name, :author_email_address)
assert_nil Topic.none.pick(:author_name, :author_email_address)
- assert_nil Topic.where("1=0").pick(:author_name, :author_email_address)
+ assert_nil Topic.where(id: 9999999999999999999).pick(:author_name, :author_email_address)
end
def test_pick_delegate_to_all
@@ -919,15 +917,15 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal({ "proposed" => 2, "published" => 2 }, Book.group(:status).count)
end
- def test_deprecate_count_with_block_and_column_name
- assert_deprecated do
- assert_equal 6, Account.count(:firm_id) { true }
+ def test_count_with_block_and_column_name_raises_an_error
+ assert_raises(ArgumentError) do
+ Account.count(:firm_id) { true }
end
end
- def test_deprecate_sum_with_block_and_column_name
- assert_deprecated do
- assert_equal 6, Account.sum(:firm_id) { 1 }
+ def test_sum_with_block_and_column_name_raises_an_error
+ assert_raises(ArgumentError) do
+ Account.sum(:firm_id) { 1 }
end
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
index 0b3fb82e12..865aacc1b5 100644
--- a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
@@ -108,6 +108,7 @@ module ActiveRecord
ActiveRecord::Base.connected_to(role: :reading) do
@ro_handler = ActiveRecord::Base.connection_handler
assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:reading]
+ assert_equal :reading, ActiveRecord::Base.current_role
assert ActiveRecord::Base.connected_to?(role: :reading)
assert_not ActiveRecord::Base.connected_to?(role: :writing)
end
@@ -115,6 +116,7 @@ module ActiveRecord
ActiveRecord::Base.connected_to(role: :writing) do
assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:writing]
assert_not_equal @ro_handler, ActiveRecord::Base.connection_handler
+ assert_equal :writing, ActiveRecord::Base.current_role
assert ActiveRecord::Base.connected_to?(role: :writing)
assert_not ActiveRecord::Base.connected_to?(role: :reading)
end
@@ -129,6 +131,7 @@ module ActiveRecord
previous_url, ENV["DATABASE_URL"] = ENV["DATABASE_URL"], "postgres://localhost/foo"
ActiveRecord::Base.connected_to(database: { writing: "postgres://localhost/bar" }) do
+ assert_equal :writing, ActiveRecord::Base.current_role
assert ActiveRecord::Base.connected_to?(role: :writing)
handler = ActiveRecord::Base.connection_handler
@@ -148,6 +151,7 @@ module ActiveRecord
config = { adapter: "sqlite3", database: "db/readonly.sqlite3" }
ActiveRecord::Base.connected_to(database: { writing: config }) do
+ assert_equal :writing, ActiveRecord::Base.current_role
assert ActiveRecord::Base.connected_to?(role: :writing)
handler = ActiveRecord::Base.connection_handler
@@ -187,6 +191,7 @@ module ActiveRecord
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
ActiveRecord::Base.connected_to(database: :readonly) do
+ assert_equal :readonly, ActiveRecord::Base.current_role
assert ActiveRecord::Base.connected_to?(role: :readonly)
handler = ActiveRecord::Base.connection_handler
@@ -211,6 +216,7 @@ module ActiveRecord
assert_equal 1, ActiveRecord::Base.connection_handlers.size
assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:writing]
+ assert_equal :writing, ActiveRecord::Base.current_role
assert ActiveRecord::Base.connected_to?(role: :writing)
ensure
ActiveRecord::Base.configurations = @prev_configs
diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
index 02e76ce146..38331aa641 100644
--- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
@@ -27,8 +27,12 @@ if current_adapter?(:Mysql2Adapter)
def test_string_types
assert_lookup_type :string, "enum('one', 'two', 'three')"
assert_lookup_type :string, "ENUM('one', 'two', 'three')"
+ assert_lookup_type :string, "enum ('one', 'two', 'three')"
+ assert_lookup_type :string, "ENUM ('one', 'two', 'three')"
assert_lookup_type :string, "set('one', 'two', 'three')"
assert_lookup_type :string, "SET('one', 'two', 'three')"
+ assert_lookup_type :string, "set ('one', 'two', 'three')"
+ assert_lookup_type :string, "SET ('one', 'two', 'three')"
end
def test_set_type_with_value_matching_other_type
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
index 67496381d1..727cab77f5 100644
--- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb
+++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
@@ -91,6 +91,22 @@ module ActiveRecord
@cache.clear_data_source_cache!("posts")
end
+ test "#columns_hash? is populated by #columns_hash" do
+ assert_not @cache.columns_hash?("posts")
+
+ @cache.columns_hash("posts")
+
+ assert @cache.columns_hash?("posts")
+ end
+
+ test "#columns_hash? is not populated by #data_source_exists?" do
+ assert_not @cache.columns_hash?("posts")
+
+ @cache.data_source_exists?("posts")
+
+ assert_not @cache.columns_hash?("posts")
+ end
+
private
def schema_dump_path
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 99d286dc52..cc4f86a0fb 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -144,7 +144,7 @@ class CounterCacheTest < ActiveRecord::TestCase
test "update other counters on parent destroy" do
david, joanna = dog_lovers(:david, :joanna)
- joanna = joanna # squelch a warning
+ _ = joanna # squelch a warning
assert_difference "joanna.reload.dogs_count", -1 do
david.destroy
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 961ae03a4c..6af2a43c7f 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -21,6 +21,7 @@ require "models/dog"
require "models/car"
require "models/tyre"
require "models/subscriber"
+require "support/stubs/strong_parameters"
class FinderTest < ActiveRecord::TestCase
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars
@@ -224,6 +225,18 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Subscriber.exists?(" ")
end
+ def test_exists_with_strong_parameters
+ assert_equal false, Subscriber.exists?(Parameters.new(nick: "foo").permit!)
+
+ Subscriber.create!(nick: "foo")
+
+ assert_equal true, Subscriber.exists?(Parameters.new(nick: "foo").permit!)
+
+ assert_raises(ActiveModel::ForbiddenAttributesError) do
+ Subscriber.exists?(Parameters.new(nick: "foo"))
+ end
+ end
+
def test_exists_passing_active_record_object_is_not_permitted
assert_raises(ArgumentError) do
Topic.exists?(Topic.new)
@@ -269,6 +282,17 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.order(Arel.sql("invalid sql here")).exists?
end
+ def test_exists_with_large_number
+ assert_equal true, Topic.where(id: [1, 9223372036854775808]).exists?
+ assert_equal true, Topic.where(id: 1..9223372036854775808).exists?
+ assert_equal true, Topic.where(id: -9223372036854775809..9223372036854775808).exists?
+ assert_equal false, Topic.where(id: 9223372036854775808..9223372036854775809).exists?
+ assert_equal false, Topic.where(id: -9223372036854775810..-9223372036854775809).exists?
+ assert_equal false, Topic.where(id: 9223372036854775808..1).exists?
+ assert_equal true, Topic.where(id: 1).or(Topic.where(id: 9223372036854775808)).exists?
+ assert_equal true, Topic.where.not(id: 9223372036854775808).exists?
+ end
+
def test_exists_with_joins
assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
end
@@ -370,16 +394,19 @@ class FinderTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) do
Topic.where("1=1").find(9999999999999999999999999999999)
end
+ assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find(1)
end
def test_find_by_on_relation_with_large_number
assert_nil Topic.where("1=1").find_by(id: 9999999999999999999999999999999)
+ assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find_by(id: 1)
end
def test_find_by_bang_on_relation_with_large_number
assert_raises(ActiveRecord::RecordNotFound) do
Topic.where("1=1").find_by!(id: 9999999999999999999999999999999)
end
+ assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find_by!(id: 1)
end
def test_find_an_empty_array
@@ -938,6 +965,7 @@ class FinderTest < ActiveRecord::TestCase
assert_kind_of Money, zaphod_balance
found_customers = Customer.where(balance: [david_balance, zaphod_balance])
assert_equal [customers(:david), customers(:zaphod)], found_customers.sort_by(&:id)
+ assert_equal Customer.where(balance: [david_balance.amount, zaphod_balance.amount]).to_sql, found_customers.to_sql
end
def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate
@@ -975,6 +1003,24 @@ class FinderTest < ActiveRecord::TestCase
assert_equal customers(:david), found_customer
end
+ def test_hash_condition_find_nil_with_aggregate_having_one_mapping
+ assert_nil customers(:zaphod).gps_location
+ found_customer = Customer.where(gps_location: nil, name: customers(:zaphod).name).first
+ assert_equal customers(:zaphod), found_customer
+ end
+
+ def test_hash_condition_find_nil_with_aggregate_having_multiple_mappings
+ customers(:david).update(address: nil)
+ assert_nil customers(:david).address_street
+ assert_nil customers(:david).address_city
+ found_customer = Customer.where(address: nil, name: customers(:david).name).first
+ assert_equal customers(:david), found_customer
+ end
+
+ def test_hash_condition_find_empty_array_with_aggregate_having_multiple_mappings
+ assert_nil Customer.where(address: []).first
+ end
+
def test_condition_utc_time_interpolation_with_default_timezone_local
with_env_tz "America/New_York" do
with_timezone_config default: :local do
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index fe2f417a04..2fe4879fe6 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -301,20 +301,6 @@ class FixturesTest < ActiveRecord::TestCase
assert_equal fixtures, result.to_a
end
- def test_deprecated_insert_fixtures
- fixtures = [
- { "name" => "first", "wheels_count" => 2 },
- { "name" => "second", "wheels_count" => 3 }
- ]
- conn = ActiveRecord::Base.connection
- conn.delete("DELETE FROM aircraft")
- assert_deprecated do
- conn.insert_fixtures(fixtures, "aircraft")
- end
- result = conn.select_all("SELECT name, wheels_count FROM aircraft ORDER BY id")
- assert_equal fixtures, result.to_a
- end
-
def test_broken_yaml_exception
badyaml = Tempfile.new ["foo", ".yml"]
badyaml.write "a: : "
@@ -1362,3 +1348,37 @@ class NilFixturePathTest < ActiveRecord::TestCase
MSG
end
end
+
+class MultipleDatabaseFixturesTest < ActiveRecord::TestCase
+ test "enlist_fixture_connections ensures multiple databases share a connection pool" do
+ with_temporary_connection_pool do
+ ActiveRecord::Base.connects_to database: { writing: :arunit, reading: :arunit2 }
+
+ rw_conn = ActiveRecord::Base.connection
+ ro_conn = ActiveRecord::Base.connection_handlers[:reading].connection_pool_list.first.connection
+
+ assert_not_equal rw_conn, ro_conn
+
+ enlist_fixture_connections
+
+ rw_conn = ActiveRecord::Base.connection
+ ro_conn = ActiveRecord::Base.connection_handlers[:reading].connection_pool_list.first.connection
+
+ assert_equal rw_conn, ro_conn
+ end
+ ensure
+ ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.connection_handler }
+ end
+
+ private
+
+ def with_temporary_connection_pool
+ old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
+ new_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new ActiveRecord::Base.connection_pool.spec
+ ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = new_pool
+
+ yield
+ ensure
+ ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = old_pool
+ end
+end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 3d3189900f..19655a2d38 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -240,7 +240,7 @@ class InheritanceTest < ActiveRecord::TestCase
cabbage = vegetable.becomes!(Cabbage)
assert_equal "Cabbage", cabbage.custom_type
- vegetable = cabbage.becomes!(Vegetable)
+ cabbage.becomes!(Vegetable)
assert_nil cabbage.custom_type
end
@@ -654,7 +654,7 @@ class InheritanceAttributeMappingTest < ActiveRecord::TestCase
assert_equal ["omg_inheritance_attribute_mapping_test/company"], ActiveRecord::Base.connection.select_values("SELECT sponsorable_type FROM sponsors")
- sponsor = Sponsor.first
+ sponsor = Sponsor.find(sponsor.id)
assert_equal startup, sponsor.sponsorable
end
end
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 5687afbc71..4185e8d682 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -191,21 +191,6 @@ class IntegrationTest < ActiveRecord::TestCase
end
end
- def test_named_timestamps_for_cache_key
- assert_deprecated do
- owner = owners(:blackbeard)
- assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at)
- end
- end
-
- def test_cache_key_when_named_timestamp_is_nil
- assert_deprecated do
- owner = owners(:blackbeard)
- owner.happy_at = nil
- assert_equal "owners/#{owner.id}", owner.cache_key(:happy_at)
- end
- end
-
def test_cache_key_is_stable_with_versioning_on
with_cache_versioning do
developer = Developer.first
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 8b0ecd2516..46e2ff79d9 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -71,13 +71,10 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migration.verbose = @verbose_was
end
- def test_migrator_migrations_path_is_deprecated
- assert_deprecated do
- ActiveRecord::Migrator.migrations_path = "/whatever"
- end
- ensure
+ def test_passing_migrations_paths_to_assume_migrated_upto_version_is_deprecated
+ ActiveRecord::SchemaMigration.create_table
assert_deprecated do
- ActiveRecord::Migrator.migrations_path = "db/migrate"
+ ActiveRecord::Base.connection.assume_migrated_upto_version(0, [])
end
end
@@ -442,7 +439,6 @@ class MigrationTest < ActiveRecord::TestCase
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
- current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrator = ActiveRecord::MigrationContext.new(migrations_path)
migrator.up
assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 4830ff2b5f..d5057ad381 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -53,6 +53,20 @@ class PersistenceTest < ActiveRecord::TestCase
assert_not_equal "2 updated", Topic.find(2).content
end
+ def test_class_level_update_without_ids
+ topics = Topic.all
+ assert_equal 5, topics.length
+ topics.each do |topic|
+ assert_not_equal "updated", topic.content
+ end
+
+ updated = Topic.update(content: "updated")
+ assert_equal 5, updated.length
+ updated.each do |topic|
+ assert_equal "updated", topic.content
+ end
+ end
+
def test_class_level_update_is_affected_by_scoping
topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index b630f782bc..402ddcf05a 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -48,7 +48,7 @@ module ActiveRecord
reaper = ConnectionPool::Reaper.new(fp, 0.0001)
reaper.run
- until fp.reaped
+ until fp.flushed
Thread.pass
end
assert fp.reaped
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index a8030c2d64..b600c999a6 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -23,23 +23,8 @@ module ActiveRecord
end
end
- module DeprecatedArelDelegationTests
- AREL_METHODS = [
- :with, :orders, :froms, :project, :projections, :taken, :constraints, :exists, :locked, :where_sql,
- :ast, :source, :join_sources, :to_dot, :create_insert, :create_true, :create_false
- ]
-
- def test_deprecate_arel_delegation
- AREL_METHODS.each do |method|
- assert_deprecated { target.public_send(method) }
- assert_deprecated { target.public_send(method) }
- end
- end
- end
-
class DelegationAssociationTest < ActiveRecord::TestCase
include ArrayDelegationTests
- include DeprecatedArelDelegationTests
def target
Post.new.comments
@@ -48,7 +33,6 @@ module ActiveRecord
class DelegationRelationTest < ActiveRecord::TestCase
include ArrayDelegationTests
- include DeprecatedArelDelegationTests
def target
Comment.all
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index 065819e0f1..8623867864 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -30,6 +30,11 @@ module ActiveRecord
assert_equal expected, Post.where("id = 1").or(Post.none).to_a
end
+ def test_or_with_large_number
+ expected = Post.where("id = 1 or id = 9223372036854775808").to_a
+ assert_equal expected, Post.where(id: 1).or(Post.where(id: 9223372036854775808)).to_a
+ end
+
def test_or_with_bind_params
assert_equal Post.find([1, 2]).sort_by(&:id), Post.where(id: 1).or(Post.where(id: 2)).sort_by(&:id)
end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index 99797528b2..d49ed092b2 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -359,6 +359,16 @@ module ActiveRecord
assert_equal author, Author.where(params.permit!).first
end
+ def test_where_with_large_number
+ assert_equal [authors(:bob)], Author.where(id: [3, 9223372036854775808])
+ assert_equal [authors(:bob)], Author.where(id: 3..9223372036854775808)
+ end
+
+ def test_to_sql_with_large_number
+ assert_equal [authors(:bob)], Author.find_by_sql(Author.where(id: [3, 9223372036854775808]).to_sql)
+ assert_equal [authors(:bob)], Author.find_by_sql(Author.where(id: 3..9223372036854775808).to_sql)
+ end
+
def test_where_with_unsupported_arguments
assert_raises(ArgumentError) { Author.where(42) }
end
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 778cf86ac3..18b27bd6d1 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -168,12 +168,6 @@ class SanitizeTest < ActiveRecord::TestCase
assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call
end
- def test_deprecated_expand_hash_conditions_for_aggregates
- assert_deprecated do
- assert_equal({ "balance" => 50 }, Customer.send(:expand_hash_conditions_for_aggregates, balance: Money.new(50)))
- end
- end
-
private
def bind(statement, *vars)
if vars.first.is_a?(Hash)
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index f707951a16..418a2ae04e 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -303,13 +303,6 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal "lifo", topic.author_name
end
- def test_deprecated_delegating_private_method
- assert_deprecated do
- scope = Topic.all.by_private_lifo
- assert_not scope.instance_variable_get(:@delegate_to_klass)
- end
- end
-
def test_reserved_scope_names
klass = Class.new(ActiveRecord::Base) do
self.table_name = "topics"
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index f6cd4f85ee..fa136fe8da 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -22,7 +22,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
end
def test_serialize_does_not_eagerly_load_columns
- Topic.reset_column_information
+ reset_column_information_of(Topic)
assert_no_queries do
Topic.serialize(:content)
end
@@ -377,7 +377,8 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic.update group: "1"
model.serialize :group, JSON
- model.reset_column_information
+
+ reset_column_information_of(model)
# This isn't strictly necessary for the test, but a little bit of
# knowledge of internals allows us to make failures far more likely.
@@ -397,4 +398,12 @@ class SerializedAttributeTest < ActiveRecord::TestCase
# raw string ("1"), or raise an exception.
assert_equal [1] * threads.size, threads.map(&:value)
end
+
+ private
+
+ def reset_column_information_of(topic_class)
+ topic_class.reset_column_information
+ # reset original topic to undefine attribute methods
+ ::Topic.reset_column_information
+ end
end
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
index e3c12f68fd..6a6d73dc38 100644
--- a/activerecord/test/cases/statement_cache_test.rb
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -4,6 +4,7 @@ require "cases/helper"
require "models/book"
require "models/liquid"
require "models/molecule"
+require "models/numeric_data"
require "models/electron"
module ActiveRecord
@@ -74,6 +75,11 @@ module ActiveRecord
assert_equal "salty", liquids[0].name
end
+ def test_statement_cache_with_strictly_cast_attribute
+ row = NumericData.create(temperature: 1.5)
+ assert_equal row, NumericData.find_by(temperature: 1.5)
+ end
+
def test_statement_cache_values_differ
cache = ActiveRecord::StatementCache.create(Book.connection) do |params|
Book.where(name: "my book")
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 45c93ca949..1009dd0f99 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -884,17 +884,6 @@ class TransactionTest < ActiveRecord::TestCase
assert_predicate transaction.state, :committed?
end
- def test_set_state_method_is_deprecated
- connection = Topic.connection
- transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
-
- transaction.commit
-
- assert_deprecated do
- transaction.state.set_state(:rolledback)
- end
- end
-
def test_mark_transaction_state_as_committed
connection = Topic.connection
transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
diff --git a/activerecord/test/cases/type/time_test.rb b/activerecord/test/cases/type/time_test.rb
new file mode 100644
index 0000000000..1a2c47479f
--- /dev/null
+++ b/activerecord/test/cases/type/time_test.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/topic"
+
+module ActiveRecord
+ module Type
+ class TimeTest < ActiveRecord::TestCase
+ def test_default_year_is_correct
+ expected_time = ::Time.utc(2000, 1, 1, 10, 30, 0)
+ topic = Topic.new(bonus_time: { 4 => 10, 5 => 30 })
+
+ assert_equal expected_time, topic.bonus_time
+
+ topic.save!
+ topic.reload
+
+ assert_equal expected_time, topic.bonus_time
+ end
+ end
+ end
+end
diff --git a/activerecord/test/models/drink_designer.rb b/activerecord/test/models/drink_designer.rb
index eb6701b84e..8258408f35 100644
--- a/activerecord/test/models/drink_designer.rb
+++ b/activerecord/test/models/drink_designer.rb
@@ -4,5 +4,11 @@ class DrinkDesigner < ActiveRecord::Base
has_one :chef, as: :employable
end
+class DrinkDesignerWithPolymorphicDependentNullifyChef < ActiveRecord::Base
+ self.table_name = "drink_designers"
+
+ has_one :chef, as: :employable, dependent: :nullify
+end
+
class MocktailDesigner < DrinkDesigner
end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 5cba1e440e..c3d15a571a 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -62,6 +62,11 @@ class PersonWithDependentNullifyJobs < ActiveRecord::Base
has_many :jobs, source: :job, through: :references, dependent: :nullify
end
+class PersonWithPolymorphicDependentNullifyComments < ActiveRecord::Base
+ self.table_name = "people"
+ has_many :comments, as: :author, dependent: :nullify
+end
+
class LoosePerson < ActiveRecord::Base
self.table_name = "people"
self.abstract_class = true
diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb
index 2a7a1e3b77..82185040d6 100644
--- a/activerecord/test/models/reference.rb
+++ b/activerecord/test/models/reference.rb
@@ -4,6 +4,7 @@ class Reference < ActiveRecord::Base
belongs_to :person
belongs_to :job
+ has_many :ideal_jobs, class_name: "Job", foreign_key: :ideal_reference_id
has_many :agents_posts_authors, through: :person
class << self; attr_accessor :make_comments; end
diff --git a/activerecord/test/support/stubs/strong_parameters.rb b/activerecord/test/support/stubs/strong_parameters.rb
new file mode 100644
index 0000000000..84f93a28b9
--- /dev/null
+++ b/activerecord/test/support/stubs/strong_parameters.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class Parameters
+ def initialize(parameters = {})
+ @parameters = parameters.with_indifferent_access
+ @permitted = false
+ end
+
+ def permitted?
+ @permitted
+ end
+
+ def permit!
+ @permitted = true
+ self
+ end
+
+ def to_h
+ @parameters.to_h
+ end
+end
diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md
index 99f1ef9d86..659bacebde 100644
--- a/activestorage/CHANGELOG.md
+++ b/activestorage/CHANGELOG.md
@@ -1,3 +1,31 @@
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Replace `config.active_storage.queue` with two options that indicate which
+ queues analysis and purge jobs should use, respectively:
+
+ * `config.active_storage.queues.analysis`
+ * `config.active_storage.queues.purge`
+
+ `config.active_storage.queue` is preferred over the new options when it's
+ set, but it is deprecated and will be removed in Rails 6.1.
+
+ *George Claghorn*
+
+* Permit generating variants of TIFF images.
+
+ *Luciano Sousa*
+
+* Use base36 (all lowercase) for all new Blob keys to prevent
+ collisions and undefined behavior with case-insensitive filesystems and
+ database indices.
+
+ *Julik Tarkhanov*
+
+* It doesn’t include an `X-CSRF-Token` header if a meta tag is not found on
+ the page. It previously included one with a value of `undefined`.
+
+ *Cameron Bothner*
+
* Fix `ArgumentError` when uploading to amazon s3
*Hiroki Sanpei*
diff --git a/activestorage/MIT-LICENSE b/activestorage/MIT-LICENSE
index eed89ac398..771376cc7e 100644
--- a/activestorage/MIT-LICENSE
+++ b/activestorage/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2017-2018 David Heinemeier Hansson, Basecamp
+Copyright (c) 2017-2019 David Heinemeier Hansson, Basecamp
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activestorage/README.md b/activestorage/README.md
index bd31f0ea58..2886169ca7 100644
--- a/activestorage/README.md
+++ b/activestorage/README.md
@@ -101,7 +101,7 @@ Variation of image attachment:
```erb
<%# Hitting the variant URL will lazy transform the original blob and then redirect to its new service location %>
-<%= image_tag user.avatar.variant(resize_to_fit: [100, 100]) %>
+<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %>
```
## Direct uploads
@@ -118,8 +118,7 @@ Active Storage, with its included JavaScript library, supports uploading directl
```
Using the npm package:
```js
- import * as ActiveStorage from "activestorage"
- ActiveStorage.start()
+ require("@rails/activestorage").start()
```
2. Annotate file inputs with the direct upload URL.
diff --git a/activestorage/app/assets/javascripts/activestorage.js b/activestorage/app/assets/javascripts/activestorage.js
index b71e251a11..e2bcb520b9 100644
--- a/activestorage/app/assets/javascripts/activestorage.js
+++ b/activestorage/app/assets/javascripts/activestorage.js
@@ -560,7 +560,10 @@
this.xhr.setRequestHeader("Content-Type", "application/json");
this.xhr.setRequestHeader("Accept", "application/json");
this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
- this.xhr.setRequestHeader("X-CSRF-Token", getMetaValue("csrf-token"));
+ var csrfToken = getMetaValue("csrf-token");
+ if (csrfToken != undefined) {
+ this.xhr.setRequestHeader("X-CSRF-Token", csrfToken);
+ }
this.xhr.addEventListener("load", function(event) {
return _this.requestDidLoad(event);
});
diff --git a/activestorage/app/javascript/activestorage/blob_record.js b/activestorage/app/javascript/activestorage/blob_record.js
index ff847892b2..7fbe315f76 100644
--- a/activestorage/app/javascript/activestorage/blob_record.js
+++ b/activestorage/app/javascript/activestorage/blob_record.js
@@ -17,7 +17,12 @@ export class BlobRecord {
this.xhr.setRequestHeader("Content-Type", "application/json")
this.xhr.setRequestHeader("Accept", "application/json")
this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
- this.xhr.setRequestHeader("X-CSRF-Token", getMetaValue("csrf-token"))
+
+ const csrfToken = getMetaValue("csrf-token")
+ if (csrfToken != undefined) {
+ this.xhr.setRequestHeader("X-CSRF-Token", csrfToken)
+ }
+
this.xhr.addEventListener("load", event => this.requestDidLoad(event))
this.xhr.addEventListener("error", event => this.requestDidError(event))
}
diff --git a/activestorage/app/jobs/active_storage/analyze_job.rb b/activestorage/app/jobs/active_storage/analyze_job.rb
index 804ee4557a..35d043d508 100644
--- a/activestorage/app/jobs/active_storage/analyze_job.rb
+++ b/activestorage/app/jobs/active_storage/analyze_job.rb
@@ -2,6 +2,8 @@
# Provides asynchronous analysis of ActiveStorage::Blob records via ActiveStorage::Blob#analyze_later.
class ActiveStorage::AnalyzeJob < ActiveStorage::BaseJob
+ queue_as { ActiveStorage.queues[:analysis] }
+
retry_on ActiveStorage::IntegrityError, attempts: 10, wait: :exponentially_longer
def perform(blob)
diff --git a/activestorage/app/jobs/active_storage/base_job.rb b/activestorage/app/jobs/active_storage/base_job.rb
index 6caab42a2d..7bc2064dc5 100644
--- a/activestorage/app/jobs/active_storage/base_job.rb
+++ b/activestorage/app/jobs/active_storage/base_job.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
class ActiveStorage::BaseJob < ActiveJob::Base
- queue_as { ActiveStorage.queue }
end
diff --git a/activestorage/app/jobs/active_storage/purge_job.rb b/activestorage/app/jobs/active_storage/purge_job.rb
index 2604977bf1..5ceb222005 100644
--- a/activestorage/app/jobs/active_storage/purge_job.rb
+++ b/activestorage/app/jobs/active_storage/purge_job.rb
@@ -2,6 +2,8 @@
# Provides asynchronous purging of ActiveStorage::Blob records via ActiveStorage::Blob#purge_later.
class ActiveStorage::PurgeJob < ActiveStorage::BaseJob
+ queue_as { ActiveStorage.queues[:purge] }
+
discard_on ActiveRecord::RecordNotFound
retry_on ActiveRecord::Deadlocked, attempts: 10, wait: :exponentially_longer
diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb
index 04f9dbff9f..6ca7d49bc1 100644
--- a/activestorage/app/models/active_storage/blob.rb
+++ b/activestorage/app/models/active_storage/blob.rb
@@ -79,6 +79,15 @@ class ActiveStorage::Blob < ActiveRecord::Base
def create_before_direct_upload!(filename:, byte_size:, checksum:, content_type: nil, metadata: nil)
create! filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, metadata: metadata
end
+
+ # To prevent problems with case-insensitive filesystems, especially in combination
+ # with databases which treat indices as case-sensitive, all blob keys generated are going
+ # to only contain the base-36 character alphabet and will therefore be lowercase. To maintain
+ # the same or higher amount of entropy as in the base-58 encoding used by `has_secure_token`
+ # the number of bytes used is increased to 28 from the standard 24
+ def generate_unique_secure_token
+ SecureRandom.base36(28)
+ end
end
# Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering.
@@ -87,9 +96,10 @@ class ActiveStorage::Blob < ActiveRecord::Base
ActiveStorage.verifier.generate(id, purpose: :blob_id)
end
- # Returns the key pointing to the file on the service that's associated with this blob. The key is in the
- # standard secure-token format from Rails. So it'll look like: XTAPjJCJiuDrLk3TmwyJGpUo. This key is not intended
- # to be revealed directly to the user. Always refer to blobs using the signed_id or a verified form of the key.
+ # Returns the key pointing to the file on the service that's associated with this blob. The key is the
+ # secure-token format from Rails in lower case. So it'll look like: xtapjjcjiudrlk3tmwyjgpuobabd.
+ # This key is not intended to be revealed directly to the user.
+ # Always refer to blobs using the signed_id or a verified form of the key.
def key
# We can't wait until the record is first saved to have a key for it
self[:key] ||= self.class.generate_unique_secure_token
diff --git a/activestorage/app/models/active_storage/blob/representable.rb b/activestorage/app/models/active_storage/blob/representable.rb
index 03d5511481..32e8fcefdf 100644
--- a/activestorage/app/models/active_storage/blob/representable.rb
+++ b/activestorage/app/models/active_storage/blob/representable.rb
@@ -10,7 +10,7 @@ module ActiveStorage::Blob::Representable
# Returns an ActiveStorage::Variant instance with the set of +transformations+ provided. This is only relevant for image
# files, and it allows any image to be transformed for size, colors, and the like. Example:
#
- # avatar.variant(resize_to_fit: [100, 100]).processed.service_url
+ # avatar.variant(resize_to_limit: [100, 100]).processed.service_url
#
# This will create and process a variant of the avatar blob that's constrained to a height and width of 100px.
# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
@@ -18,7 +18,7 @@ module ActiveStorage::Blob::Representable
# Frequently, though, you don't actually want to transform the variant right away. But rather simply refer to a
# specific variant that can be created by a controller on-demand. Like so:
#
- # <%= image_tag Current.user.avatar.variant(resize_to_fit: [100, 100]) %>
+ # <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %>
#
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
# can then produce on-demand.
@@ -43,13 +43,13 @@ module ActiveStorage::Blob::Representable
# from a non-image blob. Active Storage comes with built-in previewers for videos and PDF documents. The video previewer
# extracts the first frame from a video and the PDF previewer extracts the first page from a PDF document.
#
- # blob.preview(resize_to_fit: [100, 100]).processed.service_url
+ # blob.preview(resize_to_limit: [100, 100]).processed.service_url
#
# Avoid processing previews synchronously in views. Instead, link to a controller action that processes them on demand.
# Active Storage provides one, but you may want to create your own (for example, if you need authentication). Here’s
# how to use the built-in version:
#
- # <%= image_tag video.preview(resize_to_fit: [100, 100]) %>
+ # <%= image_tag video.preview(resize_to_limit: [100, 100]) %>
#
# This method raises ActiveStorage::UnpreviewableError if no previewer accepts the receiving blob. To determine
# whether a blob is accepted by any previewer, call ActiveStorage::Blob#previewable?.
@@ -69,7 +69,7 @@ module ActiveStorage::Blob::Representable
# Returns an ActiveStorage::Preview for a previewable blob or an ActiveStorage::Variant for a variable image blob.
#
- # blob.representation(resize_to_fit: [100, 100]).processed.service_url
+ # blob.representation(resize_to_limit: [100, 100]).processed.service_url
#
# Raises ActiveStorage::UnrepresentableError if the receiving blob is neither variable nor previewable. Call
# ActiveStorage::Blob#representable? to determine whether a blob is representable.
diff --git a/activestorage/app/models/active_storage/preview.rb b/activestorage/app/models/active_storage/preview.rb
index dd50494799..bb9d960443 100644
--- a/activestorage/app/models/active_storage/preview.rb
+++ b/activestorage/app/models/active_storage/preview.rb
@@ -38,7 +38,7 @@ class ActiveStorage::Preview
# Processes the preview if it has not been processed yet. Returns the receiving Preview instance for convenience:
#
- # blob.preview(resize_to_fit: [100, 100]).processed.service_url
+ # blob.preview(resize_to_limit: [100, 100]).processed.service_url
#
# Processing a preview generates an image from its blob and attaches the preview image to the blob. Because the preview
# image is stored with the blob, it is only generated once.
diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb
index ea57fa5f78..bc0058967a 100644
--- a/activestorage/app/models/active_storage/variant.rb
+++ b/activestorage/app/models/active_storage/variant.rb
@@ -27,7 +27,7 @@ require "ostruct"
# To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided
# by Active Storage like so:
#
-# <%= image_tag Current.user.avatar.variant(resize_to_fit: [100, 100]) %>
+# <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %>
#
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
# can then produce on-demand.
@@ -36,15 +36,15 @@ require "ostruct"
# has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform
# the transformations, upload the variant to the service, and return itself again. Example:
#
-# avatar.variant(resize_to_fit: [100, 100]).processed.service_url
+# avatar.variant(resize_to_limit: [100, 100]).processed.service_url
#
# This will create and process a variant of the avatar blob that's constrained to a height and width of 100.
# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
#
# You can combine any number of ImageMagick/libvips operations into a variant, as well as any macros provided by the
-# ImageProcessing gem (such as +resize_to_fit+):
+# ImageProcessing gem (such as +resize_to_limit+):
#
-# avatar.variant(resize_to_fit: [800, 800], monochrome: true, rotate: "-90")
+# avatar.variant(resize_to_limit: [800, 800], monochrome: true, rotate: "-90")
#
# Visit the following links for a list of available ImageProcessing commands and ImageMagick/libvips operations:
#
diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb
index 3adc2407e5..67568772da 100644
--- a/activestorage/app/models/active_storage/variation.rb
+++ b/activestorage/app/models/active_storage/variation.rb
@@ -6,7 +6,7 @@
# In case you do need to use this directly, it's instantiated using a hash of transformations where
# the key is the command and the value is the arguments. Example:
#
-# ActiveStorage::Variation.new(resize_to_fit: [100, 100], monochrome: true, trim: true, rotate: "-90")
+# ActiveStorage::Variation.new(resize_to_limit: [100, 100], monochrome: true, trim: true, rotate: "-90")
#
# The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands.
class ActiveStorage::Variation
diff --git a/activestorage/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb b/activestorage/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb
new file mode 100644
index 0000000000..6830203cd6
--- /dev/null
+++ b/activestorage/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb
@@ -0,0 +1,7 @@
+class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0]
+ def up
+ unless foreign_key_exists?(:active_storage_attachments, column: :blob_id)
+ add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage.rb b/activestorage/lib/active_storage.rb
index 19c7f6be2a..e542c4b2ca 100644
--- a/activestorage/lib/active_storage.rb
+++ b/activestorage/lib/active_storage.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2017-2018 David Heinemeier Hansson, Basecamp
+# Copyright (c) 2017-2019 David Heinemeier Hansson, Basecamp
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -42,7 +42,7 @@ module ActiveStorage
mattr_accessor :logger
mattr_accessor :verifier
- mattr_accessor :queue
+ mattr_accessor :queues, default: {}
mattr_accessor :previewers, default: []
mattr_accessor :analyzers, default: []
mattr_accessor :variant_processor, default: :mini_magick
diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb
index b3bcf48d6f..384e6ebfa6 100644
--- a/activestorage/lib/active_storage/engine.rb
+++ b/activestorage/lib/active_storage/engine.rb
@@ -20,6 +20,7 @@ module ActiveStorage
config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
config.active_storage.paths = ActiveSupport::OrderedOptions.new
+ config.active_storage.queues = ActiveSupport::OrderedOptions.new
config.active_storage.variable_content_types = %w(
image/png
@@ -27,6 +28,7 @@ module ActiveStorage
image/jpg
image/jpeg
image/pjpeg
+ image/tiff
image/vnd.adobe.photoshop
image/vnd.microsoft.icon
)
@@ -49,6 +51,7 @@ module ActiveStorage
image/gif
image/jpg
image/jpeg
+ image/tiff
image/vnd.adobe.photoshop
image/vnd.microsoft.icon
application/pdf
@@ -59,7 +62,6 @@ module ActiveStorage
initializer "active_storage.configs" do
config.after_initialize do |app|
ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
- ActiveStorage.queue = app.config.active_storage.queue
ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick
ActiveStorage.previewers = app.config.active_storage.previewers || []
ActiveStorage.analyzers = app.config.active_storage.analyzers || []
@@ -115,6 +117,20 @@ module ActiveStorage
end
end
+ initializer "active_storage.queues" do
+ config.after_initialize do |app|
+ if queue = app.config.active_storage.queue
+ ActiveSupport::Deprecation.warn \
+ "config.active_storage.queue is deprecated and will be removed in Rails 6.1. " \
+ "Set config.active_storage.queues.purge and config.active_storage.queues.analysis instead."
+
+ ActiveStorage.queues = { purge: queue, analysis: queue }
+ else
+ ActiveStorage.queues = app.config.active_storage.queues || {}
+ end
+ end
+ end
+
initializer "active_storage.reflection" do
ActiveSupport.on_load(:active_record) do
include Reflection::ActiveRecordExtensions
diff --git a/activestorage/lib/active_storage/gem_version.rb b/activestorage/lib/active_storage/gem_version.rb
index 492620731b..c4c7bf5677 100644
--- a/activestorage/lib/active_storage/gem_version.rb
+++ b/activestorage/lib/active_storage/gem_version.rb
@@ -10,7 +10,7 @@ module ActiveStorage
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activestorage/lib/tasks/activestorage.rake b/activestorage/lib/tasks/activestorage.rake
index ac254d717f..6b0469636c 100644
--- a/activestorage/lib/tasks/activestorage.rake
+++ b/activestorage/lib/tasks/activestorage.rake
@@ -12,4 +12,11 @@ namespace :active_storage do
Rake::Task["app:active_storage:install:migrations"].invoke
end
end
+
+ # desc "Copy over the migrations needed to the application upgrading"
+ task update: :environment do
+ ENV["MIGRATIONS_PATH"] = "db/update_migrate"
+
+ Rake::Task["active_storage:install"].invoke
+ end
end
diff --git a/activestorage/package.json b/activestorage/package.json
index 00876985cf..f7b896b5ee 100644
--- a/activestorage/package.json
+++ b/activestorage/package.json
@@ -1,6 +1,6 @@
{
- "name": "activestorage",
- "version": "6.0.0-alpha",
+ "name": "@rails/activestorage",
+ "version": "6.0.0-beta1",
"description": "Attach cloud and local files in Rails applications",
"main": "app/assets/javascripts/activestorage.js",
"files": [
diff --git a/activestorage/test/controllers/blobs_controller_test.rb b/activestorage/test/controllers/blobs_controller_test.rb
index 9c811df895..9bf2641de6 100644
--- a/activestorage/test/controllers/blobs_controller_test.rb
+++ b/activestorage/test/controllers/blobs_controller_test.rb
@@ -20,3 +20,28 @@ class ActiveStorage::BlobsControllerTest < ActionDispatch::IntegrationTest
assert_equal "max-age=300, private", @response.headers["Cache-Control"]
end
end
+
+if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].present?
+ class ActiveStorage::S3BlobsControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @old_service = ActiveStorage::Blob.service
+ ActiveStorage::Blob.service = ActiveStorage::Service.configure(:s3, SERVICE_CONFIGURATIONS)
+ end
+
+ teardown do
+ ActiveStorage::Blob.service = @old_service
+ end
+
+ test "allow redirection to the different host" do
+ blob = create_file_blob filename: "racecar.jpg"
+
+ assert_nothing_raised { get rails_blob_url(blob) }
+ assert_response :redirect
+ assert_no_match @request.host, @response.headers["Location"]
+ ensure
+ blob.purge
+ end
+ end
+else
+ puts "Skipping S3 redirection tests because no S3 configuration was supplied"
+end
diff --git a/activestorage/test/controllers/representations_controller_test.rb b/activestorage/test/controllers/representations_controller_test.rb
index 2662cc5283..4ae0ff877e 100644
--- a/activestorage/test/controllers/representations_controller_test.rb
+++ b/activestorage/test/controllers/representations_controller_test.rb
@@ -59,3 +59,33 @@ class ActiveStorage::RepresentationsControllerWithPreviewsTest < ActionDispatch:
assert_response :not_found
end
end
+
+if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].present?
+ class ActiveStorage::S3RepresentationsControllerWithVariantsTest < ActionDispatch::IntegrationTest
+ setup do
+ @old_service = ActiveStorage::Blob.service
+ ActiveStorage::Blob.service = ActiveStorage::Service.configure(:s3, SERVICE_CONFIGURATIONS)
+ end
+
+ teardown do
+ ActiveStorage::Blob.service = @old_service
+ end
+
+ test "allow redirection to the different host" do
+ blob = create_file_blob filename: "racecar.jpg"
+
+ assert_nothing_raised do
+ get rails_blob_representation_url(
+ filename: blob.filename,
+ signed_blob_id: blob.signed_id,
+ variation_key: ActiveStorage::Variation.encode(resize: "100x100"))
+ end
+ assert_response :redirect
+ assert_no_match @request.host, @response.headers["Location"]
+ ensure
+ blob.purge
+ end
+ end
+else
+ puts "Skipping S3 redirection tests because no S3 configuration was supplied"
+end
diff --git a/activestorage/test/dummy/config/application.rb b/activestorage/test/dummy/config/application.rb
index bd14ac0b1a..151c8ade4b 100644
--- a/activestorage/test/dummy/config/application.rb
+++ b/activestorage/test/dummy/config/application.rb
@@ -15,7 +15,7 @@ Bundler.require(*Rails.groups)
module Dummy
class Application < Rails::Application
- config.load_defaults 5.2
+ config.load_defaults 6.0
config.active_storage.service = :local
end
diff --git a/activestorage/test/dummy/config/environments/development.rb b/activestorage/test/dummy/config/environments/development.rb
index 47fc5bf25c..4b80d291ca 100644
--- a/activestorage/test/dummy/config/environments/development.rb
+++ b/activestorage/test/dummy/config/environments/development.rb
@@ -17,6 +17,7 @@ Rails.application.configure do
# Enable/disable caching. By default caching is disabled.
if Rails.root.join("tmp/caching-dev.txt").exist?
config.action_controller.perform_caching = true
+ config.action_controller.enable_fragment_cache_logging = true
config.cache_store = :memory_store
config.public_file_server.headers = {
diff --git a/activestorage/test/fixtures/files/racecar.tif b/activestorage/test/fixtures/files/racecar.tif
new file mode 100644
index 0000000000..0a11b22896
--- /dev/null
+++ b/activestorage/test/fixtures/files/racecar.tif
Binary files differ
diff --git a/activestorage/test/models/blob_test.rb b/activestorage/test/models/blob_test.rb
index 1503f5fc50..54cf9e2b8a 100644
--- a/activestorage/test/models/blob_test.rb
+++ b/activestorage/test/models/blob_test.rb
@@ -47,6 +47,10 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
assert_equal "text/csv", blob.content_type
end
+ test "create after upload generates a 28-character base36 key" do
+ assert_match(/^[a-z0-9]{28}$/, create_blob.key)
+ end
+
test "image?" do
blob = create_file_blob filename: "racecar.jpg"
assert_predicate blob, :image?
diff --git a/activestorage/test/models/variant_test.rb b/activestorage/test/models/variant_test.rb
index 4f88440e54..d98935eb9f 100644
--- a/activestorage/test/models/variant_test.rb
+++ b/activestorage/test/models/variant_test.rb
@@ -133,6 +133,17 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase
assert_equal 20, image.height
end
+ test "resized variation of TIFF blob" do
+ blob = create_file_blob(filename: "racecar.tif")
+ variant = blob.variant(resize: "50x50").processed
+ assert_match(/racecar\.png/, variant.service_url)
+
+ image = read_image(variant)
+ assert_equal "PNG", image.type
+ assert_equal 50, image.width
+ assert_equal 33, image.height
+ end
+
test "optimized variation of GIF blob" do
blob = create_file_blob(filename: "image.gif", content_type: "image/gif")
@@ -150,7 +161,7 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase
test "service_url doesn't grow in length despite long variant options" do
blob = create_file_blob(filename: "racecar.jpg")
variant = blob.variant(font: "a" * 10_000).processed
- assert_operator variant.service_url.length, :<, 726
+ assert_operator variant.service_url.length, :<, 730
end
test "works for vips processor" do
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index e678f48244..6272dd3471 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,23 @@
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Remove deprecated `Module#reachable?` method.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `#acronym_regex` method from `Inflections`.
+
+ *Rafael Mendonça França*
+
+* Fix `String#safe_constantize` throwing a `LoadError` for incorrectly cased constant references.
+
+ *Keenan Brock*
+
+* Preserve key order passed to `ActiveSupport::CacheStore#fetch_multi`.
+
+ `fetch_multi(*names)` now returns its results in the same order as the `*names` requested, rather than returning cache hits followed by cache misses.
+
+ *Gannon McGibbon*
+
* If the same block is `included` multiple times for a Concern, an exception is no longer raised.
*Mark J. Titorenko*, *Vlad Bokov*
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index 8f769c0767..315a4bc7c4 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2018 David Heinemeier Hansson
+Copyright (c) 2005-2019 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index a4fb697669..5589c71281 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2005-2018 David Heinemeier Hansson
+# Copyright (c) 2005-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index e8518645d9..30a69c550b 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -438,18 +438,18 @@ module ActiveSupport
options = merged_options(options)
instrument :read_multi, names, options do |payload|
- read_multi_entries(names, options).tap do |results|
- payload[:hits] = results.keys
- payload[:super_operation] = :fetch_multi
+ reads = read_multi_entries(names, options)
+ writes = {}
+ ordered = names.each_with_object({}) do |name, hash|
+ hash[name] = reads.fetch(name) { writes[name] = yield(name) }
+ end
- writes = {}
+ payload[:hits] = reads.keys
+ payload[:super_operation] = :fetch_multi
- (names - results.keys).each do |name|
- results[name] = writes[name] = yield(name)
- end
+ write_multi(writes, options)
- write_multi writes, options
- end
+ ordered
end
end
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 1cd7acb05d..d03a8d3997 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -110,12 +110,13 @@ class Date
# Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
# any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
def advance(options)
- options = options.dup
d = self
- d = d >> options.delete(:years) * 12 if options[:years]
- d = d >> options.delete(:months) if options[:months]
- d = d + options.delete(:weeks) * 7 if options[:weeks]
- d = d + options.delete(:days) if options[:days]
+
+ d = d >> options[:years] * 12 if options[:years]
+ d = d >> options[:months] if options[:months]
+ d = d + options[:weeks] * 7 if options[:weeks]
+ d = d + options[:days] if options[:days]
+
d
end
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index 05abd83221..e2e11545e2 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -134,7 +134,7 @@ module DateAndTime
# now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
# now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000
def beginning_of_quarter
- first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
+ first_quarter_month = month - (2 + month) % 3
beginning_of_month.change(month: first_quarter_month)
end
alias :at_beginning_of_quarter :beginning_of_quarter
@@ -149,7 +149,7 @@ module DateAndTime
# now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
# now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000
def end_of_quarter
- last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
+ last_quarter_month = month + (12 - month) % 3
beginning_of_month.change(month: last_quarter_month).end_of_month
end
alias :at_end_of_quarter :end_of_quarter
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index d91e3fba6a..542af98c04 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -3,7 +3,6 @@
require "active_support/core_ext/module/aliasing"
require "active_support/core_ext/module/introspection"
require "active_support/core_ext/module/anonymous"
-require "active_support/core_ext/module/reachable"
require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/module/attribute_accessors_per_thread"
require "active_support/core_ext/module/attr_internal"
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index be90390ae4..5652f2d1cc 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -243,7 +243,7 @@ class Module
# end
#
# def person
- # @event.detail.person || @event.creator
+ # detail.person || creator
# end
#
# private
@@ -266,7 +266,7 @@ class Module
# end
#
# def person
- # @event.detail.person || @event.creator
+ # detail.person || creator
# end
# end
#
diff --git a/activesupport/lib/active_support/core_ext/module/reachable.rb b/activesupport/lib/active_support/core_ext/module/reachable.rb
index e9cbda5245..2020f5204c 100644
--- a/activesupport/lib/active_support/core_ext/module/reachable.rb
+++ b/activesupport/lib/active_support/core_ext/module/reachable.rb
@@ -3,9 +3,4 @@
require "active_support/core_ext/module/anonymous"
require "active_support/core_ext/string/inflections"
-class Module
- def reachable? #:nodoc:
- !anonymous? && name.safe_constantize.equal?(self)
- end
- deprecate :reachable?
-end
+ActiveSupport::Deprecation.warn("reachable is deprecated and will be removed from the framework.")
diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
index b4a491f5fd..ef812f7e1a 100644
--- a/activesupport/lib/active_support/core_ext/securerandom.rb
+++ b/activesupport/lib/active_support/core_ext/securerandom.rb
@@ -4,17 +4,18 @@ require "securerandom"
module SecureRandom
BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"]
+ BASE36_ALPHABET = ("0".."9").to_a + ("a".."z").to_a
+
# SecureRandom.base58 generates a random base58 string.
#
- # The argument _n_ specifies the length, of the random string to be generated.
+ # The argument _n_ specifies the length of the random string to be generated.
#
# If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
#
- # The result may contain alphanumeric characters except 0, O, I and l
+ # The result may contain alphanumeric characters except 0, O, I and l.
#
# p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
# p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
- #
def self.base58(n = 16)
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
idx = byte % 64
@@ -22,4 +23,23 @@ module SecureRandom
BASE58_ALPHABET[idx]
end.join
end
+
+ # SecureRandom.base36 generates a random base36 string in lowercase.
+ #
+ # The argument _n_ specifies the length of the random string to be generated.
+ #
+ # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
+ # This method can be used over +base58+ if a deterministic case key is necessary.
+ #
+ # The result will contain alphanumeric characters in lowercase.
+ #
+ # p SecureRandom.base36 # => "4kugl2pdqmscqtje"
+ # p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
+ def self.base36(n = 16)
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
+ idx = byte % 64
+ idx = SecureRandom.random_number(36) if idx >= 36
+ BASE36_ALPHABET[idx]
+ end.join
+ end
end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 314c926ac0..97b4634d7b 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -214,8 +214,11 @@ module ActiveSupport
end
def coerce(other) #:nodoc:
- if Scalar === other
+ case other
+ when Scalar
[other, self]
+ when Duration
+ [Scalar.new(other.value), self]
else
[Scalar.new(other), self]
end
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index c951ad16a3..8bf016e88c 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -10,7 +10,7 @@ module ActiveSupport
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index fa087c4dd6..88cdd99dbd 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -65,8 +65,7 @@ module ActiveSupport
@__instance__[locale] ||= new
end
- attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
- deprecate :acronym_regex
+ attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms
attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc:
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 1af9833d46..ee193add6f 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -328,6 +328,8 @@ module ActiveSupport
e.name.to_s == camel_cased_word.to_s)
rescue ArgumentError => e
raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match?(e.message)
+ rescue LoadError => e
+ raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(e.message)
end
# Returns the suffix that should be added to a number to denote the position
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 00edcdd05a..8b61982883 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -38,36 +38,4 @@ module ActiveSupport
@cache_keys[args.join] ||= @key_generator.generate_key(*args)
end
end
-
- class LegacyKeyGenerator # :nodoc:
- SECRET_MIN_LENGTH = 30 # Characters
-
- def initialize(secret)
- ensure_secret_secure(secret)
- @secret = secret
- end
-
- def generate_key(salt)
- @secret
- end
-
- private
-
- # To prevent users from using something insecure like "Password" we make sure that the
- # secret they've provided is at least 30 characters in length.
- def ensure_secret_secure(secret)
- if secret.blank?
- raise ArgumentError, "A secret is required to generate an integrity hash " \
- "for cookie session data. Set a secret_key_base of at least " \
- "#{SECRET_MIN_LENGTH} characters by running `rails credentials:edit`."
- end
-
- if secret.length < SECRET_MIN_LENGTH
- raise ArgumentError, "Secret should be something secure, " \
- "like \"#{SecureRandom.hex(16)}\". The value you " \
- "provided, \"#{secret}\", is shorter than the minimum length " \
- "of #{SECRET_MIN_LENGTH} characters."
- end
- end
- end
end
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index 0f7be06c8e..938cfdb914 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -5,8 +5,8 @@ require "active_support/core_ext/class/attribute"
require "active_support/subscriber"
module ActiveSupport
- # ActiveSupport::LogSubscriber is an object set to consume
- # ActiveSupport::Notifications with the sole purpose of logging them.
+ # <tt>ActiveSupport::LogSubscriber</tt> is an object set to consume
+ # <tt>ActiveSupport::Notifications</tt> with the sole purpose of logging them.
# The log subscriber dispatches notifications to a registered object based
# on its given namespace.
#
@@ -16,7 +16,7 @@ module ActiveSupport
# module ActiveRecord
# class LogSubscriber < ActiveSupport::LogSubscriber
# def sql(event)
- # "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
+ # info "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
# end
# end
# end
@@ -29,13 +29,36 @@ module ActiveSupport
# subscriber, the line above should be called after your
# <tt>ActiveRecord::LogSubscriber</tt> definition.
#
- # After configured, whenever a "sql.active_record" notification is published,
- # it will properly dispatch the event (ActiveSupport::Notifications::Event) to
- # the sql method.
+ # After configured, whenever a <tt>"sql.active_record"</tt> notification is published,
+ # it will properly dispatch the event
+ # (<tt>ActiveSupport::Notifications::Event</tt>) to the sql method.
+ #
+ # Being an <tt>ActiveSupport::Notifications</tt> consumer,
+ # <tt>ActiveSupport::LogSubscriber</tt> exposes a simple interface to check if
+ # instrumented code raises an exception. It is common to log a different
+ # message in case of an error, and this can be achieved by extending
+ # the previous example:
+ #
+ # module ActiveRecord
+ # class LogSubscriber < ActiveSupport::LogSubscriber
+ # def sql(event)
+ # exception = event.payload[:exception]
+ #
+ # if exception
+ # exception_object = event.payload[:exception_object]
+ #
+ # error "[ERROR] #{event.payload[:name]}: #{exception.join(', ')} " \
+ # "(#{exception_object.backtrace.first})"
+ # else
+ # # standard logger code
+ # end
+ # end
+ # end
+ # end
#
# Log subscriber also has some helpers to deal with logging and automatically
- # flushes all logs when the request finishes (via action_dispatch.callback
- # notification) in a Rails environment.
+ # flushes all logs when the request finishes
+ # (via <tt>action_dispatch.callback</tt> notification) in a Rails environment.
class LogSubscriber < Subscriber
# Embed in a String to clear all previous ANSI sequences.
CLEAR = "\e[0m"
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 0ff32bd810..7ccc333463 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -67,9 +67,12 @@ module ActiveSupport
# have a key <tt>:exception</tt> with an array of two elements as value: a string with
# the name of the exception class, and the exception message.
# The <tt>:exception_object</tt> key of the payload will have the exception
- # itself as the value.
+ # itself as the value:
#
- # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
+ # event.payload[:exception] # => ["ArgumentError", "Invalid value"]
+ # event.payload[:exception_object] # => #<ArgumentError: Invalid value>
+ #
+ # As the earlier example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
# is able to take the arguments as they come and provide an object-oriented
# interface to that data.
#
diff --git a/activesupport/test/autoloading_fixtures/raises_load_error.rb b/activesupport/test/autoloading_fixtures/raises_load_error.rb
new file mode 100644
index 0000000000..f97be29b71
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/raises_load_error.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+# raises a load error typical of the dynamic code that manually raises load errors
+raise LoadError, "required gem not present kind of error"
diff --git a/activesupport/test/cache/behaviors/cache_store_behavior.rb b/activesupport/test/cache/behaviors/cache_store_behavior.rb
index 9f54b1e7de..a696760bb2 100644
--- a/activesupport/test/cache/behaviors/cache_store_behavior.rb
+++ b/activesupport/test/cache/behaviors/cache_store_behavior.rb
@@ -130,7 +130,7 @@ module CacheStoreBehavior
assert_equal("fufu", @cache.read("fu"))
end
- def test_multi_with_objects
+ def test_fetch_multi_with_objects
cache_struct = Struct.new(:cache_key, :title)
foo = cache_struct.new("foo", "FOO!")
bar = cache_struct.new("bar")
@@ -142,6 +142,14 @@ module CacheStoreBehavior
assert_equal({ foo => "FOO!", bar => "BAM!" }, values)
end
+ def test_fetch_multi_returns_ordered_names
+ @cache.write("bam", "BAM")
+
+ values = @cache.fetch_multi("foo", "bar", "bam") { |key| key.upcase }
+
+ assert_equal(%w(foo bar bam), values.keys)
+ end
+
def test_fetch_multi_without_block
assert_raises(ArgumentError) do
@cache.fetch_multi("foo")
diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb
index 2c6145940b..cdb8441b81 100644
--- a/activesupport/test/constantize_test_cases.rb
+++ b/activesupport/test/constantize_test_cases.rb
@@ -112,6 +112,16 @@ module ConstantizeTestCases
assert_nil yield("A::Object::B")
assert_nil yield("A::Object::Object::Object::B")
+ with_autoloading_fixtures do
+ assert_nil yield("Em")
+ end
+
+ assert_raises(LoadError) do
+ with_autoloading_fixtures do
+ yield("RaisesLoadError")
+ end
+ end
+
assert_raises(NameError) do
with_autoloading_fixtures do
yield("RaisesNameError")
diff --git a/activesupport/test/core_ext/module/reachable_test.rb b/activesupport/test/core_ext/module/reachable_test.rb
deleted file mode 100644
index f356d46957..0000000000
--- a/activesupport/test/core_ext/module/reachable_test.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-require "abstract_unit"
-require "active_support/core_ext/module/reachable"
-
-class AnonymousTest < ActiveSupport::TestCase
- test "an anonymous class or module is not reachable" do
- assert_deprecated do
- assert_not_predicate Module.new, :reachable?
- assert_not_predicate Class.new, :reachable?
- end
- end
-
- test "ordinary named classes or modules are reachable" do
- assert_deprecated do
- assert_predicate Kernel, :reachable?
- assert_predicate Object, :reachable?
- end
- end
-
- test "a named class or module whose constant has gone is not reachable" do
- c = eval "class C; end; C"
- m = eval "module M; end; M"
-
- self.class.send(:remove_const, :C)
- self.class.send(:remove_const, :M)
-
- assert_deprecated do
- assert_not_predicate c, :reachable?
- assert_not_predicate m, :reachable?
- end
- end
-
- test "a named class or module whose constants store different objects are not reachable" do
- c = eval "class C; end; C"
- m = eval "module M; end; M"
-
- self.class.send(:remove_const, :C)
- self.class.send(:remove_const, :M)
-
- eval "class C; end"
- eval "module M; end"
-
- assert_deprecated do
- assert_predicate C, :reachable?
- assert_predicate M, :reachable?
- assert_not_predicate c, :reachable?
- assert_not_predicate m, :reachable?
- end
- end
-end
diff --git a/activesupport/test/core_ext/secure_random_test.rb b/activesupport/test/core_ext/secure_random_test.rb
index 7067fb524c..4b73233971 100644
--- a/activesupport/test/core_ext/secure_random_test.rb
+++ b/activesupport/test/core_ext/secure_random_test.rb
@@ -19,4 +19,24 @@ class SecureRandomTest < ActiveSupport::TestCase
assert_not_equal s1, s2
assert_equal 24, s1.length
end
+
+ def test_base36
+ s1 = SecureRandom.base36
+ s2 = SecureRandom.base36
+
+ assert_not_equal s1, s2
+ assert_equal 16, s1.length
+ assert_match(/^[a-z0-9]+$/, s1)
+ assert_match(/^[a-z0-9]+$/, s2)
+ end
+
+ def test_base36_with_length
+ s1 = SecureRandom.base36(24)
+ s2 = SecureRandom.base36(24)
+
+ assert_not_equal s1, s2
+ assert_equal 24, s1.length
+ assert_match(/^[a-z0-9]+$/, s1)
+ assert_match(/^[a-z0-9]+$/, s2)
+ end
end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 5e50acf5db..c3e1faff5d 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -224,12 +224,6 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal("json_html_api", ActiveSupport::Inflector.underscore("JSONHTMLAPI"))
end
- def test_acronym_regexp_is_deprecated
- assert_deprecated do
- ActiveSupport::Inflector.inflections.acronym_regex
- end
- end
-
def test_underscore
CamelToUnderscore.each do |camel, underscore|
assert_equal(underscore, ActiveSupport::Inflector.underscore(camel))
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index 8698c66e6d..56cd2665e0 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -104,7 +104,7 @@ class AssertionsTest < ActiveSupport::TestCase
def test_expression_is_evaluated_in_the_appropriate_scope
silence_warnings do
local_scope = "foo"
- local_scope = local_scope # to suppress unused variable warning
+ _ = local_scope # to suppress unused variable warning
assert_difference("local_scope; @object.num") { @object.increment }
end
end
diff --git a/ci/travis.rb b/ci/travis.rb
index 1de281046f..038db476e9 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -20,20 +20,6 @@ commands.each do |command|
end
class Build
- MAP = {
- "railties" => "railties",
- "ap" => "actionpack",
- "am" => "actionmailer",
- "amo" => "activemodel",
- "as" => "activesupport",
- "ar" => "activerecord",
- "av" => "actionview",
- "aj" => "activejob",
- "ac" => "actioncable",
- "ast" => "activestorage",
- "guides" => "guides"
- }
-
attr_reader :component, :options
def initialize(component, options = {})
@@ -114,7 +100,7 @@ class Build
end
def gem
- MAP[component.split(":").first]
+ component.split(":").first
end
alias :dir :gem
@@ -149,7 +135,7 @@ class Build
end
end
-if ENV["GEM"] == "aj:integration"
+if ENV["GEM"] == "activejob:integration"
ENV["QC_DATABASE_URL"] = "postgres://postgres@localhost/active_jobs_qc_int_test"
ENV["QUE_DATABASE_URL"] = "postgres://postgres@localhost/active_jobs_que_int_test"
end
@@ -159,14 +145,16 @@ results = {}
ENV["GEM"].split(",").each do |gem|
[false, true].each do |isolated|
next if ENV["TRAVIS_PULL_REQUEST"] && ENV["TRAVIS_PULL_REQUEST"] != "false" && isolated
- next if RUBY_VERSION < "2.5" && isolated
+ next if RUBY_VERSION < "2.6" && isolated
next if gem == "railties" && isolated
- next if gem == "ac" && isolated
- next if gem == "ac:integration" && isolated
- next if gem == "aj:integration" && isolated
+ next if gem == "actioncable" && isolated
+ next if gem == "actioncable:integration" && isolated
+ next if gem == "activejob:integration" && isolated
next if gem == "guides" && isolated
- next if gem == "av:ujs" && isolated
- next if gem == "ast" && isolated
+ next if gem == "actionview:ujs" && isolated
+ next if gem == "activestorage" && isolated
+ next if gem == "actionmailbox" && isolated
+ next if gem == "actiontext" && isolated
build = Build.new(gem, isolated: isolated)
results[build.key] = build.run!
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index 9f95e22245..6432dbfd00 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,3 +1,13 @@
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Add "Action Text Overview" Guide.
+
+ *DHH*
+
+* Add "Action Mailbox Basics" Guide.
+
+ *DHH*
+
* New section _Troubleshooting_ in the _Autoloading and Reloading Constants_ guide.
*Xavier Noria*
diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb
index 48e90510e1..fd33c3f8a7 100644
--- a/guides/rails_guides/generator.rb
+++ b/guides/rails_guides/generator.rb
@@ -63,9 +63,9 @@ module RailsGuides
end
def mobi
- mobi = "ruby_on_rails_guides_#{@version || @edge[0, 7]}"
- mobi += ".#{@language}" if @language
- mobi += ".mobi"
+ mobi = +"ruby_on_rails_guides_#{@version || @edge[0, 7]}"
+ mobi << ".#{@language}" if @language
+ mobi << ".mobi"
end
def initialize_dirs
diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb
index a98aa8fe66..018f49ffd0 100644
--- a/guides/rails_guides/markdown.rb
+++ b/guides/rails_guides/markdown.rb
@@ -3,6 +3,7 @@
require "redcarpet"
require "nokogiri"
require "rails_guides/markdown/renderer"
+require "rails-html-sanitizer"
module RailsGuides
class Markdown
@@ -20,6 +21,7 @@ module RailsGuides
@raw_body = body
extract_raw_header_and_body
generate_header
+ generate_description
generate_title
generate_body
generate_structure
@@ -82,6 +84,11 @@ module RailsGuides
@header = engine.render(@raw_header).html_safe
end
+ def generate_description
+ sanitizer = Rails::Html::FullSanitizer.new
+ @description = sanitizer.sanitize(@header).squish
+ end
+
def generate_structure
@headings_for_index = []
if @body.present?
@@ -165,6 +172,7 @@ module RailsGuides
def render_page
@view.content_for(:header_section) { @header }
+ @view.content_for(:description) { @description }
@view.content_for(:page_title) { @title }
@view.content_for(:index_section) { @index }
@view.render(layout: @layout, html: @body.html_safe)
diff --git a/guides/source/6_0_release_notes.md b/guides/source/6_0_release_notes.md
index f3ed21dc45..e6fb15c09c 100644
--- a/guides/source/6_0_release_notes.md
+++ b/guides/source/6_0_release_notes.md
@@ -5,7 +5,10 @@ Ruby on Rails 6.0 Release Notes
Highlights in Rails 6.0:
+* Action Mailbox
+* Action Text
* Parallel Testing
+* Action Cable Testing
These release notes cover only the major changes. To learn about various bug
fixes and changes, please refer to the change logs or check out the [list of
@@ -28,6 +31,29 @@ guide.
Major Features
--------------
+### Action Mailbox
+
+[Pull Request](https://github.com/rails/rails/pull/34786)
+
+[Action Mailbox](https://github.com/rails/rails/tree/6-0-stable/actionmailbox) allows you
+to route incoming emails to controller-like mailboxes.
+You can read more about Action Mailbox in the [Action Mailbox Basics](action_mailbox_basics.html) guide.
+
+### Action Text
+
+[Pull Request](https://github.com/rails/rails/pull/34873)
+
+[Action Text](https://github.com/rails/rails/tree/6-0-stable/actiontext)
+brings rich text content and editing to Rails. It includes
+the [Trix editor](https://trix-editor.org) that handles everything from formatting
+to links to quotes to lists to embedded images and galleries.
+The rich text content generated by the Trix editor is saved in its own
+RichText model that's associated with any existing Active Record model in the application.
+Any embedded images (or other attachments) are automatically stored using
+Active Storage and associated with the included RichText model.
+
+You can read more about Action Text in the [Action Text Overview](action_text_overview.html) guide.
+
### Parallel Testing
[Pull Request](https://github.com/rails/rails/pull/31900)
@@ -37,6 +63,13 @@ test suite. While forking processes is the default method, threading is
supported as well. Running tests in parallel reduces the time it takes
your entire test suite to run.
+### Action Cable Testing
+
+[Pull Request](https://github.com/rails/rails/pull/33659)
+
+[Action Cable testing tools](testing.html#testing-action-cable) allow you to test your
+Action Cable functionality at any level: connections, channels, broadcasts.
+
Railties
--------
@@ -59,6 +92,22 @@ Please refer to the [Changelog][action-cable] for detailed changes.
### Notable changes
+* The ActionCable javascript package has been converted from CoffeeScript
+ to ES2015, and we now publish the source code in the npm distribution.
+
+ This allows ActionCable users to depend on the javascript source code
+ rather than the compiled code, which can produce smaller javascript bundles.
+
+ This change includes some breaking changes to optional parts of the
+ ActionCable javascript API:
+
+ - Configuration of the WebSocket adapter and logger adapter have been moved
+ from properties of `ActionCable` to properties of `ActionCable.adapters`.
+
+ - The `ActionCable.startDebugging()` and `ActionCable.stopDebugging()`
+ methods have been removed and replaced with the property
+ `ActionCable.logger.enabled`.
+
Action Pack
-----------
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
index 2f602c3e0a..8f5c44849a 100644
--- a/guides/source/action_cable_overview.md
+++ b/guides/source/action_cable_overview.md
@@ -27,6 +27,36 @@ client-side JavaScript framework and a server-side Ruby framework. You have
access to your full domain model written with Active Record or your ORM of
choice.
+Terminology
+-----------
+
+A single Action Cable server can handle multiple connection instances. It has one
+connection instance per WebSocket connection. A single user may have multiple
+WebSockets open to your application if they use multiple browser tabs or devices.
+The client of a WebSocket connection is called the consumer.
+
+Each consumer can in turn subscribe to multiple cable channels. Each channel
+encapsulates a logical unit of work, similar to what a controller does in
+a regular MVC setup. For example, you could have a `ChatChannel` and
+an `AppearancesChannel`, and a consumer could be subscribed to either
+or to both of these channels. At the very least, a consumer should be subscribed
+to one channel.
+
+When the consumer is subscribed to a channel, they act as a subscriber.
+The connection between the subscriber and the channel is, surprise-surprise,
+called a subscription. A consumer can act as a subscriber to a given channel
+any number of times. For example, a consumer could subscribe to multiple chat rooms
+at the same time. (And remember that a physical user may have multiple consumers,
+one per tab/device open to your connection).
+
+Each channel can then again be streaming zero or more broadcastings.
+A broadcasting is a pubsub link where anything transmitted by the broadcaster is
+sent directly to the channel subscribers who are streaming that named broadcasting.
+
+As you can see, this is a fairly deep architectural stack. There's a lot of new
+terminology to identify the new pieces, and on top of that, you're dealing
+with both client and server side reflections of each unit.
+
What is Pub/Sub
---------------
@@ -147,16 +177,13 @@ established using the following JavaScript, which is generated by default by Rai
#### Connect Consumer
```js
-// app/assets/javascripts/cable.js
-//= require action_cable
-//= require_self
-//= require_tree ./channels
+// app/javascript/channels/consumer.js
+// Action Cable provides the framework to deal with WebSockets in Rails.
+// You can generate new channels where WebSocket features live using the `rails generate channel` command.
-(function() {
- this.App || (this.App = {});
+import { createConsumer } from "@rails/actioncable"
- App.cable = ActionCable.createConsumer();
-}).call(this);
+export default createConsumer()
```
This will ready a consumer that'll connect against `/cable` on your server by default.
@@ -167,12 +194,16 @@ you're interested in having.
A consumer becomes a subscriber by creating a subscription to a given channel:
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/chat.coffee
-App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }
+```js
+// app/javascript/channels/chat_channel.js
+import consumer from "./consumer"
+
+consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" })
-# app/assets/javascripts/cable/subscriptions/appearance.coffee
-App.cable.subscriptions.create { channel: "AppearanceChannel" }
+// app/javascript/channels/appearance_channel.js
+import consumer from "./consumer"
+
+consumer.subscriptions.create({ channel: "AppearanceChannel" })
```
While this creates the subscription, the functionality needed to respond to
@@ -181,9 +212,12 @@ received data will be described later on.
A consumer can act as a subscriber to a given channel any number of times. For
example, a consumer could subscribe to multiple chat rooms at the same time:
-```coffeescript
-App.cable.subscriptions.create { channel: "ChatChannel", room: "1st Room" }
-App.cable.subscriptions.create { channel: "ChatChannel", room: "2nd Room" }
+```js
+// app/javascript/channels/chat_channel.js
+import consumer from "./consumer"
+
+consumer.subscriptions.create({ channel: "ChatChannel", room: "1st Room" })
+consumer.subscriptions.create({ channel: "ChatChannel", room: "2nd Room" })
```
## Client-Server Interactions
@@ -256,24 +290,31 @@ When a consumer is subscribed to a channel, they act as a subscriber. This
connection is called a subscription. Incoming messages are then routed to
these channel subscriptions based on an identifier sent by the cable consumer.
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/chat.coffee
-# Assumes you've already requested the right to send web notifications
-App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
- received: (data) ->
- @appendLine(data)
-
- appendLine: (data) ->
- html = @createLine(data)
- $("[data-chat-room='Best Room']").append(html)
-
- createLine: (data) ->
- """
- <article class="chat-line">
- <span class="speaker">#{data["sent_by"]}</span>
- <span class="body">#{data["body"]}</span>
- </article>
- """
+```js
+// app/javascript/channels/chat_channel.js
+// Assumes you've already requested the right to send web notifications
+import consumer from "./consumer"
+
+consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
+ received(data) {
+ this.appendLine(data)
+ },
+
+ appendLine(data) {
+ const html = this.createLine(data)
+ const element = document.querySelector("[data-chat-room='Best Room']")
+ element.insertAdjacentHTML("beforeend", html)
+ },
+
+ createLine(data) {
+ return `
+ <article class="chat-line">
+ <span class="speaker">${data["sent_by"]}</span>
+ <span class="body">${data["body"]}</span>
+ </article>
+ `
+ }
+})
```
### Passing Parameters to Channels
@@ -293,23 +334,30 @@ end
An object passed as the first argument to `subscriptions.create` becomes the
params hash in the cable channel. The keyword `channel` is required:
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/chat.coffee
-App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
- received: (data) ->
- @appendLine(data)
-
- appendLine: (data) ->
- html = @createLine(data)
- $("[data-chat-room='Best Room']").append(html)
-
- createLine: (data) ->
- """
- <article class="chat-line">
- <span class="speaker">#{data["sent_by"]}</span>
- <span class="body">#{data["body"]}</span>
- </article>
- """
+```js
+// app/javascript/channels/chat_channel.js
+import consumer from "./consumer"
+
+consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
+ received(data) {
+ this.appendLine(data)
+ },
+
+ appendLine(data) {
+ const html = this.createLine(data)
+ const element = document.querySelector("[data-chat-room='Best Room']")
+ element.insertAdjacentHTML("beforeend", html)
+ },
+
+ createLine(data) {
+ return `
+ <article class="chat-line">
+ <span class="speaker">${data["sent_by"]}</span>
+ <span class="body">${data["body"]}</span>
+ </article>
+ `
+ }
+})
```
```ruby
@@ -340,13 +388,17 @@ class ChatChannel < ApplicationCable::Channel
end
```
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/chat.coffee
-App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
- received: (data) ->
- # data => { sent_by: "Paul", body: "This is a cool chat app." }
+```js
+// app/javascript/channels/chat_channel.js
+import consumer from "./consumer"
+
+const chatChannel = consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
+ received(data) {
+ // data => { sent_by: "Paul", body: "This is a cool chat app." }
+ }
+}
-App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
+chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
```
The rebroadcast will be received by all connected clients, _including_ the
@@ -396,46 +448,69 @@ appear/disappear API could be backed by Redis, a database, or whatever else.
Create the client-side appearance channel subscription:
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/appearance.coffee
-App.cable.subscriptions.create "AppearanceChannel",
- # Called when the subscription is ready for use on the server.
- connected: ->
- @install()
- @appear()
-
- # Called when the WebSocket connection is closed.
- disconnected: ->
- @uninstall()
-
- # Called when the subscription is rejected by the server.
- rejected: ->
- @uninstall()
-
- appear: ->
- # Calls `AppearanceChannel#appear(data)` on the server.
- @perform("appear", appearing_on: $("main").data("appearing-on"))
-
- away: ->
- # Calls `AppearanceChannel#away` on the server.
- @perform("away")
-
-
- buttonSelector = "[data-behavior~=appear_away]"
-
- install: ->
- $(document).on "turbolinks:load.appearance", =>
- @appear()
-
- $(document).on "click.appearance", buttonSelector, =>
- @away()
- false
-
- $(buttonSelector).show()
-
- uninstall: ->
- $(document).off(".appearance")
- $(buttonSelector).hide()
+```js
+// app/javascript/channels/appearance_channel.js
+import consumer from "./consumer"
+
+consumer.subscriptions.create("AppearanceChannel", {
+ // Called once when the subscription is created.
+ initialized() {
+ this.update = this.update.bind(this)
+ },
+
+ // Called when the subscription is ready for use on the server.
+ connected() {
+ this.install()
+ this.update()
+ },
+
+ // Called when the WebSocket connection is closed.
+ disconnected() {
+ this.uninstall()
+ },
+
+ // Called when the subscription is rejected by the server.
+ rejected() {
+ this.uninstall()
+ },
+
+ update() {
+ this.documentIsActive ? this.appear() : this.away()
+ },
+
+ appear() {
+ // Calls `AppearanceChannel#appear(data)` on the server.
+ this.perform("appear", { appearing_on: this.appearingOn })
+ },
+
+ away() {
+ // Calls `AppearanceChannel#away` on the server.
+ this.perform("away")
+ },
+
+ install() {
+ window.addEventListener("focus", this.update)
+ window.addEventListener("blur", this.update)
+ document.addEventListener("turbolinks:load", this.update)
+ document.addEventListener("visibilitychange", this.update)
+ },
+
+ uninstall() {
+ window.removeEventListener("focus", this.update)
+ window.removeEventListener("blur", this.update)
+ document.removeEventListener("turbolinks:load", this.update)
+ document.removeEventListener("visibilitychange", this.update)
+ },
+
+ get documentIsActive() {
+ return document.visibilityState == "visible" && document.hasFocus()
+ },
+
+ get appearingOn() {
+ const element = document.querySelector("[data-appearing-on]")
+ return element ? element.getAttribute("data-appearing-on") : null
+ }
+})
```
##### Client-Server Interaction
@@ -445,16 +520,16 @@ ActionCable.createConsumer("ws://cable.example.com")`. (`cable.js`). The
**Server** identifies this connection by `current_user`.
2. **Client** subscribes to the appearance channel via
-`App.cable.subscriptions.create(channel: "AppearanceChannel")`. (`appearance.coffee`)
+`consumer.subscriptions.create({ channel: "AppearanceChannel" })`. (`appearance_channel.js`)
3. **Server** recognizes a new subscription has been initiated for the
appearance channel and runs its `subscribed` callback, calling the `appear`
method on `current_user`. (`appearance_channel.rb`)
4. **Client** recognizes that a subscription has been established and calls
-`connected` (`appearance.coffee`) which in turn calls `@install` and `@appear`.
-`@appear` calls `AppearanceChannel#appear(data)` on the server, and supplies a
-data hash of `{ appearing_on: $("main").data("appearing-on") }`. This is
+`connected` (`appearance_channel.js`) which in turn calls `install` and `appear`.
+`appear` calls `AppearanceChannel#appear(data)` on the server, and supplies a
+data hash of `{ appearing_on: this.appearingOn }`. This is
possible because the server-side channel instance automatically exposes all
public methods declared on the class (minus the callbacks), so that these can be
reached as remote procedure calls via a subscription's `perform` method.
@@ -488,13 +563,17 @@ end
Create the client-side web notifications channel subscription:
-```coffeescript
-# app/assets/javascripts/cable/subscriptions/web_notifications.coffee
-# Client-side which assumes you've already requested
-# the right to send web notifications.
-App.cable.subscriptions.create "WebNotificationsChannel",
- received: (data) ->
- new Notification data["title"], body: data["body"]
+```js
+// app/javascript/channels/web_notifications_channel.js
+// Client-side which assumes you've already requested
+// the right to send web notifications.
+import consumer from "./consumer"
+
+consumer.subscriptions.create("WebNotificationsChannel", {
+ received(data) {
+ new Notification(data["title"], body: data["body"])
+ }
+})
```
Broadcast content to a web notification channel instance from elsewhere in your
@@ -629,10 +708,9 @@ class Application < Rails::Application
end
```
-You can use `App.cable = ActionCable.createConsumer()` to connect to the cable
-server if `action_cable_meta_tag` is invoked in the layout. A custom path is
-specified as first argument to `createConsumer` (e.g. `App.cable =
-ActionCable.createConsumer("/websocket")`).
+You can use `ActionCable.createConsumer()` to connect to the cable
+server if `action_cable_meta_tag` is invoked in the layout. Otherwise, A path is
+specified as first argument to `createConsumer` (e.g. `ActionCable.createConsumer("/websocket")`).
For every instance of your server you create and for every worker your server
spawns, you will also have a new instance of Action Cable, but the use of Redis
@@ -690,3 +768,8 @@ internally, irrespective of whether the application server is multi-threaded or
Accordingly, Action Cable works with popular servers like Unicorn, Puma, and
Passenger.
+
+## Testing
+
+You can find detailed instructions on how to test your Action Cable functionality in the
+[testing guide](testing.html#testing-action-cable).
diff --git a/guides/source/action_mailbox_basics.md b/guides/source/action_mailbox_basics.md
new file mode 100644
index 0000000000..c90892d456
--- /dev/null
+++ b/guides/source/action_mailbox_basics.md
@@ -0,0 +1,398 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
+
+Action Mailbox Basics
+=====================
+
+This guide provides you with all you need to get started in receiving
+emails to your application.
+
+After reading this guide, you will know:
+
+* How to receive email within a Rails application.
+* How to configure Action Mailbox.
+* How to generate and route emails to a mailbox.
+* How to test incoming emails.
+
+--------------------------------------------------------------------------------
+
+Introduction
+------------
+
+Action Mailbox routes incoming emails to controller-like mailboxes for
+processing in Rails. It ships with ingresses for Amazon SES, Mailgun, Mandrill,
+Postmark, and SendGrid. You can also handle inbound mails directly via the
+built-in Exim, Postfix, and Qmail ingresses.
+
+The inbound emails are turned into `InboundEmail` records using Active Record
+and feature lifecycle tracking, storage of the original email on cloud storage
+via Active Storage, and responsible data handling with
+on-by-default incineration.
+
+These inbound emails are routed asynchronously using Active Job to one or
+several dedicated mailboxes, which are capable of interacting directly
+with the rest of your domain model.
+
+## Setup
+
+Install migrations needed for `InboundEmail` and ensure Active Storage is set up:
+
+```bash
+$ rails action_mailbox:install
+$ rails db:migrate
+```
+
+## Configuration
+
+### Amazon SES
+
+Install the [`aws-sdk-sns`](https://rubygems.org/gems/aws-sdk-sns) gem:
+
+```ruby
+# Gemfile
+gem "aws-sdk-sns", ">= 1.9.0", require: false
+```
+
+Tell Action Mailbox to accept emails from SES:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :amazon
+```
+
+[Configure SES](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-notifications.html)
+to deliver emails to your application via POST requests to
+`/rails/action_mailbox/amazon/inbound_emails`. If your application lived at
+`https://example.com`, you would specify the fully-qualified URL
+`https://example.com/rails/action_mailbox/amazon/inbound_emails`.
+
+### Exim
+
+Tell Action Mailbox to accept emails from an SMTP relay:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :relay
+```
+
+Generate a strong password that Action Mailbox can use to authenticate requests to the relay ingress.
+
+Use `rails credentials:edit` to add the password to your application's encrypted credentials under
+`action_mailbox.ingress_password`, where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ ingress_password: ...
+```
+
+Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` environment variable.
+
+Configure Exim to pipe inbound emails to `bin/rails action_mailbox:ingress:exim`,
+providing the `URL` of the relay ingress and the `INGRESS_PASSWORD` you
+previously generated. If your application lived at `https://example.com`, the
+full command would look like this:
+
+```shell
+bin/rails action_mailbox:ingress:exim URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=...
+```
+
+### Mailgun
+
+Give Action Mailbox your
+[Mailgun API key](https://help.mailgun.com/hc/en-us/articles/203380100-Where-can-I-find-my-API-key-and-SMTP-credentials)
+so it can authenticate requests to the Mailgun ingress.
+
+Use `rails credentials:edit` to add your API key to your application's
+encrypted credentials under `action_mailbox.mailgun_api_key`,
+where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ mailgun_api_key: ...
+```
+
+Alternatively, provide your API key in the `MAILGUN_INGRESS_API_KEY` environment
+variable.
+
+Tell Action Mailbox to accept emails from Mailgun:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :mailgun
+```
+
+[Configure Mailgun](https://documentation.mailgun.com/en/latest/user_manual.html#receiving-forwarding-and-storing-messages)
+to forward inbound emails to `/rails/action_mailbox/mailgun/inbound_emails/mime`.
+If your application lived at `https://example.com`, you would specify the
+fully-qualified URL `https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime`.
+
+### Mandrill
+
+Give Action Mailbox your Mandrill API key so it can authenticate requests to
+the Mandrill ingress.
+
+Use `rails credentials:edit` to add your API key to your application's
+encrypted credentials under `action_mailbox.mandrill_api_key`,
+where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ mandrill_api_key: ...
+```
+
+Alternatively, provide your API key in the `MANDRILL_INGRESS_API_KEY`
+environment variable.
+
+Tell Action Mailbox to accept emails from Mandrill:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :mandrill
+```
+
+[Configure Mandrill](https://mandrill.zendesk.com/hc/en-us/articles/205583197-Inbound-Email-Processing-Overview)
+to route inbound emails to `/rails/action_mailbox/mandrill/inbound_emails`.
+If your application lived at `https://example.com`, you would specify
+the fully-qualified URL `https://example.com/rails/action_mailbox/mandrill/inbound_emails`.
+
+### Postfix
+
+Tell Action Mailbox to accept emails from an SMTP relay:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :relay
+```
+
+Generate a strong password that Action Mailbox can use to authenticate requests to the relay ingress.
+
+Use `rails credentials:edit` to add the password to your application's encrypted credentials under
+`action_mailbox.ingress_password`, where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ ingress_password: ...
+```
+
+Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` environment variable.
+
+[Configure Postfix](https://serverfault.com/questions/258469/how-to-configure-postfix-to-pipe-all-incoming-email-to-a-script)
+to pipe inbound emails to `bin/rails action_mailbox:ingress:postfix`, providing
+the `URL` of the Postfix ingress and the `INGRESS_PASSWORD` you previously
+generated. If your application lived at `https://example.com`, the full command
+would look like this:
+
+```shell
+$ bin/rails action_mailbox:ingress:postfix URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=...
+```
+
+### Postmark
+
+Tell Action Mailbox to accept emails from Postmark:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :postmark
+```
+
+Generate a strong password that Action Mailbox can use to authenticate
+requests to the Postmark ingress.
+
+Use `rails credentials:edit` to add the password to your application's
+encrypted credentials under `action_mailbox.ingress_password`,
+where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ ingress_password: ...
+```
+
+Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD`
+environment variable.
+
+[Configure Postmark inbound webhook](https://postmarkapp.com/manual#configure-your-inbound-webhook-url)
+to forward inbound emails to `/rails/action_mailbox/postmark/inbound_emails` with the username `actionmailbox`
+and the password you previously generated. If your application lived at `https://example.com`, you would
+configure Postmark with the following fully-qualified URL:
+
+```
+https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails
+```
+
+NOTE: When configuring your Postmark inbound webhook, be sure to check the box labeled **"Include raw email content in JSON payload"**.
+Action Mailbox needs the raw email content to work.
+
+### Qmail
+
+Tell Action Mailbox to accept emails from an SMTP relay:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :relay
+```
+
+Generate a strong password that Action Mailbox can use to authenticate requests to the relay ingress.
+
+Use `rails credentials:edit` to add the password to your application's encrypted credentials under
+`action_mailbox.ingress_password`, where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ ingress_password: ...
+```
+
+Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` environment variable.
+
+Configure Qmail to pipe inbound emails to `bin/rails action_mailbox:ingress:qmail`,
+providing the `URL` of the relay ingress and the `INGRESS_PASSWORD` you
+previously generated. If your application lived at `https://example.com`, the
+full command would look like this:
+
+```shell
+bin/rails action_mailbox:ingress:qmail URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=...
+```
+
+### SendGrid
+
+Tell Action Mailbox to accept emails from SendGrid:
+
+```ruby
+# config/environments/production.rb
+config.action_mailbox.ingress = :sendgrid
+```
+
+Generate a strong password that Action Mailbox can use to authenticate
+requests to the SendGrid ingress.
+
+Use `rails credentials:edit` to add the password to your application's
+encrypted credentials under `action_mailbox.ingress_password`,
+where Action Mailbox will automatically find it:
+
+```yaml
+action_mailbox:
+ ingress_password: ...
+```
+
+Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD`
+environment variable.
+
+[Configure SendGrid Inbound Parse](https://sendgrid.com/docs/for-developers/parsing-email/setting-up-the-inbound-parse-webhook/)
+to forward inbound emails to
+`/rails/action_mailbox/sendgrid/inbound_emails` with the username `actionmailbox`
+and the password you previously generated. If your application lived at `https://example.com`,
+you would configure SendGrid with the following URL:
+
+```
+https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/sendgrid/inbound_emails
+```
+
+NOTE: When configuring your SendGrid Inbound Parse webhook, be sure to check the box labeled **“Post the raw, full MIME message.”** Action Mailbox needs the raw MIME message to work.
+
+## Examples
+
+Configure basic routing:
+
+```ruby
+# app/mailboxes/application_mailbox.rb
+class ApplicationMailbox < ActionMailbox::Base
+ routing /^save@/i => :forwards
+ routing /@replies\./i => :replies
+end
+```
+
+Then set up a mailbox:
+
+```ruby
+# Generate new mailbox
+$ bin/rails generate mailbox forwards
+```
+
+```ruby
+# app/mailboxes/forwards_mailbox.rb
+class ForwardsMailbox < ApplicationMailbox
+ # Callbacks specify prerequisites to processing
+ before_processing :require_forward
+
+ def process
+ if forwarder.buckets.one?
+ record_forward
+ else
+ stage_forward_and_request_more_details
+ end
+ end
+
+ private
+ def require_forward
+ unless message.forward?
+ # Use Action Mailers to bounce incoming emails back to sender – this halts processing
+ bounce_with Forwards::BounceMailer.missing_forward(
+ inbound_email, forwarder: forwarder
+ )
+ end
+ end
+
+ def forwarder
+ @forwarder ||= Person.where(email_address: mail.from)
+ end
+
+ def record_forward
+ forwarder.buckets.first.record \
+ Forward.new forwarder: forwarder, subject: message.subject, content: mail.content
+ end
+
+ def stage_forward_and_request_more_details
+ Forwards::RoutingMailer.choose_project(mail).deliver_now
+ end
+end
+```
+
+## Incineration of InboundEmails
+
+By default, an InboundEmail that has been successfully processed will be
+incinerated after 30 days. This ensures you're not holding on to people's data
+willy-nilly after they may have canceled their accounts or deleted their
+content. The intention is that after you've processed an email, you should have
+extracted all the data you needed and turned it into domain models and content
+on your side of the application. The InboundEmail simply stays in the system
+for the extra time to provide debugging and forensics options.
+
+The actual incineration is done via the `IncinerationJob` that's scheduled
+to run after `config.action_mailbox.incinerate_after` time. This value is
+by default set to `30.days`, but you can change it in your production.rb
+configuration. (Note that this far-future incineration scheduling relies on
+your job queue being able to hold jobs for that long.)
+
+## Working with Action Mailbox in development
+
+It's helpful to be able to test incoming emails in development without actually
+sending and receiving real emails. To accomplish this, there's a conductor
+controller mounted at `/rails/conductor/action_mailbox/inbound_emails`,
+which gives you an index of all the InboundEmails in the system, their
+state of processing, and a form to create a new InboundEmail as well.
+
+## Testing mailboxes
+
+Example:
+
+```ruby
+class ForwardsMailboxTest < ActionMailbox::TestCase
+ test "directly recording a client forward for a forwarder and forwardee corresponding to one project" do
+ assert_difference -> { people(:david).buckets.first.recordings.count } do
+ receive_inbound_email_from_mail \
+ to: 'save@example.com',
+ from: people(:david).email_address,
+ subject: "Fwd: Status update?",
+ body: <<~BODY
+ --- Begin forwarded message ---
+ From: Frank Holland <frank@microsoft.com>
+
+ What's the status?
+ BODY
+ end
+
+ recording = people(:david).buckets.first.recordings.last
+ assert_equal people(:david), recording.creator
+ assert_equal "Status update?", recording.forward.subject
+ assert_match "What's the status?", recording.forward.content.to_s
+ end
+end
+```
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 1acb993cad..16db433bd4 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -3,13 +3,13 @@
Action Mailer Basics
====================
-This guide provides you with all you need to get started in sending and
-receiving emails from and to your application, and many internals of Action
+This guide provides you with all you need to get started in sending
+emails from and to your application, and many internals of Action
Mailer. It also covers how to test your mailers.
After reading this guide, you will know:
-* How to send and receive email within a Rails application.
+* How to send email within a Rails application.
* How to generate and edit an Action Mailer class and mailer view.
* How to configure Action Mailer for your environment.
* How to test your Action Mailer classes.
@@ -427,7 +427,7 @@ If you would like to render a template located outside of the default `app/views
```ruby
class UserMailer < ApplicationMailer
prepend_view_path "custom/path/to/mailer/view"
-
+
# This will try to load "custom/path/to/mailer/view/welcome_email" template
def welcome_email
# ...
@@ -651,48 +651,8 @@ class UserMailer < ApplicationMailer
end
```
-Receiving Emails
-----------------
-
-Receiving and parsing emails with Action Mailer can be a rather complex
-endeavor. Before your email reaches your Rails app, you would have had to
-configure your system to somehow forward emails to your app, which needs to be
-listening for that. So, to receive emails in your Rails app you'll need to:
-
-* Implement a `receive` method in your mailer.
-
-* Configure your email server to forward emails from the address(es) you would
- like your app to receive to `/path/to/app/bin/rails runner
- 'UserMailer.receive(STDIN.read)'`.
-
-Once a method called `receive` is defined in any mailer, Action Mailer will
-parse the raw incoming email into an email object, decode it, instantiate a new
-mailer, and pass the email object to the mailer `receive` instance
-method. Here's an example:
-
-```ruby
-class UserMailer < ApplicationMailer
- def receive(email)
- page = Page.find_by(address: email.to.first)
- page.emails.create(
- subject: email.subject,
- body: email.body
- )
-
- if email.has_attachments?
- email.attachments.each do |attachment|
- page.attachments.create({
- file: attachment,
- description: email.subject
- })
- end
- end
- end
-end
-```
-
Action Mailer Callbacks
----------------------------
+-----------------------
Action Mailer allows for you to specify a `before_action`, `after_action` and
`around_action`.
@@ -882,7 +842,7 @@ class EmailDeliveryObserver
end
end
```
-Like interceptors, you need to register observers with the Action Mailer framework. You can do this in an initializer file
+Like interceptors, you need to register observers with the Action Mailer framework. You can do this in an initializer file
`config/initializers/email_delivery_observer.rb`
```ruby
diff --git a/guides/source/action_text_overview.md b/guides/source/action_text_overview.md
new file mode 100644
index 0000000000..08ec35e52a
--- /dev/null
+++ b/guides/source/action_text_overview.md
@@ -0,0 +1,99 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
+
+Action Text Overview
+====================
+
+This guide provides you with all you need to get started in handling
+rich text content.
+
+After reading this guide, you will know:
+
+* How to configure Action Text.
+* How to handle rich text content.
+* How to style rich text content.
+
+--------------------------------------------------------------------------------
+
+Introduction
+------------
+
+Action Text brings rich text content and editing to Rails. It includes
+the [Trix editor](https://trix-editor.org) that handles everything from formatting
+to links to quotes to lists to embedded images and galleries.
+The rich text content generated by the Trix editor is saved in its own
+RichText model that's associated with any existing Active Record model in the application.
+Any embedded images (or other attachments) are automatically stored using
+Active Storage and associated with the included RichText model.
+
+## Trix compared to other rich text editors
+
+Most WYSIWYG editors are wrappers around HTML’s `contenteditable` and `execCommand` APIs,
+designed by Microsoft to support live editing of web pages in Internet Explorer 5.5,
+and [eventually reverse-engineered](https://blog.whatwg.org/the-road-to-html-5-contenteditable#history)
+and copied by other browsers.
+
+Because these APIs were never fully specified or documented,
+and because WYSIWYG HTML editors are enormous in scope, each
+browser's implementation has its own set of bugs and quirks,
+and JavaScript developers are left to resolve the inconsistencies.
+
+Trix sidesteps these inconsistencies by treating contenteditable
+as an I/O device: when input makes its way to the editor, Trix converts that input
+into an editing operation on its internal document model, then re-renders
+that document back into the editor. This gives Trix complete control over what
+happens after every keystroke, and avoids the need to use execCommand at all.
+
+## Installation
+
+Run `rails action_text:install` to add the Yarn package and copy over the necessary migration.
+
+## Examples
+
+Adding a rich text field to an existing model:
+
+```ruby
+# app/models/message.rb
+class Message < ApplicationRecord
+ has_rich_text :content
+end
+```
+
+Then refer to this field in the form for the model:
+
+```erb
+<%# app/views/messages/_form.html.erb %>
+<%= form_with(model: message) do |form| %>
+ <div class="field">
+ <%= form.label :content %>
+ <%= form.rich_text_area :content %>
+ </div>
+<% end %>
+```
+
+And finally display the sanitized rich text on a page:
+
+```erb
+<%= @message.content %>
+```
+
+To accept the rich text content, all you have to do is permit the referenced attribute:
+
+```ruby
+class MessagesController < ApplicationController
+ def create
+ message = Message.create! params.require(:message).permit(:title, :content)
+ redirect_to message
+ end
+end
+```
+
+## Custom styling
+
+By default, the Action Text editor and content is styled by the Trix defaults.
+If you want to change these defaults, you'll want to remove
+the `app/assets/stylesheets/actiontext.css` linker and base your stylings on
+the [contents of that file](https://raw.githubusercontent.com/basecamp/trix/master/dist/trix.css).
+
+You can also style the HTML used for embedded images and other attachments (known as blobs).
+On installation, Action Text will copy over a partial to
+`app/views/active_storage/blobs/_blob.html.erb`, which you can specialize.
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index a67e2924d7..b0d4bbd2c0 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -105,9 +105,9 @@ depending on the purpose of these columns.
fields that Active Record will look for when you create associations between
your models.
* **Primary keys** - By default, Active Record will use an integer column named
- `id` as the table's primary key. When using [Active Record
- Migrations](active_record_migrations.html) to create your tables, this column will be
- automatically created.
+ `id` as the table's primary key (`bigint` for Postgres and MYSQL, `integer`
+ for SQLite). When using [Active Record Migrations](active_record_migrations.html)
+ to create your tables, this column will be automatically created.
There are also some optional column names that will add additional features
to Active Record instances:
diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md
index 51f50e8931..e3bb41ae32 100644
--- a/guides/source/active_storage_overview.md
+++ b/guides/source/active_storage_overview.md
@@ -434,7 +434,7 @@ original blob into the specified format and redirect to its new service
location.
```erb
-<%= image_tag user.avatar.variant(resize_to_fit: [100, 100]) %>
+<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %>
```
To switch to the Vips processor, you would add the following to
@@ -489,8 +489,7 @@ directly from the client to the cloud.
Using the npm package:
```js
- import * as ActiveStorage from "activestorage"
- ActiveStorage.start()
+ require("@rails/activestorage").start()
```
2. Annotate file inputs with the direct upload URL.
@@ -616,7 +615,7 @@ of choice, instantiate a DirectUpload and call its create method. Create takes
a callback to invoke when the upload completes.
```js
-import { DirectUpload } from "activestorage"
+import { DirectUpload } from "@rails/activestorage"
const input = document.querySelector('input[type=file]')
@@ -664,7 +663,7 @@ will call the object's `directUploadWillStoreFileWithXHR` method. You can then
bind your own progress handler on the XHR.
```js
-import { DirectUpload } from "activestorage"
+import { DirectUpload } from "@rails/activestorage"
class Uploader {
constructor(file, url) {
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 5e68b3f400..c1c3832b79 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -255,13 +255,16 @@ Active Record
### sql.active_record
-| Key | Value |
-| ---------------- | ---------------------------------------- |
-| `:sql` | SQL statement |
-| `:name` | Name of the operation |
-| `:connection_id` | `self.object_id` |
-| `:binds` | Bind parameters |
-| `:cached` | `true` is added when cached queries used |
+| Key | Value |
+| -------------------- | ---------------------------------------- |
+| `:sql` | SQL statement |
+| `:name` | Name of the operation |
+| `:connection_id` | Object ID of the connection object |
+| `:connection` | Connection object |
+| `:binds` | Bind parameters |
+| `:type_casted_binds` | Typecasted bind parameters |
+| `:statement_name` | SQL Statement name |
+| `:cached` | `true` is added when cached queries used |
INFO. The adapters will add their own data as well.
@@ -270,7 +273,10 @@ INFO. The adapters will add their own data as well.
sql: "SELECT \"posts\".* FROM \"posts\" ",
name: "Post Load",
connection_id: 70307250813140,
- binds: []
+ connection: #<ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f7a838850>,
+ binds: [#<ActiveModel::Attribute::WithCastValue:0x00007fe19d15dc00>],
+ type_casted_binds: [11],
+ statement_name: nil
}
```
@@ -291,32 +297,6 @@ INFO. The adapters will add their own data as well.
Action Mailer
-------------
-### receive.action_mailer
-
-| Key | Value |
-| ------------- | -------------------------------------------- |
-| `:mailer` | Name of the mailer class |
-| `:message_id` | ID of the message, generated by the Mail gem |
-| `:subject` | Subject of the mail |
-| `:to` | To address(es) of the mail |
-| `:from` | From address of the mail |
-| `:bcc` | BCC addresses of the mail |
-| `:cc` | CC addresses of the mail |
-| `:date` | Date of the mail |
-| `:mail` | The encoded form of the mail |
-
-```ruby
-{
- mailer: "Notification",
- message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",
- subject: "Rails Guides",
- to: ["users@rails.com", "dhh@rails.com"],
- from: ["me@rails.com"],
- date: Sat, 10 Mar 2012 14:18:09 +0100,
- mail: "..." # omitted for brevity
-}
-```
-
### deliver.action_mailer
| Key | Value |
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 500e230ff9..e7faa5c330 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -1101,7 +1101,7 @@ Windows you have a JavaScript runtime installed in your operating system.
-### Serving GZipped version of assets
+### GZipping your assets
By default, gzipped version of compiled assets will be generated, along with
the non-gzipped version of assets. Gzipped assets help reduce the transmission
@@ -1111,6 +1111,8 @@ of data over the wire. You can configure this by setting the `gzip` flag.
config.assets.gzip = false # disable gzipped assets generation
```
+Refer to your web server's documentation for instructions on how to serve gzipped assets.
+
### Using Your Own Compressor
The compressor config settings for CSS and JavaScript also take any object.
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 4f3e8b2cff..38e46e4bf8 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1257,7 +1257,7 @@ Controls what happens to the associated object when its owner is destroyed:
* `:destroy` causes the associated object to also be destroyed
* `:delete` causes the associated object to be deleted directly from the database (so callbacks will not execute)
-* `:nullify` causes the foreign key to be set to `NULL`. Callbacks are not executed.
+* `:nullify` causes the foreign key to be set to `NULL`. Polymorphic type column is also nullified on polymorphic associations. Callbacks are not executed.
* `:restrict_with_exception` causes an `ActiveRecord::DeleteRestrictionError` exception to be raised if there is an associated record
* `:restrict_with_error` causes an error to be added to the owner if there is an associated object
@@ -1658,7 +1658,7 @@ Controls what happens to the associated objects when their owner is destroyed:
* `:destroy` causes all the associated objects to also be destroyed
* `:delete_all` causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
-* `:nullify` causes the foreign keys to be set to `NULL`. Callbacks are not executed.
+* `:nullify` causes the foreign key to be set to `NULL`. Polymorphic type column is also nullified on polymorphic associations. Callbacks are not executed.
* `:restrict_with_exception` causes an `ActiveRecord::DeleteRestrictionError` exception to be raised if there are any associated records
* `:restrict_with_error` causes an error to be added to the owner if there are any associated objects
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 3ac3f8fa8b..56c0ca78a0 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -51,10 +51,10 @@ For instance, it will not impact low-level caching, that we address
### Page Caching
Page caching is a Rails mechanism which allows the request for a generated page
-to be fulfilled by the webserver (i.e. Apache or NGINX) without having to go
+to be fulfilled by the web server (i.e. Apache or NGINX) without having to go
through the entire Rails stack. While this is super fast it can't be applied to
every situation (such as pages that need authentication). Also, because the
-webserver is serving a file directly from the filesystem you will need to
+web server is serving a file directly from the filesystem you will need to
implement cache expiration.
INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching).
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index ae1de3079f..a727dcd010 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -387,28 +387,6 @@ The PostgreSQL adapter adds one additional configuration option:
highly recommended that you do not enable this in a production environment.
Defaults to `false` in all environments.
-The SQLite3Adapter adapter adds one additional configuration option:
-
-* `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer`
-indicates whether boolean values are stored in sqlite3 databases as 1 and 0 or
-'t' and 'f'. Leaving `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer`
-set to false is deprecated. SQLite databases have used 't' and 'f' to serialize
-boolean values and must have old data converted to 1 and 0 (its native boolean
-serialization) before setting this flag to true. Conversion can be accomplished
-by setting up a Rake task which runs
-
- ```ruby
- ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1)
- ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0)
- ```
-
- for all models and all boolean columns, after which the flag must be set to true
-by adding the following to your `application.rb` file:
-
- ```ruby
- Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
- ```
-
The schema dumper adds two additional configuration options:
* `ActiveRecord::SchemaDumper.ignore_tables` accepts an array of tables that should _not_ be included in any generated schema file.
@@ -441,7 +419,7 @@ The schema dumper adds two additional configuration options:
* `config.action_controller.per_form_csrf_tokens` configures whether CSRF tokens are only valid for the method/action they were generated for.
-* `config.action_controller.default_protect_from_forgery` determines whether forgery protection is added on `ActionController:Base`. This is false by default, but enabled when loading defaults for Rails 5.2.
+* `config.action_controller.default_protect_from_forgery` determines whether forgery protection is added on `ActionController:Base`. This is false by default.
* `config.action_controller.relative_url_root` can be used to tell Rails that you are [deploying to a subdirectory](configuring.html#deploy-to-a-subdirectory-relative-url-root). The default is `ENV['RAILS_RELATIVE_URL_ROOT']`.
@@ -619,6 +597,29 @@ Defaults to `'signed cookie'`.
development mode, but for large test suites, disabling this option in
the test environment can improve performance. This defaults to `true`.
+
+### Configuring Action Mailbox
+
+`config.action_mailbox` provides the following configuration options:
+
+* `config.action_mailbox.logger` contains the logger used by Action Mailbox. It accepts a logger conforming to the interface of Log4r or the default Ruby Logger class. The default is `Rails.logger`.
+
+ ```ruby
+ config.action_mailbox.logger = ActiveSupport::Logger.new(STDOUT)
+ ```
+
+* `config.action_mailbox.incinerate_after` accepts an `ActiveSupport::Duration` indicating how long after processing `ActionMailbox::InboundEmail` records should be destroyed. It defaults to `30.days`.
+
+ ```ruby
+ # Incinerate inbound emails 14 days after processing.
+ config.action_mailbox.incinerate_after = 14.days
+ ```
+
+* `config.action_mailbox.queues.incineration` accepts a symbol indicating the Active Job queue to use for incineration jobs. It defaults to `:action_mailbox_incineration`.
+
+* `config.action_mailbox.queues.routing` accepts a symbol indicating the Active Job queue to use for routing jobs. It defaults to `:action_mailbox_routing`.
+
+
### Configuring Action Mailer
There are a number of settings available on `config.action_mailer`:
@@ -698,6 +699,8 @@ There are a number of settings available on `config.action_mailer`:
* `config.action_mailer.perform_caching` specifies whether the mailer templates should perform fragment caching or not. By default this is `false` in all environments.
+* `config.action_mailer.delivery_job` specifies delivery job for mail. Defaults to `ActionMailer::DeliveryJob`.
+
### Configuring Active Support
@@ -715,7 +718,7 @@ There are a few configuration options available in Active Support:
* `config.active_support.use_sha1_digests` specifies whether to use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header. Defaults to false.
-* `config.active_support.use_authenticated_message_encryption` specifies whether to use AES-256-GCM authenticated encryption as the default cipher for encrypting messages instead of AES-256-CBC. This is false by default, but enabled when loading defaults for Rails 5.2.
+* `config.active_support.use_authenticated_message_encryption` specifies whether to use AES-256-GCM authenticated encryption as the default cipher for encrypting messages instead of AES-256-CBC. This is false by default.
* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
@@ -815,15 +818,21 @@ normal Rails server.
config.active_storage.paths[:ffprobe] = '/usr/local/bin/ffprobe'
```
-* `config.active_storage.variable_content_types` accepts an array of strings indicating the content types that Active Storage can transform through ImageMagick. The default is `%w(image/png image/gif image/jpg image/jpeg image/pjpeg image/vnd.adobe.photoshop image/vnd.microsoft.icon)`.
+* `config.active_storage.variable_content_types` accepts an array of strings indicating the content types that Active Storage can transform through ImageMagick. The default is `%w(image/png image/gif image/jpg image/jpeg image/pjpeg image/tiff image/vnd.adobe.photoshop image/vnd.microsoft.icon)`.
* `config.active_storage.content_types_to_serve_as_binary` accepts an array of strings indicating the content types that Active Storage will always serve as an attachment, rather than inline. The default is `%w(text/html
text/javascript image/svg+xml application/postscript application/x-shockwave-flash text/xml application/xml application/xhtml+xml)`.
-* `config.active_storage.queue` can be used to set the name of the Active Job queue used to perform jobs like analyzing the content of a blob or purging a blog.
+* `config.active_storage.queues.analysis` accepts a symbol indicating the Active Job queue to use for analysis jobs. When this option is `nil`, analysis jobs are sent to the default Active Job queue (see `config.active_job.default_queue_name`).
+
+ ```ruby
+ config.active_storage.queues.analysis = :low_priority
+ ```
+
+* `config.active_storage.queues.purge` accepts a symbol indicating the Active Job queue to use for purge jobs. When this option is `nil`, purge jobs are sent to the default Active Job queue (see `config.active_job.default_queue_name`).
```ruby
- config.active_storage.queue = :low_priority
+ config.active_storage.queues.purge = :low_priority
```
* `config.active_storage.logger` can be used to set the logger used by Active Storage. Accepts a logger conforming to the interface of Log4r or the default Ruby Logger class.
@@ -847,6 +856,39 @@ text/javascript image/svg+xml application/postscript application/x-shockwave-fla
The default is `/rails/active_storage`
+### Results of `load_defaults`
+
+#### With '5.0':
+
+- `config.action_controller.per_form_csrf_tokens`: `true`
+- `config.action_controller.forgery_protection_origin_check`: `true`
+- `ActiveSupport.to_time_preserves_timezone`: `true`
+- `config.active_record.belongs_to_required_by_default`: `true`
+- `config.ssl_options`: `{ hsts: { subdomains: true } }`
+
+#### With '5.1':
+
+- `config.assets.unknown_asset_fallback`: `false`
+- `config.action_view.form_with_generates_remote_forms`: `true`
+
+#### With '5.2':
+
+- `config.active_record.cache_versioning`: `true`
+- `action_dispatch.use_authenticated_cookie_encryption`: `true`
+- `config.active_support.use_authenticated_message_encryption`: `true`
+- `config.active_support.use_sha1_digests`: `true`
+- `config.action_controller.default_protect_from_forgery`: `true`
+- `config.action_view.form_with_generates_ids`: `true`
+
+#### With '6.0':
+
+- `config.action_view.default_enforce_utf8`: `false`
+- `config.action_dispatch.use_cookies_with_metadata`: `true`
+- `config.action_mailer.delivery_job`: `"ActionMailer::MailDeliveryJob"`
+- `config.active_job.return_false_on_aborted_enqueue`: `true`
+- `config.active_storage.queues.analysis`: `:active_storage_analysis`
+- `config.active_storage.queues.purge`: `:active_storage_purge`
+
### Configuring a Database
Just about every Rails application will interact with a database. You can connect to the database by setting an environment variable `ENV['DATABASE_URL']` or by using a configuration file called `config/database.yml`.
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index b3baf726e3..e84e5561f2 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -227,7 +227,6 @@ If you're using another database, check the file `activerecord/test/config.yml`
If you installed Yarn, you will need to install the javascript dependencies:
```bash
-$ cd activestorage
$ yarn install
```
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 25c159d471..25e4fdb4e6 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -74,7 +74,17 @@
-
name: Action Mailer Basics
url: action_mailer_basics.html
- description: This guide describes how to use Action Mailer to send and receive emails.
+ description: This guide describes how to use Action Mailer to send emails.
+ -
+ name: Action Mailbox Basics
+ work_in_progress: true
+ url: action_mailbox_basics.html
+ description: This guide describes how to use Action Mailbox to receive emails.
+ -
+ name: Action Text Overview
+ work_in_progress: true
+ url: action_text_overview.html
+ description: This guide describes how to use Action Text to handle rich text content.
-
name: Active Job Basics
url: active_job_basics.html
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 1e93a19c84..f15383e3f1 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -1091,16 +1091,15 @@ main Rails application.
Engine model and controller classes can be extended by open classing them in the
main Rails application (since model and controller classes are just Ruby classes
that inherit Rails specific functionality). Open classing an Engine class
-redefines it for use in the main application. This is usually implemented by
-using the decorator pattern.
+redefines it for use in the main application.
For simple class modifications, use `Class#class_eval`. For complex class
modifications, consider using `ActiveSupport::Concern`.
-#### A note on Decorators and Loading Code
+#### A note on Overriding and Loading Code
-Because these decorators are not referenced by your Rails application itself,
-Rails' autoloading system will not kick in and load your decorators. This means
+Because these overrides are not referenced by your Rails application itself,
+Rails' autoloading system will not kick in and load your overrides. This means
that you need to require them yourself.
Here is some sample code to do this:
@@ -1112,7 +1111,7 @@ module Blorgh
isolate_namespace Blorgh
config.to_prepare do
- Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c|
+ Dir.glob(Rails.root + "app/overrides/**/*_override*.rb").each do |c|
require_dependency(c)
end
end
@@ -1120,15 +1119,15 @@ module Blorgh
end
```
-This doesn't apply to just Decorators, but anything that you add in an engine
+This doesn't apply to just overrides, but anything that you add in an engine
that isn't referenced by your main application.
-#### Implementing Decorator Pattern Using Class#class_eval
+#### Reopening existing classes using Class#class_eval
**Adding** `Article#time_since_created`:
```ruby
-# MyApp/app/decorators/models/blorgh/article_decorator.rb
+# MyApp/app/overrides/models/blorgh/article_override.rb
Blorgh::Article.class_eval do
def time_since_created
@@ -1149,7 +1148,7 @@ end
**Overriding** `Article#summary`:
```ruby
-# MyApp/app/decorators/models/blorgh/article_decorator.rb
+# MyApp/app/overrides/models/blorgh/article_override.rb
Blorgh::Article.class_eval do
def summary
@@ -1169,7 +1168,7 @@ class Article < ApplicationRecord
end
```
-#### Implementing Decorator Pattern Using ActiveSupport::Concern
+#### Reopening existing classes using ActiveSupport::Concern
Using `Class#class_eval` is great for simple adjustments, but for more complex
class modifications, you might want to consider using [`ActiveSupport::Concern`]
@@ -1505,13 +1504,19 @@ To hook into the initialization process of one of the following classes use the
| `ActionController::TestCase` | `action_controller_test_case` |
| `ActionDispatch::IntegrationTest` | `action_dispatch_integration_test` |
| `ActionDispatch::SystemTestCase` | `action_dispatch_system_test_case` |
+| `ActionMailbox::Base` | `action_mailbox` |
+| `ActionMailbox::InboundEmail` | `action_mailbox_inbound_email` |
+| `ActionMailbox::TestCase` | `action_mailbox_test_case` |
| `ActionMailer::Base` | `action_mailer` |
| `ActionMailer::TestCase` | `action_mailer_test_case` |
+| `ActionText::Content` | `action_text_content` |
+| `ActionText::RichText` | `action_text_rich_text` |
| `ActionView::Base` | `action_view` |
| `ActionView::TestCase` | `action_view_test_case` |
| `ActiveJob::Base` | `active_job` |
| `ActiveJob::TestCase` | `active_job_test_case` |
| `ActiveRecord::Base` | `active_record` |
+| `ActiveStorage::Blob` | `active_storage_blob` |
| `ActiveSupport::TestCase` | `active_support_test_case` |
| `i18n` | `i18n` |
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 10b1a6de7e..d146685675 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -139,10 +139,12 @@ Note that appending directly to `I18n.load_paths` instead of to the application'
### Managing the Locale across Requests
-The default locale is used for all translations unless `I18n.locale` is explicitly set.
-
A localized application will likely need to provide support for multiple locales. To accomplish this, the locale should be set at the beginning of each request so that all strings are translated using the desired locale during the lifetime of that request.
+The default locale is used for all translations unless `I18n.locale=` or `I18n.with_locale` is used.
+
+`I18n.locale` can leak into subsequent requests served by the same thread/process if it is not consistently set in every controller. For example executing `I18n.locale = :es` in one POST requests will have effects for all later requests to controllers that don't set the locale, but only in that particular thread/process. For that reason, instead of `I18n.locale =` you can use `I18n.with_locale` which does not have this leak issue.
+
The locale can be set in an `around_action` in the `ApplicationController`:
```ruby
@@ -299,7 +301,7 @@ A trivial implementation of using an `Accept-Language` header would be:
def switch_locale(&action)
logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
locale = extract_locale_from_accept_language_header
- logger.debug "* Locale set to '#{I18n.locale}'"
+ logger.debug "* Locale set to '#{locale}'"
I18n.with_locale(locale, &action)
end
diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb
index 76f01fea0a..10e388774c 100644
--- a/guides/source/index.html.erb
+++ b/guides/source/index.html.erb
@@ -1,6 +1,5 @@
-<% content_for :page_title do %>
-Ruby on Rails Guides
-<% end %>
+<% content_for :page_title, "Ruby on Rails Guides" %>
+<% content_for :description, "Ruby on Rails Guides" %>
<% content_for :header_section do %>
<%= render 'welcome' %>
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 1f42d72756..65a003fceb 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
- <title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title>
+ <title><%= yield(:page_title) %></title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" data-turbolinks-track="reload">
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print">
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" data-turbolinks-track="reload">
@@ -14,6 +14,13 @@
<script src="javascripts/turbolinks.js" data-turbolinks-track="reload"></script>
<script src="javascripts/guides.js" data-turbolinks-track="reload"></script>
<script src="javascripts/responsive-tables.js" data-turbolinks-track="reload"></script>
+ <meta property="og:title" content="<%= yield(:page_title) %>" />
+ <meta name="description" content="<%= yield(:description) %>" />
+ <meta property="og:description" content="<%= yield(:description) %>" />
+ <meta property="og:locale" content="en_US" />
+ <meta property="og:site_name" content="Ruby on Rails Guides" />
+ <meta property="og:image" content="https://avatars.githubusercontent.com/u/4223" />
+ <meta property="og:type" content="website" />
</head>
<body class="guide">
<% if @edge %>
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index c33851a0f9..69b5f254bf 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -35,7 +35,7 @@ application. Any Rack compliant web server should be using
### `rails server`
-`rails server` does the basic job of creating a `Rack::Server` object and starting the webserver.
+`rails server` does the basic job of creating a `Rack::Server` object and starting the web server.
Here's how `rails server` creates an instance of `Rack::Server`
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 0a0f1b6754..a33ac6a589 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -519,7 +519,7 @@ resources :photos do
end
```
-You can leave out the `:on` option, this will create the same member route except that the resource id value will be available in `params[:photo_id]` instead of `params[:id]`.
+You can leave out the `:on` option, this will create the same member route except that the resource id value will be available in `params[:photo_id]` instead of `params[:id]`. Route helpers will also be renamed from `preview_photo_url` and `preview_photo_path` to `photo_preview_url` and `photo_preview_path`.
#### Adding Collection Routes
diff --git a/guides/source/security.md b/guides/source/security.md
index dbec3cdd2d..a2fb4663cf 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -32,27 +32,17 @@ In order to develop secure web applications you have to keep up to date on all l
Sessions
--------
-A good place to start looking at security is with sessions, which can be vulnerable to particular attacks.
+This chapter describes some particular attacks related to sessions, and security measures to protect your session data.
### What are Sessions?
-NOTE: _HTTP is a stateless protocol. Sessions make it stateful._
+INFO: Sessions enable the application to maintain user-specific state, while users interact with the application. For example, sessions allow users to authenticate once and remain signed in for future requests.
-Most applications need to keep track of certain state of a particular user. This could be the contents of a shopping basket or the user id of the currently logged in user. Without the idea of sessions, the user would have to identify, and probably authenticate, on every request.
-Rails will create a new session automatically if a new user accesses the application. It will load an existing session if the user has already used the application.
+Most applications need to keep track of state for users that interact with the application. This could be the contents of a shopping basket, or the user id of the currently logged in user. This kind of user-specific state can be stored in the session.
-A session usually consists of a hash of values and a session ID, usually a 32-character string, to identify the hash. Every cookie sent to the client's browser includes the session ID. And the other way round: the browser will send it to the server on every request from the client. In Rails you can save and retrieve values using the session method:
+Rails provides a session object for each user that accesses the application. If the user already has an active session, Rails uses the existing session. Otherwise a new session is created.
-```ruby
-session[:user_id] = @current_user.id
-User.find(session[:user_id])
-```
-
-### Session ID
-
-NOTE: _The session ID is a 32-character random hex string._
-
-The session ID is generated using `SecureRandom.hex` which generates a random hex string using platform specific methods (such as OpenSSL, /dev/urandom or Win32 CryptoAPI) for generating cryptographically secure random numbers. Currently it is not feasible to brute-force Rails' session IDs.
+NOTE: Read more about sessions and how to use them in [Action Controller Overview Guide](action_controller_overview.html#session).
### Session Hijacking
@@ -76,35 +66,31 @@ Hence, the cookie serves as temporary authentication for the web application. An
The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from 0.5%-10% of account balance, $0.5-$30 for credit card numbers ($20-$60 with full details), $0.1-$1.5 for identities (Name, SSN & DOB), $20-$50 for retailer accounts, and $6-$10 for cloud service provider accounts, according to the [Symantec Internet Security Threat Report (2017)](https://www.symantec.com/content/dam/symantec/docs/reports/istr-22-2017-en.pdf).
-### Session Guidelines
-
-Here are some general guidelines on sessions.
-
-* _Do not store large objects in a session_. Instead you should store them in the database and save their id in the session. This will eliminate synchronization headaches and it won't fill up your session storage space (depending on what session storage you chose, see below).
-This will also be a good idea, if you modify the structure of an object and old versions of it are still in some user's cookies. With server-side session storages you can clear out the sessions, but with client-side storages, this is hard to mitigate.
-
-* _Critical data should not be stored in session_. If the user clears their cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data.
+### Session Storage
-### Encrypted Session Storage
+NOTE: Rails uses `ActionDispatch::Session::CookieStore` as the default session storage.
-NOTE: _Rails provides several storage mechanisms for the session hashes. The most important is `ActionDispatch::Session::CookieStore`._
+TIP: Learn more about other session storages in [Action Controller Overview Guide](action_controller_overview.html#session).
-The `CookieStore` saves the session hash directly in a cookie on the
-client-side. The server retrieves the session hash from the cookie and
+Rails `CookieStore` saves the session hash in a cookie on the client-side.
+The server retrieves the session hash from the cookie and
eliminates the need for a session ID. That will greatly increase the
speed of the application, but it is a controversial storage option and
you have to think about the security implications and storage
limitations of it:
-* Cookies imply a strict size limit of 4kB. This is fine as you should
- not store large amounts of data in a session anyway, as described
- before. Storing the current user's database id in a session is common
- practice.
+* Cookies have a size limit of 4kB. Use cookies only for data which is relevant for the session.
+
+* Cookies are stored on the client-side. The client may preserve cookie contents even for expired cookies. The client may copy cookies to other machines. Avoid storing sensitive data in cookies.
+
+* Cookies are temporary by nature. The server can set expiration time for the cookie, but the client may delete the cookie and its contents before that. Persist all data that is of more permanent nature on the server side.
* Session cookies do not invalidate themselves and can be maliciously
reused. It may be a good idea to have your application invalidate old
session cookies using a stored timestamp.
+* Rails encrypts cookies by default. The client cannot read or edit the contents of the cookie, without breaking encryption. If you take appropriate care of your secrets, you can consider your cookies to be generally secured.
+
The `CookieStore` uses the
[encrypted](http://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-encrypted)
cookie jar to provide a secure, encrypted location to store session
@@ -114,32 +100,9 @@ verification key used for
[signed](http://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-signed)
cookies, is derived from the `secret_key_base` configuration value.
-As of Rails 5.2 encrypted cookies and sessions are protected using AES
-GCM encryption. This form of encryption is a type of Authenticated
-Encryption and couples authentication and encryption in single step
-while also producing shorter ciphertexts as compared to other
-algorithms previously used. The key for cookies encrypted with AES GCM
-are derived using a salt value defined by the
-`config.action_dispatch.authenticated_encrypted_cookie_salt`
-configuration value.
-
-Prior to this version, encrypted cookies were secured using AES in CBC
-mode with HMAC using SHA1 for authentication. The keys for this type of
-encryption and for HMAC verification were derived via the salts defined
-by `config.action_dispatch.encrypted_cookie_salt` and
-`config.action_dispatch.encrypted_signed_cookie_salt` respectively.
-
-Prior to Rails version 4 in both versions 2 and 3, session cookies were
-protected using only HMAC verification. As such, these session cookies
-only provided integrity to their content because the actual session data
-was stored in plaintext encoded as base64. This is how `signed` cookies
-work in the current version of Rails. These kinds of cookies are still
-useful for protecting the integrity of certain client-stored data and
-information.
-
-__Do not use a trivial secret for the `secret_key_base`, i.e. a word
-from a dictionary, or one which is shorter than 30 characters! Instead
-use `rails secret` to generate secret keys!__
+TIP: Secrets must be long and random. Use `rails secret` to get new unique secrets.
+
+INFO: Learn more about [managing credentials later in this guide](security.html#custom-credentials)
It is also important to use different salt values for encrypted and
signed cookies. Using the same value for different salt configuration
@@ -150,7 +113,7 @@ In test and development applications get a `secret_key_base` derived from the ap
secret_key_base: 492f...
-If you have received an application where the secret was exposed (e.g. an application whose source was shared), strongly consider changing the secret.
+WARNING: If your application's secrets may have been exposed, strongly consider changing them. Changing `secret_key_base` will expire currently active sessions.
### Rotating Encrypted and Signed Cookies Configurations
@@ -1204,23 +1167,18 @@ loaded inline `<script>` elements.
Environmental Security
----------------------
-It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, and your server-side secret, e.g. stored in `config/secrets.yml`. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information.
+It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, master key for `credentials.yml`, and other unencrypted secrets. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information.
### Custom credentials
-Rails generates a `config/credentials.yml.enc` to store third-party credentials
-within the repo. This is only viable because Rails encrypts the file with a master
-key that's generated into a version control ignored `config/master.key` — Rails
-will also look for that key in `ENV["RAILS_MASTER_KEY"]`. Rails also requires the
-key to boot in production, so the credentials can be read.
+Rails stores secrets in `config/credentials.yml.enc`, which is encrypted and hence cannot be edited directly. Rails uses `config/master.key` or alternatively looks for environment variable `ENV["RAILS_MASTER_KEY"]` to encrypt the credentials file. The credentials file can be stored in version control, as long as master key is kept safe.
-To edit stored credentials use `rails credentials:edit`.
+To add new secret to credentials, first run `rails secret` to get a new secret. Then run `rails credentials:edit` to edit credentials, and add the secret. Running `credentials:edit` creates new credentials file and master key, if they did not already exist.
By default, this file contains the application's
-`secret_key_base`, but it could also be used to store other credentials such as
-access keys for external APIs.
+`secret_key_base`, but it could also be used to store other credentials such as access keys for external APIs.
-The credentials added to this file are accessible via `Rails.application.credentials`.
+The secrets kept in credentials file are accessible via `Rails.application.credentials`.
For example, with the following decrypted `config/credentials.yml.enc`:
secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
@@ -1235,6 +1193,11 @@ version:
Rails.application.credentials.some_api_key! # => raises KeyError: :some_api_key is blank
```
+
+TIP: Learn more about credentials with `rails credentials:help`.
+
+WARNING: Keep your master key safe. Do not commit your master key.
+
Dependency Management and CVEs
------------------------------
diff --git a/guides/source/testing.md b/guides/source/testing.md
index f34f9d95f4..61353e8e7d 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -33,11 +33,11 @@ Rails creates a `test` directory for you as soon as you create a Rails project u
```bash
$ ls -F test
-application_system_test_case.rb fixtures/ integration/ models/ test_helper.rb
-controllers/ helpers/ mailers/ system/
+application_system_test_case.rb controllers/ helpers/ mailers/ system/
+channels/ fixtures/ integration/ models/ test_helper.rb
```
-The `helpers`, `mailers`, and `models` directories are meant to hold tests for view helpers, mailers, and models, respectively. The `controllers` directory is meant to hold tests for controllers, routes, and views. The `integration` directory is meant to hold tests for interactions between controllers.
+The `helpers`, `mailers`, and `models` directories are meant to hold tests for view helpers, mailers, and models, respectively. The `channels` directory is meant to hold tests for Action Cable connection and channels. The `controllers` directory is meant to hold tests for controllers, routes, and views. The `integration` directory is meant to hold tests for interactions between controllers.
The system test directory holds system tests, which are used for full browser
testing of your application. System tests allow you to test your application
@@ -1731,6 +1731,139 @@ class ProductTest < ActiveJob::TestCase
end
```
+Testing Action Cable
+--------------------
+
+Since Action Cable is used at different levels inside your application,
+you'll need to test both the channels, connection classes themselves, and that other
+entities broadcast correct messages.
+
+### Connection Test Case
+
+By default, when you generate new Rails application with Action Cable, a test for the base connection class (`ApplicationCable::Connection`) is generated as well under `test/channels/application_cable` directory.
+
+Connection tests aim to check whether a connection's identifiers get assigned properly
+or that any improper connection requests are rejected. Here is an example:
+
+```ruby
+class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
+ test "connects with params" do
+ # Simulate a connection opening by calling the `connect` method
+ connect params: { user_id: 42 }
+
+ # You can access the Connection object via `connection` in tests
+ assert_equal connection.user_id, "42"
+ end
+
+ test "rejects connection without params" do
+ # Use `assert_reject_connection` matcher to verify that
+ # connection is rejected
+ assert_reject_connection { connect }
+ end
+end
+```
+
+You can also specify request cookies the same way you do in integration tests:
+
+```ruby
+test "connects with cookies" do
+ cookies.signed[:user_id] = "42"
+
+ connect
+
+ assert_equal connection.user_id, "42"
+end
+```
+
+See the API documentation for [`ActionCable::Connection::TestCase`](http://api.rubyonrails.org/classes/ActionCable/Connection/TestCase.html) for more information.
+
+### Channel Test Case
+
+By default, when you generate a channel, an associated test will be generated as well
+under the `test/channels` directory. Here's an example test with a chat channel:
+
+```ruby
+require "test_helper"
+
+class ChatChannelTest < ActionCable::Channel::TestCase
+ test "subscribes and stream for room" do
+ # Simulate a subscription creation by calling `subscribe`
+ subscribe room: "15"
+
+ # You can access the Channel object via `subscription` in tests
+ assert subscription.confirmed?
+ assert_has_stream "chat_15"
+ end
+end
+```
+
+This test is pretty simple and only asserts that the channel subscribes the connection to a particular stream.
+
+You can also specify the underlying connection identifiers. Here's an example test with a web notifications channel:
+
+```ruby
+require "test_helper"
+
+class WebNotificationsChannelTest < ActionCable::Channel::TestCase
+ test "subscribes and stream for user" do
+ stub_connection current_user: users(:john)
+
+ subscribe
+
+ assert_has_stream_for users(:john)
+ end
+end
+```
+
+See the API documentation for [`AcionCable::Channel::TestCase`](http://api.rubyonrails.org/classes/ActionCable/Channel/TestCase.html) for more information.
+
+### Custom Assertions And Testing Broadcasts Inside Other Components
+
+Action Cable ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActionCable::TestHelper`](http://api.rubyonrails.org/classes/ActionCable/TestHelper.html).
+
+It's a good practice to ensure that the correct message has been broadcasted inside other components (e.g. inside your controllers). This is precisely where
+the custom assertions provided by Action Cable are pretty useful. For instance,
+within a model:
+
+```ruby
+require 'test_helper'
+
+class ProductTest < ActionCable::TestCase
+ test "broadcast status after charge" do
+ assert_broadcast_on("products:#{product.id}", type: "charged") do
+ product.charge(account)
+ end
+ end
+end
+```
+
+If you want to test the broadcasting made with `Channel.broadcast_to`, you shoud use
+`Channel.broadcasting_for` to generate an underlying stream name:
+
+```ruby
+# app/jobs/chat_relay_job.rb
+class ChatRelayJob < ApplicationJob
+ def perform_later(room, message)
+ ChatChannel.broadcast_to room, text: message
+ end
+end
+
+# test/jobs/chat_relay_job_test.rb
+require 'test_helper'
+
+class ChatRelayJobTest < ActiveJob::TestCase
+ include ActionCable::TestHelper
+
+ test "broadcast message to room" do
+ room = rooms(:all)
+
+ assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do
+ ChatRelayJob.perform_now(room, "Hi!")
+ end
+ end
+end
+```
+
Additional Testing Resources
----------------------------
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 2682c6ffd7..1f30ce1971 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -97,6 +97,42 @@ If you require your cookies to be read by 5.2 and older, or you are still valida
to allow you to rollback set
`Rails.application.config.action_dispatch.use_cookies_with_metadata` to `false`.
+### ActionCable javascript API Changes
+
+The ActionCable javascript package has been converted from CoffeeScript
+to ES2015, and we now publish the source code in the npm distribution.
+
+This change includes some breaking changes to optional parts of the
+ActionCable javascript API:
+
+- Configuration of the WebSocket adapter and logger adapter have been moved
+ from properties of `ActionCable` to properties of `ActionCable.adapters`.
+ If you are currently configuring these adapters you will need to make
+ these changes when upgrading:
+
+ ```diff
+ - ActionCable.WebSocket = MyWebSocket
+ + ActionCable.adapters.WebSocket = MyWebSocket
+ ```
+ ```diff
+ - ActionCable.logger = myLogger
+ + ActionCable.adapters.logger = myLogger
+ ```
+
+- The `ActionCable.startDebugging()` and `ActionCable.stopDebugging()`
+ methods have been removed and replaced with the property
+ `ActionCable.logger.enabled`. If you are currently using these methods you
+ will need to make these changes when upgrading:
+
+ ```diff
+ - ActionCable.startDebugging()
+ + ActionCable.logger.enabled = true
+ ```
+ ```diff
+ - ActionCable.stopDebugging()
+ + ActionCable.logger.enabled = false
+ ```
+
Upgrading from Rails 5.1 to Rails 5.2
-------------------------------------
diff --git a/package.json b/package.json
index 88029de141..43055fa6bc 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,7 @@
"private": true,
"workspaces": [
"actioncable",
+ "actiontext",
"activestorage",
"actionview",
"tmp/templates/app_template",
diff --git a/rails.gemspec b/rails.gemspec
index 936f74c64b..5f2f1dfdcc 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -29,6 +29,8 @@ Gem::Specification.new do |s|
s.add_dependency "activejob", version
s.add_dependency "actioncable", version
s.add_dependency "activestorage", version
+ s.add_dependency "actionmailbox", version
+ s.add_dependency "actiontext", version
s.add_dependency "railties", version
s.add_dependency "bundler", ">= 1.3.0"
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 8e358b4293..e55217c5c4 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,66 @@
+* Fix deeply nested namespace command printing.
+
+ *Gannon McGibbon*
+
+
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* Remove deprecated `after_bundle` helper inside plugins templates.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to old `config.ru` that use the application class as argument of `run`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `environment` argument from the rails commands.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `capify!`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `config.secret_token`.
+
+ *Rafael Mendonça França*
+
+* Seed database with inline ActiveJob job adapter.
+
+ *Gannon McGibbon*
+
+* Add `rails db:system:change` command for changing databases.
+
+ ```
+ bin/rails db:system:change --to=postgresql
+ force config/database.yml
+ gsub Gemfile
+ ```
+
+ The change command copies a template `config/database.yml` with the target database adapter into your app, and replaces your database gem with the target database gem.
+
+ *Gannon McGibbon*
+
+* Add `rails test:channels`.
+
+ *bogdanvlviv*
+
+* Use original `bundler` environment variables during the process of generating a new rails project.
+
+ *Marco Costa*
+
+* Send Active Storage analysis and purge jobs to dedicated queues by default.
+
+ Analysis jobs now use the `:active_storage_analysis` queue, and purge jobs
+ now use the `:active_storage_purge` queue. This matches Action Mailbox,
+ which sends its jobs to dedicated queues by default.
+
+ *George Claghorn*
+
+* Add `rails test:mailboxes`.
+
+ *George Claghorn*
+
* Introduce guard against DNS rebinding attacks
The `ActionDispatch::HostAuthorization` is a new middleware that prevent
@@ -59,12 +122,6 @@
*Gannon McGibbon*
-* Add JSON support to rails properties route (`/rails/info/properties`).
-
- Now, `Rails::Info` properties may be accessed in JSON format at `/rails/info/properties.json`.
-
- *Yoshiyuki Hirano*
-
* Use Ids instead of memory addresses when displaying references in scaffold views.
Fixes #29200.
@@ -106,12 +163,20 @@
*Richard Schneeman*
-* Support environment specific credentials file.
+* Support environment specific credentials overrides.
+
+ So any environment will look for `config/credentials/#{Rails.env}.yml.enc` and fall back
+ to `config/credentials.yml.enc`.
+
+ The encryption key can be in `ENV["RAILS_MASTER_KEY"]` or `config/credentials/production.key`.
+
+ Environment credentials overrides can be edited with `rails credentials:edit --environment production`.
+ If no override is setup for the passed environment, it will be created.
+
+ Additionally, the default lookup paths can be overwritten with these configs:
- For `production` environment look first for `config/credentials/production.yml.enc` file that can be decrypted by
- `ENV["RAILS_MASTER_KEY"]` or `config/credentials/production.key` master key.
- Edit given environment credentials file by command `rails credentials:edit --environment production`.
- Default paths can be overwritten by setting `config.credentials.content_path` and `config.credentials.key_path`.
+ - `config.credentials.content_path`
+ - `config.credentials.key_path`
*Wojciech Wnętrzak*
diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE
index cce00cbc3a..ea8823d7ef 100644
--- a/railties/MIT-LICENSE
+++ b/railties/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2018 David Heinemeier Hansson
+Copyright (c) 2004-2019 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc
index 89fc6bcbce..1c4252edd0 100644
--- a/railties/RDOC_MAIN.rdoc
+++ b/railties/RDOC_MAIN.rdoc
@@ -44,11 +44,11 @@ or to generate the body of an email. In \Rails, View generation is handled by {A
{Active Record}[link:files/activerecord/README_rdoc.html], {Active Model}[link:files/activemodel/README_rdoc.html],
{Action Pack}[link:files/actionpack/README_rdoc.html], and {Action View}[link:files/actionview/README_rdoc.html] can each be used independently outside \Rails.
In addition to that, \Rails also comes with {Action Mailer}[link:files/actionmailer/README_rdoc.html], a library
-to generate and send emails; {Active Job}[link:files/activejob/README_md.html], a
-framework for declaring jobs and making them run on a variety of queueing
+to generate and send emails; {Action Mailbox}[link:files/actionmailbox/README_md.html], a library to receive emails within a Rails application;
+{Active Job}[link:files/activejob/README_md.html], a framework for declaring jobs and making them run on a variety of queueing
backends; {Action Cable}[link:files/actioncable/README_md.html], a framework to
integrate WebSockets with a \Rails application; {Active Storage}[link:files/activestorage/README_md.html],
-a library to attach cloud and local files to \Rails applications;
+a library to attach cloud and local files to \Rails applications; {Action Text}[link:files/actiontext/README_md.html], a library to handle rich text content;
and {Active Support}[link:files/activesupport/README_rdoc.html], a collection
of utility classes and standard library extensions that are useful for \Rails,
and may also be used independently outside \Rails.
diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb
index 7f4c625b39..da810f1eed 100644
--- a/railties/lib/rails/all.rb
+++ b/railties/lib/rails/all.rb
@@ -12,6 +12,8 @@ require "rails"
action_mailer/railtie
active_job/railtie
action_cable/engine
+ action_mailbox/engine
+ action_text/engine
rails/test_unit/railtie
sprockets/railtie
).each do |railtie|
diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb
index e7f0557584..d5312843e4 100644
--- a/railties/lib/rails/api/task.rb
+++ b/railties/lib/rails/api/task.rb
@@ -74,6 +74,22 @@ module Rails
)
},
+ "actionmailbox" => {
+ include: %w(
+ README.md
+ app/**/action_mailbox/**/*.rb
+ lib/action_mailbox/**/*.rb
+ )
+ },
+
+ "actiontext" => {
+ include: %w(
+ README.md
+ app/**/action_text/**/*.rb
+ lib/action_text/**/*.rb
+ )
+ },
+
"railties" => {
include: %w(
README.rdoc
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index acd97b64bf..5a924ab8e6 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -172,14 +172,9 @@ module Rails
def key_generator
# number of iterations selected based on consultation with the google security
# team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220
- @caching_key_generator ||=
- if secret_key_base
- ActiveSupport::CachingKeyGenerator.new(
- ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
- )
- else
- ActiveSupport::LegacyKeyGenerator.new(secrets.secret_token)
- end
+ @caching_key_generator ||= ActiveSupport::CachingKeyGenerator.new(
+ ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
+ )
end
# Returns a message verifier object.
@@ -254,7 +249,6 @@ module Rails
super.merge(
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.redirect_filter" => config.filter_redirect,
- "action_dispatch.secret_token" => secrets.secret_token,
"action_dispatch.secret_key_base" => secret_key_base,
"action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
"action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
@@ -404,14 +398,6 @@ module Rails
# Fallback to config.secret_key_base if secrets.secret_key_base isn't set
secrets.secret_key_base ||= config.secret_key_base
- # Fallback to config.secret_token if secrets.secret_token isn't set
- secrets.secret_token ||= config.secret_token
-
- if secrets.secret_token.present?
- ActiveSupport::Deprecation.warn(
- "`secrets.secret_token` is deprecated in favor of `secret_key_base` and will be removed in Rails 6.0."
- )
- end
secrets
end
@@ -587,7 +573,7 @@ module Rails
secret_key_base
elsif secret_key_base
raise ArgumentError, "`secret_key_base` for #{Rails.env} environment must be a type of String`"
- elsif secrets.secret_token.blank?
+ else
raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `rails credentials:edit`"
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 22a82c051d..b7838f7e32 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -13,7 +13,7 @@ module Rails
:cache_classes, :cache_store, :consider_all_requests_local, :console,
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :hosts, :logger, :log_formatter, :log_tags,
- :railties_order, :relative_url_root, :secret_key_base, :secret_token,
+ :railties_order, :relative_url_root, :secret_key_base,
:ssl_options, :public_file_server,
:session_options, :time_zone, :reload_classes_only_on_change,
:beginning_of_week, :filter_redirect, :x, :enable_dependency_loading,
@@ -30,7 +30,7 @@ module Rails
@filter_parameters = []
@filter_redirect = []
@helpers_paths = []
- @hosts = Array(([IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0"), "localhost"] if Rails.env.development?))
+ @hosts = Array(([IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0"), ".localhost"] if Rails.env.development?))
@public_file_server = ActiveSupport::OrderedOptions.new
@public_file_server.enabled = true
@public_file_server.index_name = "index"
@@ -50,7 +50,6 @@ module Rails
@autoflush_log = true
@log_formatter = ActiveSupport::Logger::SimpleFormatter.new
@eager_load = nil
- @secret_token = nil
@secret_key_base = nil
@api_only = false
@debug_exception_response_format = nil
@@ -97,10 +96,6 @@ module Rails
if respond_to?(:active_record)
active_record.cache_versioning = true
- # Remove the temporary load hook from SQLite3Adapter when this is removed
- ActiveSupport.on_load(:active_record_sqlite3adapter) do
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
- end
end
if respond_to?(:action_dispatch)
@@ -130,9 +125,18 @@ module Rails
action_dispatch.use_cookies_with_metadata = true
end
+ if respond_to?(:action_mailer)
+ action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
+ end
+
if respond_to?(:active_job)
active_job.return_false_on_aborted_enqueue = true
end
+
+ if respond_to?(:active_storage)
+ active_storage.queues.analysis = :active_storage_analysis
+ active_storage.queues.purge = :active_storage_purge
+ end
else
raise "Unknown version #{target_version.to_s.inspect}"
end
@@ -284,25 +288,25 @@ module Rails
end
private
- def credentials_available_for_current_env?
- File.exist?("#{root}/config/credentials/#{Rails.env}.yml.enc")
- end
-
def default_credentials_content_path
if credentials_available_for_current_env?
- File.join(root, "config", "credentials", "#{Rails.env}.yml.enc")
+ root.join("config", "credentials", "#{Rails.env}.yml.enc")
else
- File.join(root, "config", "credentials.yml.enc")
+ root.join("config", "credentials.yml.enc")
end
end
def default_credentials_key_path
if credentials_available_for_current_env?
- File.join(root, "config", "credentials", "#{Rails.env}.key")
+ root.join("config", "credentials", "#{Rails.env}.key")
else
- File.join(root, "config", "master.key")
+ root.join("config", "master.key")
end
end
+
+ def credentials_available_for_current_env?
+ File.exist?(root.join("config", "credentials", "#{Rails.env}.yml.enc"))
+ end
end
end
end
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 19d331ff30..09082282f3 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -95,8 +95,8 @@ class CodeStatistics #:nodoc:
end
def print_line(name, statistics)
- m_over_c = (statistics.methods / statistics.classes) rescue m_over_c = 0
- loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0
+ m_over_c = (statistics.methods / statistics.classes) rescue 0
+ loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue 0
print "| #{name.ljust(20)} "
HEADERS.each_key do |k|
diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb
index 766872de8a..a22b198c66 100644
--- a/railties/lib/rails/command/base.rb
+++ b/railties/lib/rails/command/base.rb
@@ -115,7 +115,7 @@ module Rails
# For a Rails::Command::TestCommand placed in <tt>rails/command/test_command.rb</tt>
# would return <tt>rails/test</tt>.
def default_command_root
- path = File.expand_path(File.join("../commands", command_root_namespace), __dir__)
+ path = File.expand_path(relative_command_path, __dir__)
path if File.exist?(path)
end
@@ -135,12 +135,20 @@ module Rails
end
def command_root_namespace
- (namespace.split(":") - %w( rails )).first
+ (namespace.split(":") - %w(rails)).join(":")
+ end
+
+ def relative_command_path
+ File.join("../commands", *command_root_namespace.split(":"))
end
def namespaced_commands
commands.keys.map do |key|
- key == command_root_namespace ? key : "#{command_root_namespace}:#{key}"
+ if command_root_namespace.match?(/(\A|\:)#{key}\z/)
+ command_root_namespace
+ else
+ "#{command_root_namespace}:#{key}"
+ end
end
end
end
diff --git a/railties/lib/rails/command/environment_argument.rb b/railties/lib/rails/command/environment_argument.rb
index 5dc98b113d..fdc5ee92d9 100644
--- a/railties/lib/rails/command/environment_argument.rb
+++ b/railties/lib/rails/command/environment_argument.rb
@@ -8,23 +8,13 @@ module Rails
extend ActiveSupport::Concern
included do
- argument :environment, optional: true, banner: "environment"
-
class_option :environment, aliases: "-e", type: :string,
desc: "Specifies the environment to run this console under (test/development/production)."
end
private
def extract_environment_option_from_argument
- if environment
- self.options = options.merge(environment: acceptable_environment(environment))
-
- ActiveSupport::Deprecation.warn "Passing the environment's name as a " \
- "regular argument is deprecated and " \
- "will be removed in the next Rails " \
- "version. Please, use the -e option " \
- "instead."
- elsif options[:environment]
+ if options[:environment]
self.options = options.merge(environment: acceptable_environment(options[:environment]))
else
self.options = options.merge(environment: Rails::Command.environment)
diff --git a/railties/lib/rails/commands/credentials/USAGE b/railties/lib/rails/commands/credentials/USAGE
index 6b33d1ab74..d235592f46 100644
--- a/railties/lib/rails/commands/credentials/USAGE
+++ b/railties/lib/rails/commands/credentials/USAGE
@@ -41,9 +41,18 @@ from leaking.
=== Environment Specific Credentials
-It is possible to have credentials for each environment. If the file for current environment exists it will take
-precedence over `config/credentials.yml.enc`, thus for `production` environment first look for
-`config/credentials/production.yml.enc` that can be decrypted using master key taken from `ENV["RAILS_MASTER_KEY"]`
-or stored in `config/credentials/production.key`.
-To edit given file use command `rails credentials:edit --environment production`
-Default paths can be overwritten by setting `config.credentials.content_path` and `config.credentials.key_path`.
+The `credentials` command supports passing an `--environment` option to create an
+environment specific override. That override will takes precedence over the
+global `config/credentials.yml.enc` file when running in that environment. So:
+
+ rails credentials:edit --environment development
+
+will create `config/credentials/development.yml.enc` with the corresponding
+encryption key in `config/credentials/development.key` if the credentials file
+doesn't exist.
+
+The encryption key can also be put in `ENV["RAILS_MASTER_KEY"]`, which takes
+precedence over the file encryption key.
+
+In addition to that, the default credentials lookup paths can be overriden through
+`config.credentials.content_path` and `config.credentials.key_path`.
diff --git a/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb
index 84e90e7a1b..54ccd97506 100644
--- a/railties/lib/rails/commands/credentials/credentials_command.rb
+++ b/railties/lib/rails/commands/credentials/credentials_command.rb
@@ -24,13 +24,11 @@ module Rails
ensure_editor_available(command: "bin/rails credentials:edit") || (return)
- encrypted = Rails.application.encrypted(content_path, key_path: key_path)
-
- ensure_encryption_key_has_been_added(key_path) if encrypted.key.nil?
- ensure_encrypted_file_has_been_added(content_path, key_path)
+ ensure_encryption_key_has_been_added if credentials.key.nil?
+ ensure_credentials_have_been_added
catch_editing_exceptions do
- change_encrypted_file_in_system_editor(content_path, key_path)
+ change_credentials_in_system_editor
end
say "File encrypted and saved."
@@ -41,36 +39,46 @@ module Rails
def show
require_application!
- encrypted = Rails.application.encrypted(content_path, key_path: key_path)
-
- say encrypted.read.presence || missing_encrypted_message(key: encrypted.key, key_path: key_path, file_path: content_path)
+ say credentials.read.presence || missing_credentials_message
end
private
- def content_path
- options[:environment] ? "config/credentials/#{options[:environment]}.yml.enc" : "config/credentials.yml.enc"
- end
-
- def key_path
- options[:environment] ? "config/credentials/#{options[:environment]}.key" : "config/master.key"
+ def credentials
+ Rails.application.encrypted(content_path, key_path: key_path)
end
-
- def ensure_encryption_key_has_been_added(key_path)
+ def ensure_encryption_key_has_been_added
encryption_key_file_generator.add_key_file(key_path)
encryption_key_file_generator.ignore_key_file(key_path)
end
- def ensure_encrypted_file_has_been_added(file_path, key_path)
- encrypted_file_generator.add_encrypted_file_silently(file_path, key_path)
+ def ensure_credentials_have_been_added
+ encrypted_file_generator.add_encrypted_file_silently(content_path, key_path)
end
- def change_encrypted_file_in_system_editor(file_path, key_path)
- Rails.application.encrypted(file_path, key_path: key_path).change do |tmp_path|
+ def change_credentials_in_system_editor
+ credentials.change do |tmp_path|
system("#{ENV["EDITOR"]} #{tmp_path}")
end
end
+ def missing_credentials_message
+ if credentials.key.nil?
+ "Missing '#{key_path}' to decrypt credentials. See `rails credentials:help`"
+ else
+ "File '#{content_path}' does not exist. Use `rails credentials:edit` to change that."
+ end
+ end
+
+
+ def content_path
+ options[:environment] ? "config/credentials/#{options[:environment]}.yml.enc" : "config/credentials.yml.enc"
+ end
+
+ def key_path
+ options[:environment] ? "config/credentials/#{options[:environment]}.key" : "config/master.key"
+ end
+
def encryption_key_file_generator
require "rails/generators"
@@ -85,14 +93,6 @@ module Rails
Rails::Generators::EncryptedFileGenerator.new
end
-
- def missing_encrypted_message(key:, key_path:, file_path:)
- if key.nil?
- "Missing '#{key_path}' to decrypt credentials. See `rails credentials:help`"
- else
- "File '#{file_path}' does not exist. Use `rails credentials:edit` to change that."
- end
- end
end
end
end
diff --git a/railties/lib/rails/commands/db/system/change/change_command.rb b/railties/lib/rails/commands/db/system/change/change_command.rb
new file mode 100644
index 0000000000..760c229c07
--- /dev/null
+++ b/railties/lib/rails/commands/db/system/change/change_command.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require "rails/generators"
+require "rails/generators/rails/db/system/change/change_generator"
+
+module Rails
+ module Command
+ module Db
+ module System
+ class ChangeCommand < Base # :nodoc:
+ class_option :to, desc: "The database system to switch to."
+
+ def perform
+ Rails::Generators::Db::System::ChangeGenerator.start
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb
index 70789e0303..47c3f05bb3 100644
--- a/railties/lib/rails/commands/server/server_command.rb
+++ b/railties/lib/rails/commands/server/server_command.rb
@@ -21,19 +21,6 @@ module Rails
set_environment
end
- def app
- @app ||= begin
- app = super
- if app.is_a?(Class)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Using `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0.
- Please change `run #{app}` to `run Rails.application` in config.ru.
- MSG
- end
- app.respond_to?(:to_app) ? app.to_app : app
- end
- end
-
def opt_parser
Options.new
end
@@ -302,9 +289,10 @@ module Rails
MSG
else
suggestion = Rails::Command::Spellchecker.suggest(server, from: RACK_SERVERS)
+ suggestion_msg = "Maybe you meant #{suggestion.inspect}?" if suggestion
<<~MSG
- Could not find server "#{server}". Maybe you meant #{suggestion.inspect}?
+ Could not find server "#{server}". #{suggestion_msg}
Run `rails server --help` for more options.
MSG
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 6a13a84108..f768c30db0 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -548,7 +548,7 @@ module Rails
# Blog::Engine.load_seed
def load_seed
seed_file = paths["db/seeds.rb"].existent.first
- load(seed_file) if seed_file
+ with_inline_jobs { load(seed_file) } if seed_file
end
# Add configured load paths to Ruby's load path, and remove duplicate entries.
@@ -658,6 +658,18 @@ module Rails
end
end
+ def with_inline_jobs
+ queue_adapter = config.active_job.queue_adapter
+ ActiveSupport.on_load(:active_job) do
+ self.queue_adapter = :inline
+ end
+ yield
+ ensure
+ ActiveSupport.on_load(:active_job) do
+ self.queue_adapter = queue_adapter
+ end
+ end
+
def has_migrations?
paths["db/migrate"].existent.any?
end
diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb
index 54bfbdd516..82df56559c 100644
--- a/railties/lib/rails/gem_version.rb
+++ b/railties/lib/rails/gem_version.rb
@@ -10,7 +10,7 @@ module Rails
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 5e8cebc50a..b835b3f3fd 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -23,6 +23,8 @@ module Rails
autoload :ActiveModel, "rails/generators/active_model"
autoload :Base, "rails/generators/base"
autoload :Migration, "rails/generators/migration"
+ autoload :Database, "rails/generators/database"
+ autoload :AppName, "rails/generators/app_name"
autoload :NamedBase, "rails/generators/named_base"
autoload :ResourceHelpers, "rails/generators/resource_helpers"
autoload :TestCase, "rails/generators/test_case"
@@ -218,6 +220,7 @@ module Rails
rails.delete("encryption_key_file")
rails.delete("master_key")
rails.delete("credentials")
+ rails.delete("db:system:change")
hidden_namespaces.each { |n| groups.delete(n.to_s) }
@@ -272,8 +275,10 @@ module Rails
else
options = sorted_groups.flat_map(&:last)
suggestion = Rails::Command::Spellchecker.suggest(namespace.to_s, from: options)
+ suggestion_msg = "Maybe you meant #{suggestion.inspect}?" if suggestion
+
puts <<~MSG
- Could not find generator '#{namespace}'. Maybe you meant #{suggestion.inspect}?
+ Could not find generator '#{namespace}'. #{suggestion_msg}
Run `rails generate --help` for more options.
MSG
end
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 4646a55316..3856a74a39 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -8,7 +8,6 @@ module Rails
def initialize(*) # :nodoc:
super
@indentation = 0
- @after_bundle_callbacks = []
end
# Adds an entry into +Gemfile+ for the supplied gem.
@@ -248,15 +247,6 @@ module Rails
execute_command :rails, command, options
end
- # Just run the capify command in root
- #
- # capify!
- def capify!
- ActiveSupport::Deprecation.warn("`capify!` is deprecated and will be removed in the next version of Rails.")
- log :capify, ""
- in_root { run("#{extify(:capify)} .", verbose: false) }
- end
-
# Make an entry in Rails routing file <tt>config/routes.rb</tt>
#
# route "root 'welcome#index'"
@@ -276,16 +266,6 @@ module Rails
log File.read(find_in_source_paths(path))
end
- # Registers a callback to be executed after bundle and spring binstubs
- # have run.
- #
- # after_bundle do
- # git add: '.'
- # end
- def after_bundle(&block)
- @after_bundle_callbacks << block
- end
-
private
# Define log for backwards compatibility. If just one argument is sent,
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index f3b99ff937..0eed552042 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -11,9 +11,8 @@ require "active_support/core_ext/array/extract_options"
module Rails
module Generators
class AppBase < Base # :nodoc:
- DATABASES = %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver )
- JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc )
- DATABASES.concat(JDBC_DATABASES)
+ include Database
+ include AppName
attr_accessor :rails_template
add_shebang_option!
@@ -44,6 +43,12 @@ module Rails
default: false,
desc: "Skip Action Mailer files"
+ class_option :skip_action_mailbox, type: :boolean, default: false,
+ desc: "Skip Action Mailbox gem"
+
+ class_option :skip_action_text, type: :boolean, default: false,
+ desc: "Skip Action Text gem"
+
class_option :skip_active_record, type: :boolean, aliases: "-O", default: false,
desc: "Skip Active Record files"
@@ -100,7 +105,6 @@ module Rails
@gem_filter = lambda { |gem| true }
@extra_entries = []
super
- convert_database_option_for_jruby
end
private
@@ -124,7 +128,7 @@ module Rails
def gemfile_entries # :doc:
[rails_gemfile_entry,
database_gemfile_entry,
- webserver_gemfile_entry,
+ web_server_gemfile_entry,
assets_gemfile_entry,
webpacker_gemfile_entry,
javascript_gemfile_entry,
@@ -185,7 +189,7 @@ module Rails
"Use #{options[:database]} as the database for Active Record"
end
- def webserver_gemfile_entry # :doc:
+ def web_server_gemfile_entry # :doc:
return [] if options[:skip_puma]
comment = "Use Puma as the app server"
GemfileEntry.new("puma", "~> 3.11", comment)
@@ -200,7 +204,9 @@ module Rails
:skip_sprockets,
:skip_action_cable
),
- skip_active_storage?
+ skip_active_storage?,
+ skip_action_mailbox?,
+ skip_action_text?
].flatten.none?
end
@@ -229,6 +235,14 @@ module Rails
options[:skip_active_storage] || options[:skip_active_record]
end
+ def skip_action_mailbox? # :doc:
+ options[:skip_action_mailbox] || skip_active_storage?
+ end
+
+ def skip_action_text? # :doc:
+ options[:skip_action_text] || skip_active_storage?
+ end
+
class GemfileEntry < Struct.new(:name, :version, :comment, :options, :commented_out)
def initialize(name, version, comment, options = {}, commented_out = false)
super
@@ -290,34 +304,6 @@ module Rails
end
end
- def gem_for_database
- # %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql )
- case options[:database]
- when "mysql" then ["mysql2", [">= 0.4.4"]]
- when "postgresql" then ["pg", [">= 0.18", "< 2.0"]]
- when "oracle" then ["activerecord-oracle_enhanced-adapter", nil]
- when "frontbase" then ["ruby-frontbase", nil]
- when "sqlserver" then ["activerecord-sqlserver-adapter", nil]
- when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil]
- when "jdbcsqlite3" then ["activerecord-jdbcsqlite3-adapter", nil]
- when "jdbcpostgresql" then ["activerecord-jdbcpostgresql-adapter", nil]
- when "jdbc" then ["activerecord-jdbc-adapter", nil]
- else [options[:database], nil]
- end
- end
-
- def convert_database_option_for_jruby
- if defined?(JRUBY_VERSION)
- opt = options.dup
- case opt[:database]
- when "postgresql" then opt[:database] = "jdbcpostgresql"
- when "mysql" then opt[:database] = "jdbcmysql"
- when "sqlite3" then opt[:database] = "jdbcsqlite3"
- end
- self.options = opt.freeze
- end
- end
-
def assets_gemfile_entry
return [] if options[:skip_sprockets]
@@ -330,7 +316,7 @@ module Rails
if options.dev? || options.edge?
GemfileEntry.github "webpacker", "rails/webpacker", nil, "Use development version of Webpacker"
else
- GemfileEntry.new "webpacker", nil, "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker"
+ GemfileEntry.version "webpacker", ">= 4.0.0.rc.3", "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker"
end
end
@@ -372,19 +358,21 @@ module Rails
# its own vendored Thor, which could be a different version. Running both
# things in the same process is a recipe for a night with paracetamol.
#
- # We unset temporary bundler variables to load proper bundler and Gemfile.
- #
# Thanks to James Tucker for the Gem tricks involved in this call.
_bundle_command = Gem.bin_path("bundler", "bundle")
require "bundler"
- Bundler.with_clean_env do
- full_command = %Q["#{Gem.ruby}" "#{_bundle_command}" #{command}]
- if options[:quiet]
- system(env, full_command, out: File::NULL)
- else
- system(env, full_command)
- end
+ Bundler.with_original_env do
+ exec_bundle_command(_bundle_command, command, env)
+ end
+ end
+
+ def exec_bundle_command(bundle_command, command, env)
+ full_command = %Q["#{Gem.ruby}" "#{bundle_command}" #{command}]
+ if options[:quiet]
+ system(env, full_command, out: File::NULL)
+ else
+ system(env, full_command)
end
end
diff --git a/railties/lib/rails/generators/app_name.rb b/railties/lib/rails/generators/app_name.rb
new file mode 100644
index 0000000000..c4f71694d8
--- /dev/null
+++ b/railties/lib/rails/generators/app_name.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Rails
+ module Generators
+ module AppName # :nodoc:
+ RESERVED_NAMES = %w(application destroy plugin runner test)
+
+ private
+ def app_name
+ @app_name ||= original_app_name.tr("-", "_")
+ end
+
+ def original_app_name
+ @original_app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr('\\', "").tr(". ", "_")
+ end
+
+ def defined_app_name
+ defined_app_const_base.underscore
+ end
+
+ def defined_app_const_base
+ Rails.respond_to?(:application) && defined?(Rails::Application) &&
+ Rails.application.is_a?(Rails::Application) && Rails.application.class.name.chomp("::Application")
+ end
+
+ alias :defined_app_const_base? :defined_app_const_base
+
+ def app_const_base
+ @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, "_").squeeze("_").camelize
+ end
+ alias :camelized :app_const_base
+
+ def app_const
+ @app_const ||= "#{app_const_base}::Application"
+ end
+
+ def valid_const?
+ if /^\d/.match?(app_const)
+ raise Error, "Invalid application name #{original_app_name}. Please give a name which does not start with numbers."
+ elsif RESERVED_NAMES.include?(original_app_name)
+ raise Error, "Invalid application name #{original_app_name}. Please give a " \
+ "name which does not match one of the reserved rails " \
+ "words: #{RESERVED_NAMES.join(", ")}"
+ elsif Object.const_defined?(app_const_base)
+ raise Error, "Invalid application name #{original_app_name}, constant #{app_const_base} is already in use. Please choose another application name."
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/database.rb b/railties/lib/rails/generators/database.rb
new file mode 100644
index 0000000000..be3e61bde8
--- /dev/null
+++ b/railties/lib/rails/generators/database.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Rails
+ module Generators
+ module Database # :nodoc:
+ JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc )
+ DATABASES = %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver ) + JDBC_DATABASES
+
+ def initialize(*)
+ super
+ convert_database_option_for_jruby
+ end
+
+ def gem_for_database(database = options[:database])
+ case database
+ when "mysql" then ["mysql2", [">= 0.4.4"]]
+ when "postgresql" then ["pg", [">= 0.18", "< 2.0"]]
+ when "oracle" then ["activerecord-oracle_enhanced-adapter", nil]
+ when "frontbase" then ["ruby-frontbase", nil]
+ when "sqlserver" then ["activerecord-sqlserver-adapter", nil]
+ when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil]
+ when "jdbcsqlite3" then ["activerecord-jdbcsqlite3-adapter", nil]
+ when "jdbcpostgresql" then ["activerecord-jdbcpostgresql-adapter", nil]
+ when "jdbc" then ["activerecord-jdbc-adapter", nil]
+ else [database, nil]
+ end
+ end
+
+ def convert_database_option_for_jruby
+ if defined?(JRUBY_VERSION)
+ opt = options.dup
+ case opt[:database]
+ when "postgresql" then opt[:database] = "jdbcpostgresql"
+ when "mysql" then opt[:database] = "jdbcmysql"
+ when "sqlite3" then opt[:database] = "jdbcsqlite3"
+ end
+ self.options = opt.freeze
+ end
+ end
+
+ private
+ def mysql_socket
+ @mysql_socket ||= [
+ "/tmp/mysql.sock", # default
+ "/var/run/mysqld/mysqld.sock", # debian/gentoo
+ "/var/tmp/mysql.sock", # freebsd
+ "/var/lib/mysql/mysql.sock", # fedora
+ "/opt/local/lib/mysql/mysql.sock", # fedora
+ "/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql
+ "/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4
+ "/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5
+ "/opt/lampp/var/mysql/mysql.sock" # xampp for linux
+ ].find { |f| File.exist?(f) } unless Gem.win_platform?
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 33002790d4..f2f46d6e25 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -213,6 +213,7 @@ module Rails
empty_directory_with_keep_file "test/helpers"
empty_directory_with_keep_file "test/integration"
+ template "test/channels/application_cable/connection_test.rb"
template "test/test_helper.rb"
end
@@ -241,7 +242,6 @@ module Rails
# We need to store the RAILS_DEV_PATH in a constant, otherwise the path
# can change in Ruby 1.8.7 when we FileUtils.cd.
RAILS_DEV_PATH = File.expand_path("../../../../../..", __dir__)
- RESERVED_NAMES = %w[application destroy plugin runner test]
class AppGenerator < AppBase # :nodoc:
WEBPACKS = %w( react vue angular elm stimulus )
@@ -268,7 +268,7 @@ module Rails
super
if !options[:skip_active_record] && !DATABASES.include?(options[:database])
- raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
+ raise Error, "Invalid value for --database option. Supported preconfigurations are: #{DATABASES.join(", ")}."
end
# Force sprockets and yarn to be skipped when generating API only apps.
@@ -276,6 +276,8 @@ module Rails
if options[:api]
self.options = options.merge(skip_sprockets: true, skip_javascript: true).freeze
end
+
+ @after_bundle_callbacks = []
end
public_task :set_default_accessors!
@@ -305,6 +307,13 @@ module Rails
end
remove_task :update_bin_files
+ def update_active_storage
+ unless skip_active_storage?
+ rails_command "active_storage:update"
+ end
+ end
+ remove_task :update_active_storage
+
def create_config_files
build(:config)
end
@@ -440,6 +449,7 @@ module Rails
if options[:skip_action_cable]
remove_dir "app/javascript/channels"
remove_dir "app/channels"
+ remove_dir "test/channels"
end
end
@@ -489,58 +499,14 @@ module Rails
create_file(*args, &block)
end
- def app_name
- @app_name ||= original_app_name.tr("-", "_")
- end
-
- def original_app_name
- @original_app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr('\\', "").tr(". ", "_")
- end
-
- def defined_app_name
- defined_app_const_base.underscore
- end
-
- def defined_app_const_base
- Rails.respond_to?(:application) && defined?(Rails::Application) &&
- Rails.application.is_a?(Rails::Application) && Rails.application.class.name.sub(/::Application$/, "")
- end
-
- alias :defined_app_const_base? :defined_app_const_base
-
- def app_const_base
- @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, "_").squeeze("_").camelize
- end
- alias :camelized :app_const_base
-
- def app_const
- @app_const ||= "#{app_const_base}::Application"
- end
-
- def valid_const?
- if /^\d/.match?(app_const)
- raise Error, "Invalid application name #{original_app_name}. Please give a name which does not start with numbers."
- elsif RESERVED_NAMES.include?(original_app_name)
- raise Error, "Invalid application name #{original_app_name}. Please give a " \
- "name which does not match one of the reserved rails " \
- "words: #{RESERVED_NAMES.join(", ")}"
- elsif Object.const_defined?(app_const_base)
- raise Error, "Invalid application name #{original_app_name}, constant #{app_const_base} is already in use. Please choose another application name."
- end
- end
-
- def mysql_socket
- @mysql_socket ||= [
- "/tmp/mysql.sock", # default
- "/var/run/mysqld/mysqld.sock", # debian/gentoo
- "/var/tmp/mysql.sock", # freebsd
- "/var/lib/mysql/mysql.sock", # fedora
- "/opt/local/lib/mysql/mysql.sock", # fedora
- "/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql
- "/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4
- "/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5
- "/opt/lampp/var/mysql/mysql.sock" # xampp for linux
- ].find { |f| File.exist?(f) } unless Gem.win_platform?
+ # Registers a callback to be executed after bundle and spring binstubs
+ # have run.
+ #
+ # after_bundle do
+ # git add: '.'
+ # end
+ def after_bundle(&block) # :doc:
+ @after_bundle_callbacks << block
end
def get_builder_class
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
index fb264935bd..d39b5d311f 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
@@ -18,17 +18,14 @@ ruby <%= "'#{RUBY_VERSION}'" -%>
<% end -%>
<% end -%>
-# Use ActiveModel has_secure_password
+# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'
<% unless skip_active_storage? -%>
-# Use ActiveStorage variant
+# Use Active Storage variant
# gem 'image_processing', '~> 1.2'
<% end -%>
-# Use Capistrano for deployment
-# gem 'capistrano-rails', group: :development
-
<% if depend_on_bootsnap? -%>
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false
diff --git a/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js b/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js
index 76ca3d0f2f..0eceb59b18 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js
+++ b/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js
@@ -1,6 +1,6 @@
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
-import ActionCable from "actioncable"
+import { createConsumer } from "@rails/actioncable"
-export default ActionCable.createConsumer()
+export default createConsumer()
diff --git a/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt
index 4d7a145cd6..908487d500 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt
@@ -3,19 +3,13 @@
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
-import Rails from "rails-ujs"
-Rails.start()
+require("@rails/ujs").start()
<%- unless options[:skip_turbolinks] -%>
-
-import Turbolinks from "turbolinks"
-Turbolinks.start()
+require("turbolinks").start()
<%- end -%>
<%- unless skip_active_storage? -%>
-
-import * as ActiveStorage from "activestorage"
-ActiveStorage.start()
+require("@rails/activestorage").start()
<%- end -%>
<%- unless options[:skip_action_cable] -%>
-
-import "channels"
+require("channels")
<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt
index 9a427113c7..1b0ee54071 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt
@@ -11,6 +11,8 @@ require "active_job/railtie"
<%= comment_if :skip_active_storage %>require "active_storage/engine"
require "action_controller/railtie"
<%= comment_if :skip_action_mailer %>require "action_mailer/railtie"
+<%= comment_if :skip_action_mailbox %>require "action_mailbox/engine"
+<%= comment_if :skip_action_text %>require "action_text/engine"
require "action_view/railtie"
<%= comment_if :skip_action_cable %>require "action_cable/engine"
<%= comment_if :skip_sprockets %>require "sprockets/railtie"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt
index 33f422c622..6ab4a26084 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt
@@ -24,7 +24,7 @@ test:
<<: *default
database: <%= app_name %>_test
-# As with config/secrets.yml, you never want to store sensitive information,
+# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt
index 681c765e93..e422aa31fc 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt
@@ -60,7 +60,7 @@ test:
<<: *default
database: <%= app_name[0,4] %>_tst
-# As with config/secrets.yml, you never want to store sensitive information,
+# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt
index af69f12059..678455c622 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt
@@ -54,7 +54,7 @@ test:
<<: *default
url: jdbc:db://localhost/<%= app_name %>_test
-# As with config/secrets.yml, you never want to store sensitive information,
+# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt
index f39593372c..b5a0efef47 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt
@@ -27,7 +27,7 @@ test:
<<: *default
database: <%= app_name %>_test
-# As with config/secrets.yml, you never want to store sensitive information,
+# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt
index 2383fe97d3..009a81a6b8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt
@@ -43,7 +43,7 @@ test:
<<: *default
database: <%= app_name %>_test
-# As with config/secrets.yml, you never want to store sensitive information,
+# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
index b6c2e7448a..386eb511e5 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
@@ -32,7 +32,7 @@ test:
<<: *default
database: <%= app_name %>_test
-# As with config/secrets.yml, you never want to store sensitive information,
+# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt
index 8d9d33ba6c..f7b6dfafab 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt
@@ -33,7 +33,7 @@ test:
<<: *default
database: <%= app_name %>_test
-# As with config/secrets.yml, you never want to store sensitive information,
+# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt
index 2f51030756..44dafbd0c0 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt
@@ -59,7 +59,7 @@ test:
<<: *default
database: <%= app_name %>_test
-# As with config/secrets.yml, you never want to store sensitive information,
+# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt
index 0246fb0d02..27e8f2f35d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt
@@ -26,7 +26,7 @@ test:
<<: *default
database: <%= app_name %>_test
-# As with config/secrets.yml, you never want to store sensitive information,
+# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index 3807c8a9aa..2887bc2d67 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -16,6 +16,7 @@ Rails.application.configure do
# Run rails dev:cache to toggle caching.
if Rails.root.join('tmp', 'caching-dev.txt').exist?
config.action_controller.perform_caching = true
+ config.action_controller.enable_fragment_cache_logging = true
config.cache_store = :memory_store
config.public_file_server.headers = {
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
index fe48fc34ee..e92382f2d9 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
@@ -5,6 +5,10 @@ Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
+<%- unless options[:skip_javascript] -%>
+# Add Yarn node_modules folder to the asset load path.
+Rails.application.config.assets.paths << Rails.root.join('node_modules')
+<%- end -%>
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_6_0.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_6_0.rb.tt
index 5cca8ae570..d25552e923 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_6_0.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_6_0.rb.tt
@@ -6,7 +6,7 @@
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
-# Don't force requests from old versions of IE to be UTF-8 encoded
+# Don't force requests from old versions of IE to be UTF-8 encoded.
# Rails.application.config.action_view.default_enforce_utf8 = false
# Embed purpose and expiry metadata inside signed and encrypted
@@ -16,5 +16,18 @@
# It's best enabled when your entire app is migrated and stable on 6.0.
# Rails.application.config.action_dispatch.use_cookies_with_metadata = true
-# Return false instead of self when #enqueue method was aborted from the callback
-Rails.application.config.active_job.return_false_on_aborted_enqueue = true
+# Return false instead of self when enqueuing is aborted from a callback.
+# Rails.application.config.active_job.return_false_on_aborted_enqueue = true
+
+# Send Active Storage analysis and purge jobs to dedicated queues.
+# Rails.application.config.active_storage.queues.analysis = :active_storage_analysis
+# Rails.application.config.active_storage.queues.purge = :active_storage_purge
+
+# Use ActionMailer::MailDeliveryJob for sending parameterized and normal mail.
+#
+# The default delivery jobs (ActionMailer::Parameterized::DeliveryJob, ActionMailer::DeliveryJob),
+# will be removed in Rails 6.1. This setting is not backwards compatible with earlier Rails versions.
+# If you send mail in the background, job workers need to have a copy of
+# MailDeliveryJob to ensure all delivery jobs are processed properly.
+# Make sure your entire app is migrated and stable on 6.0 before using this setting.
+# Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
index f6146e7259..649253aeca 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
@@ -17,7 +17,7 @@ port ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
# Specifies the number of `workers` to boot in clustered mode.
-# Workers are forked webserver processes. If using threads and workers together
+# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
diff --git a/railties/lib/rails/generators/rails/app/templates/package.json.tt b/railties/lib/rails/generators/rails/app/templates/package.json.tt
index 7174116989..07207e1747 100644
--- a/railties/lib/rails/generators/rails/app/templates/package.json.tt
+++ b/railties/lib/rails/generators/rails/app/templates/package.json.tt
@@ -2,10 +2,10 @@
"name": "<%= app_name %>",
"private": true,
"dependencies": {
- "rails-ujs": ">=5.2.1"<% unless options[:skip_turbolinks] %>,
- "turbolinks": "5.1.1"<% end -%><% unless skip_active_storage? %>,
- "activestorage": ">=5.2.1"<% end -%><% unless options[:skip_action_cable] %>,
- "actioncable": ">=5.2.1"<% end %>
+ "@rails/ujs": "^6.0.0-alpha"<% unless options[:skip_turbolinks] %>,
+ "turbolinks": "^5.2.0"<% end -%><% unless skip_active_storage? %>,
+ "@rails/activestorage": "^6.0.0-alpha"<% end -%><% unless options[:skip_action_cable] %>,
+ "@rails/actioncable": "^6.0.0-alpha"<% end %>
},
"version": "0.1.0"
}
diff --git a/railties/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt
new file mode 100644
index 0000000000..800405f15e
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt
@@ -0,0 +1,11 @@
+require "test_helper"
+
+class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
+ # test "connects with cookies" do
+ # cookies.signed[:user_id] = 42
+ #
+ # connect
+ #
+ # assert_equal connection.user_id, "42"
+ # end
+end
diff --git a/railties/lib/rails/generators/rails/db/system/change/change_generator.rb b/railties/lib/rails/generators/rails/db/system/change/change_generator.rb
new file mode 100644
index 0000000000..63849eb18d
--- /dev/null
+++ b/railties/lib/rails/generators/rails/db/system/change/change_generator.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require "rails/generators/base"
+
+module Rails
+ module Generators
+ module Db
+ module System
+ class ChangeGenerator < Base # :nodoc:
+ include Database
+ include AppName
+
+ class_option :to, required: true,
+ desc: "The database system to switch to."
+
+ def self.default_generator_root
+ path = File.expand_path(File.join(base_name, "app"), base_root)
+ path if File.exist?(path)
+ end
+
+ def initialize(*)
+ super
+
+ unless DATABASES.include?(options[:to])
+ raise Error, "Invalid value for --to option. Supported preconfigurations are: #{DATABASES.join(", ")}."
+ end
+
+ opt = options.dup
+ opt[:database] ||= opt[:to]
+ self.options = opt.freeze
+ end
+
+ def edit_database_config
+ template("config/databases/#{options[:database]}.yml", "config/database.yml")
+ end
+
+ def edit_gemfile
+ database_gem_name, _ = gem_for_database
+ gsub_file("Gemfile", all_database_gems_regex, database_gem_name)
+ end
+
+ private
+ def all_database_gems
+ DATABASES.map { |database| gem_for_database(database) }
+ end
+
+ def all_database_gems_regex
+ all_database_gem_names = all_database_gems.map(&:first)
+ /(\b#{all_database_gem_names.join('\b|\b')}\b)/
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 239b3a5739..79a06648b5 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -263,16 +263,6 @@ task default: :test
public_task :apply_rails_template
- def run_after_bundle_callbacks
- unless @after_bundle_callbacks.empty?
- ActiveSupport::Deprecation.warn("`after_bundle` is deprecated and will be removed in the next version of Rails. ")
- end
-
- @after_bundle_callbacks.each do |callback|
- callback.call
- end
- end
-
def name
@name ||= begin
# same as ActiveSupport::Inflector#underscore except not replacing '-'
@@ -349,9 +339,9 @@ task default: :test
def wrap_in_modules(unwrapped_code)
unwrapped_code = "#{unwrapped_code}".strip.gsub(/\s$\n/, "")
modules.reverse.inject(unwrapped_code) do |content, mod|
- str = "module #{mod}\n"
- str += content.lines.map { |line| " #{line}" }.join
- str += content.present? ? "\nend" : "end"
+ str = +"module #{mod}\n"
+ str << content.lines.map { |line| " #{line}" }.join
+ str << (content.present? ? "\nend" : "end")
end
end
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index c68405619d..72b555ec19 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -54,10 +54,6 @@ module Rails
table << "</table>"
end
end
-
- def to_json
- Hash[properties].to_json
- end
end
# The Rails version.
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 14459623ac..f74d979721 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -14,16 +14,8 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
end
def properties
- respond_to do |format|
- format.html do
- @info = Rails::Info.to_html
- @page_title = "Properties"
- end
-
- format.json do
- render json: Rails::Info.to_json
- end
- end
+ @info = Rails::Info.to_html
+ @page_title = "Properties"
end
def routes
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 1a3711c446..2886986865 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -2,7 +2,7 @@
namespace :app do
desc "Update configs and some other initially generated files (or use just update:configs or update:bin)"
- task update: [ "update:configs", "update:bin", "update:upgrade_guide_info" ]
+ task update: [ "update:configs", "update:bin", "update:active_storage", "update:upgrade_guide_info" ]
desc "Applies the template supplied by LOCATION=(/path/to/template) or URL"
task template: :environment do
@@ -51,6 +51,10 @@ namespace :app do
Rails::AppUpdater.invoke_from_app_generator :update_bin_files
end
+ task :active_storage do
+ Rails::AppUpdater.invoke_from_app_generator :update_active_storage
+ end
+
task :upgrade_guide_info do
Rails::AppUpdater.invoke_from_app_generator :display_upgrade_guide_info
end
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index 8cacf4a49f..5abba7d3b4 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -9,6 +9,7 @@ STATS_DIRECTORIES = [
%w(Jobs app/jobs),
%w(Models app/models),
%w(Mailers app/mailers),
+ %w(Mailboxes app/mailboxes),
%w(Channels app/channels),
%w(JavaScripts app/assets/javascripts),
%w(JavaScript app/javascript),
@@ -18,6 +19,8 @@ STATS_DIRECTORIES = [
%w(Helper\ tests test/helpers),
%w(Model\ tests test/models),
%w(Mailer\ tests test/mailers),
+ %w(Mailbox\ tests test/mailboxes),
+ %w(Channel\ tests test/channels),
%w(Job\ tests test/jobs),
%w(Integration\ tests test/integration),
%w(System\ tests test/system),
diff --git a/railties/lib/rails/tasks/yarn.rake b/railties/lib/rails/tasks/yarn.rake
index 4fb8586b69..48a8d8e143 100644
--- a/railties/lib/rails/tasks/yarn.rake
+++ b/railties/lib/rails/tasks/yarn.rake
@@ -6,8 +6,7 @@ namespace :yarn do
# Install only production deps when for not usual envs.
valid_node_envs = %w[test development production]
node_env = ENV.fetch("NODE_ENV") do
- rails_env = ENV["RAILS_ENV"]
- valid_node_envs.include?(rails_env) ? rails_env : "production"
+ valid_node_envs.include?(Rails.env) ? Rails.env : "production"
end
system({ "NODE_ENV" => node_env }, "#{Rails.root}/bin/yarn install --no-progress --frozen-lockfile")
end
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 32ac27a135..3a1b62d9d1 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -28,7 +28,7 @@ namespace :test do
desc "Run tests quickly, but also reset db"
task db: %w[db:test:prepare test]
- ["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name|
+ ["models", "helpers", "channels", "controllers", "mailers", "integration", "jobs", "mailboxes"].each do |name|
task name => "test:prepare" do
$: << "test"
Rails::TestUnit::Runner.rake_run(["test/#{name}"])
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 886fb0f843..9da3956dda 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -331,7 +331,7 @@ module ApplicationTests
assert_not_includes Post.instance_methods, :title
end
- test "eager loads attribute methods in production" do
+ test "does not eager load attribute methods in production when the schema cache is empty" do
app_file "app/models/post.rb", <<-RUBY
class Post < ActiveRecord::Base
end
@@ -354,9 +354,71 @@ module ApplicationTests
app "production"
+ assert_not_includes Post.instance_methods, :title
+ end
+
+ test "eager loads attribute methods in production when the schema cache is populated" do
+ app_file "app/models/post.rb", <<-RUBY
+ class Post < ActiveRecord::Base
+ end
+ RUBY
+
+ app_file "config/initializers/active_record.rb", <<-RUBY
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
+ ActiveRecord::Migration.verbose = false
+ ActiveRecord::Schema.define(version: 1) do
+ create_table :posts do |t|
+ t.string :title
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ config.eager_load = true
+ config.cache_classes = true
+ RUBY
+
+ app_file "config/initializers/schema_cache.rb", <<-RUBY
+ ActiveRecord::Base.connection.schema_cache.add("posts")
+ RUBY
+
+ app "production"
+
assert_includes Post.instance_methods, :title
end
+ test "does not attempt to eager load attribute methods for models that aren't connected" do
+ app_file "app/models/post.rb", <<-RUBY
+ class Post < ActiveRecord::Base
+ end
+ RUBY
+
+ app_file "config/initializers/active_record.rb", <<-RUBY
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
+ ActiveRecord::Migration.verbose = false
+ ActiveRecord::Schema.define(version: 1) do
+ create_table :posts do |t|
+ t.string :title
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ config.eager_load = true
+ config.cache_classes = true
+ RUBY
+
+ app_file "app/models/comment.rb", <<-RUBY
+ class Comment < ActiveRecord::Base
+ establish_connection(adapter: "mysql2", database: "does_not_exist")
+ end
+ RUBY
+
+ assert_nothing_raised do
+ app "production"
+ end
+ end
+
test "initialize an eager loaded, cache classes app" do
add_to_config <<-RUBY
config.eager_load = true
@@ -534,45 +596,6 @@ module ApplicationTests
assert_equal "some_value", verifier.verify(message)
end
- test "application message verifier can be used when the key_generator is ActiveSupport::LegacyKeyGenerator" do
- app_file "config/initializers/secret_token.rb", <<-RUBY
- Rails.application.credentials.secret_key_base = nil
- Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
- RUBY
-
- app "production"
-
- assert_kind_of ActiveSupport::LegacyKeyGenerator, Rails.application.key_generator
- message = app.message_verifier(:sensitive_value).generate("some_value")
- assert_equal "some_value", Rails.application.message_verifier(:sensitive_value).verify(message)
- end
-
- test "config.secret_token is deprecated" do
- app_file "config/initializers/secret_token.rb", <<-RUBY
- Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
- RUBY
-
- app "production"
-
- assert_deprecated(/secret_token/) do
- app.secrets
- end
- end
-
- test "secrets.secret_token is deprecated" do
- app_file "config/secrets.yml", <<-YAML
- production:
- secret_token: "b3c631c314c0bbca50c1b2843150fe33"
- YAML
-
- app "production"
-
- assert_deprecated(/secret_token/) do
- app.secrets
- end
- end
-
-
test "raises when secret_key_base is blank" do
app_file "config/initializers/secret_token.rb", <<-RUBY
Rails.application.credentials.secret_key_base = nil
@@ -594,20 +617,6 @@ module ApplicationTests
end
end
- test "prefer secrets.secret_token over config.secret_token" do
- app_file "config/initializers/secret_token.rb", <<-RUBY
- Rails.application.config.secret_token = ""
- RUBY
- app_file "config/secrets.yml", <<-YAML
- development:
- secret_token: 3b7cd727ee24e8444053437c36cc66c3
- YAML
-
- app "development"
-
- assert_equal "3b7cd727ee24e8444053437c36cc66c3", app.secrets.secret_token
- end
-
test "application verifier can build different verifiers" do
make_basic_app do |application|
application.credentials.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33"
@@ -649,22 +658,6 @@ module ApplicationTests
assert_equal "3b7cd727ee24e8444053437c36cc66c3", app.secrets.secret_key_base
end
- test "config.secret_token over-writes a blank secrets.secret_token" do
- app_file "config/initializers/secret_token.rb", <<-RUBY
- Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
- RUBY
- app_file "config/secrets.yml", <<-YAML
- development:
- secret_key_base:
- secret_token:
- YAML
-
- app "development"
-
- assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.secrets.secret_token
- assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token
- end
-
test "custom secrets saved in config/secrets.yml are loaded in app secrets" do
app_file "config/secrets.yml", <<-YAML
development:
@@ -727,19 +720,6 @@ module ApplicationTests
assert_equal "iaminallyoursecretkeybase", app.secrets.secret_key_base
end
- test "uses ActiveSupport::LegacyKeyGenerator as app.key_generator when secrets.secret_key_base is blank" do
- app_file "config/initializers/secret_token.rb", <<-RUBY
- Rails.application.credentials.secret_key_base = nil
- Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
- RUBY
-
- app "production"
-
- assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token
- assert_nil app.credentials.secret_key_base
- assert_kind_of ActiveSupport::LegacyKeyGenerator, app.key_generator
- end
-
test "that nested keys are symbolized the same as parents for hashes more than one level deep" do
app_file "config/secrets.yml", <<-YAML
development:
@@ -1879,37 +1859,29 @@ module ApplicationTests
assert_equal({}, Rails.application.config.my_custom_config)
end
- test "default SQLite3Adapter.represent_boolean_as_integer for 5.1 is false" do
+ test "represent_boolean_as_integer is deprecated" do
remove_from_config '.*config\.load_defaults.*\n'
- app_file "app/models/post.rb", <<-RUBY
- class Post < ActiveRecord::Base
- end
+ app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY
+ Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
RUBY
- app "development"
- force_lazy_load_hooks { Post }
-
- assert_not ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer
- end
-
- test "default SQLite3Adapter.represent_boolean_as_integer for new installs is true" do
app_file "app/models/post.rb", <<-RUBY
class Post < ActiveRecord::Base
end
RUBY
app "development"
- force_lazy_load_hooks { Post }
-
- assert ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer
+ assert_deprecated do
+ force_lazy_load_hooks { Post }
+ end
end
- test "represent_boolean_as_integer should be able to set via config.active_record.sqlite3.represent_boolean_as_integer" do
+ test "represent_boolean_as_integer raises when the value is false" do
remove_from_config '.*config\.load_defaults.*\n'
app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY
- Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
+ Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = false
RUBY
app_file "app/models/post.rb", <<-RUBY
@@ -1918,9 +1890,9 @@ module ApplicationTests
RUBY
app "development"
- force_lazy_load_hooks { Post }
-
- assert ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer
+ assert_raises(RuntimeError) do
+ force_lazy_load_hooks { Post }
+ end
end
test "config_for containing ERB tags should evaluate" do
@@ -2164,6 +2136,132 @@ module ApplicationTests
assert_equal true, ActiveJob::Base.return_false_on_aborted_enqueue
end
+ test "ActiveStorage.queues[:analysis] is :active_storage_analysis by default" do
+ app "development"
+
+ assert_equal :active_storage_analysis, ActiveStorage.queues[:analysis]
+ end
+
+ test "ActiveStorage.queues[:analysis] is nil without Rails 6 defaults" do
+ remove_from_config '.*config\.load_defaults.*\n'
+
+ app "development"
+
+ assert_nil ActiveStorage.queues[:analysis]
+ end
+
+ test "ActiveStorage.queues[:purge] is :active_storage_purge by default" do
+ app "development"
+
+ assert_equal :active_storage_purge, ActiveStorage.queues[:purge]
+ end
+
+ test "ActiveStorage.queues[:purge] is nil without Rails 6 defaults" do
+ remove_from_config '.*config\.load_defaults.*\n'
+
+ app "development"
+
+ assert_nil ActiveStorage.queues[:purge]
+ end
+
+ test "ActionMailbox.logger is Rails.logger by default" do
+ app "development"
+
+ assert_equal Rails.logger, ActionMailbox.logger
+ end
+
+ test "ActionMailbox.logger can be configured" do
+ app_file "lib/my_logger.rb", <<-RUBY
+ require "logger"
+ class MyLogger < ::Logger
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ require "my_logger"
+ config.action_mailbox.logger = MyLogger.new(STDOUT)
+ RUBY
+
+ app "development"
+
+ assert_equal "MyLogger", ActionMailbox.logger.class.name
+ end
+
+ test "ActionMailbox.incinerate_after is 30.days by default" do
+ app "development"
+
+ assert_equal 30.days, ActionMailbox.incinerate_after
+ end
+
+ test "ActionMailbox.incinerate_after can be configured" do
+ add_to_config <<-RUBY
+ config.action_mailbox.incinerate_after = 14.days
+ RUBY
+
+ app "development"
+
+ assert_equal 14.days, ActionMailbox.incinerate_after
+ end
+
+ test "ActionMailbox.queues[:incineration] is :action_mailbox_incineration by default" do
+ app "development"
+
+ assert_equal :action_mailbox_incineration, ActionMailbox.queues[:incineration]
+ end
+
+ test "ActionMailbox.queues[:incineration] can be configured" do
+ add_to_config <<-RUBY
+ config.action_mailbox.queues.incineration = :another_queue
+ RUBY
+
+ app "development"
+
+ assert_equal :another_queue, ActionMailbox.queues[:incineration]
+ end
+
+ test "ActionMailbox.queues[:routing] is :action_mailbox_routing by default" do
+ app "development"
+
+ assert_equal :action_mailbox_routing, ActionMailbox.queues[:routing]
+ end
+
+ test "ActionMailbox.queues[:routing] can be configured" do
+ add_to_config <<-RUBY
+ config.action_mailbox.queues.routing = :another_queue
+ RUBY
+
+ app "development"
+
+ assert_equal :another_queue, ActionMailbox.queues[:routing]
+ end
+
+ test "ActionMailer::Base.delivery_job is ActionMailer::MailDeliveryJob by default" do
+ app "development"
+
+ assert_equal ActionMailer::MailDeliveryJob, ActionMailer::Base.delivery_job
+ end
+
+ test "ActionMailer::Base.delivery_job is ActionMailer::DeliveryJob in the 5.x defaults" do
+ remove_from_config '.*config\.load_defaults.*\n'
+ add_to_config 'config.load_defaults "5.2"'
+
+ app "development"
+
+ assert_equal ActionMailer::DeliveryJob, ActionMailer::Base.delivery_job
+ end
+
+ test "ActionMailer::Base.delivery_job can be configured in the new framework defaults" do
+ remove_from_config '.*config\.load_defaults.*\n'
+
+ app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY
+ Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
+ RUBY
+
+ app "development"
+
+ assert_equal ActionMailer::MailDeliveryJob, ActionMailer::Base.delivery_job
+ end
+
test "ActiveRecord::Base.filter_attributes should equal to filter_parameters" do
app_file "config/initializers/filter_parameters_logging.rb", <<-RUBY
Rails.application.config.filter_parameters += [ :password, :credit_card_number ]
@@ -2191,6 +2289,11 @@ module ApplicationTests
MESSAGE
end
+ test "the host whitelist includes .localhost in development" do
+ app "development"
+ assert_includes Rails.application.config.hosts, ".localhost"
+ end
+
private
def force_lazy_load_hooks
yield # Tasty clarifying sugar, homie! We only need to reference a constant to load it.
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index e74daccbe7..b6270525f0 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -149,7 +149,7 @@ class FullStackConsoleTest < ActiveSupport::TestCase
end
def test_environment_option_and_irb_option
- spawn_console("test -- --verbose")
+ spawn_console("-e test -- --verbose")
write_prompt "a = 1", "a = 1"
write_prompt "puts Rails.env", "puts Rails.env\r\ntest"
diff --git a/railties/test/application/credentials_test.rb b/railties/test/application/credentials_test.rb
new file mode 100644
index 0000000000..2f6b109b50
--- /dev/null
+++ b/railties/test/application/credentials_test.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require "isolation/abstract_unit"
+require "env_helpers"
+
+class Rails::CredentialsTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation, EnvHelpers
+
+ setup :build_app
+ teardown :teardown_app
+
+ test "reads credentials from environment specific path" do
+ write_credentials_override(:production)
+
+ app("production")
+
+ assert_equal "revealed", Rails.application.credentials.mystery
+ end
+
+ test "reads credentials from customized path and key" do
+ write_credentials_override(:staging)
+ add_to_env_config("production", "config.credentials.content_path = config.root.join('config/credentials/staging.yml.enc')")
+ add_to_env_config("production", "config.credentials.key_path = config.root.join('config/credentials/staging.key')")
+
+ app("production")
+
+ assert_equal "revealed", Rails.application.credentials.mystery
+ end
+
+ test "reads credentials using environment variable key" do
+ write_credentials_override(:production, with_key: false)
+
+ switch_env("RAILS_MASTER_KEY", credentials_key) do
+ app("production")
+
+ assert_equal "revealed", Rails.application.credentials.mystery
+ end
+ end
+
+ private
+ def write_credentials_override(name, with_key: true)
+ Dir.chdir(app_path) do
+ Dir.mkdir "config/credentials"
+ File.write "config/credentials/#{name}.key", credentials_key if with_key
+
+ # secret_key_base: secret
+ # mystery: revealed
+ File.write "config/credentials/#{name}.yml.enc",
+ "vgvKu4MBepIgZ5VHQMMPwnQNsLlWD9LKmJHu3UA/8yj6x+3fNhz3DwL9brX7UA==--qLdxHP6e34xeTAiI--nrcAsleXuo9NqiEuhntAhw=="
+ end
+ end
+
+ def credentials_key
+ "2117e775dc2024d4f49ddf3aeb585919"
+ end
+end
diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb
index 83cf8a27f7..515b32080e 100644
--- a/railties/test/application/middleware/remote_ip_test.rb
+++ b/railties/test/application/middleware/remote_ip_test.rb
@@ -12,7 +12,9 @@ module ApplicationTests
remote_ip = nil
env = Rack::MockRequest.env_for("/").merge(env).merge!(
"action_dispatch.show_exceptions" => false,
- "action_dispatch.key_generator" => ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
+ "action_dispatch.key_generator" => ActiveSupport::CachingKeyGenerator.new(
+ ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 1000)
+ )
)
endpoint = Proc.new do |e|
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index b25e56b625..479615c133 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -215,8 +215,6 @@ module ApplicationTests
RUBY
add_to_config <<-RUBY
- secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
-
# Enable AEAD cookies
config.action_dispatch.use_authenticated_cookie_encryption = true
RUBY
@@ -238,68 +236,6 @@ module ApplicationTests
assert_equal 1, encryptor.decrypt_and_verify(last_response.body, purpose: "cookie._myapp_session")["foo"]
end
- test "session upgrading signature to encryption cookie store upgrades session to encrypted mode" do
- app_file "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- get ':controller(/:action)'
- end
- RUBY
-
- controller :foo, <<-RUBY
- class FooController < ActionController::Base
- def write_raw_session
- # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1}
- cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749"
- head :ok
- end
-
- def write_session
- session[:foo] = session[:foo] + 1
- head :ok
- end
-
- def read_session
- render plain: session[:foo]
- end
-
- def read_encrypted_cookie
- render plain: cookies.encrypted[:_myapp_session]['foo']
- end
-
- def read_raw_cookie
- render plain: cookies[:_myapp_session]
- end
- end
- RUBY
-
- add_to_config <<-RUBY
- secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
-
- # Enable AEAD cookies
- config.action_dispatch.use_authenticated_cookie_encryption = true
- RUBY
-
- require "#{app_path}/config/environment"
-
- get "/foo/write_raw_session"
- get "/foo/read_session"
- assert_equal "1", last_response.body
-
- get "/foo/write_session"
- get "/foo/read_session"
- assert_equal "2", last_response.body
-
- get "/foo/read_encrypted_cookie"
- assert_equal "2", last_response.body
-
- cipher = "aes-256-gcm"
- secret = app.key_generator.generate_key("authenticated encrypted cookie")
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
-
- get "/foo/read_raw_cookie"
- assert_equal 2, encryptor.decrypt_and_verify(last_response.body, purpose: "cookie._myapp_session")["foo"]
- end
-
test "session upgrading from AES-CBC-HMAC encryption to AES-GCM encryption" do
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
@@ -370,70 +306,6 @@ module ApplicationTests
end
end
- test "session upgrading legacy signed cookies to new signed cookies" do
- app_file "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- get ':controller(/:action)'
- end
- RUBY
-
- controller :foo, <<-RUBY
- class FooController < ActionController::Base
- def write_raw_session
- # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1}
- cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749"
- head :ok
- end
-
- def write_session
- session[:foo] = session[:foo] + 1
- head :ok
- end
-
- def read_session
- render plain: session[:foo]
- end
-
- def read_signed_cookie
- render plain: cookies.signed[:_myapp_session]['foo']
- end
-
- def read_raw_cookie
- render plain: cookies[:_myapp_session]
- end
- end
- RUBY
-
- add_to_config <<-RUBY
- secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
- Rails.application.credentials.secret_key_base = nil
- RUBY
-
- begin
- old_rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "production"
-
- require "#{app_path}/config/environment"
-
- get "/foo/write_raw_session"
- get "/foo/read_session"
- assert_equal "1", last_response.body
-
- get "/foo/write_session"
- get "/foo/read_session"
- assert_equal "2", last_response.body
-
- get "/foo/read_signed_cookie"
- assert_equal "2", last_response.body
-
- verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token)
-
- get "/foo/read_raw_cookie"
- assert_equal 2, verifier.verify(last_response.body, purpose: "cookie._myapp_session")["foo"]
- ensure
- ENV["RAILS_ENV"] = old_rails_env
- end
- end
-
test "calling reset_session on request does not trigger an error for API apps" do
add_to_config "config.api_only = true"
diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb
index d6c81c1fe2..432344bccc 100644
--- a/railties/test/application/multiple_applications_test.rb
+++ b/railties/test/application/multiple_applications_test.rb
@@ -165,12 +165,12 @@ module ApplicationTests
app.config.some_setting = "a_different_setting"
assert_equal "a_different_setting", app.config.some_setting, "The configuration's some_setting should be set."
- new_config = Rails::Application::Configuration.new("root_of_application")
+ new_config = Rails::Application::Configuration.new(Pathname.new("root_of_application"))
new_config.some_setting = "some_setting_dude"
app.config = new_config
assert_equal "some_setting_dude", app.config.some_setting, "The configuration's some_setting should have changed."
- assert_equal "root_of_application", app.config.root, "The root should have changed to the new config's root."
+ assert_equal "root_of_application", app.config.root.to_s, "The root should have changed to the new config's root."
assert_equal new_config, app.config, "The application's config should have changed to the new config."
end
end
diff --git a/railties/test/application/rake/routes_test.rb b/railties/test/application/rake/routes_test.rb
index 2c23ff4679..9879d1f047 100644
--- a/railties/test/application/rake/routes_test.rb
+++ b/railties/test/application/rake/routes_test.rb
@@ -6,6 +6,7 @@ module ApplicationTests
module RakeTests
class RakeRoutesTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
+
setup :build_app
teardown :teardown_app
@@ -17,13 +18,28 @@ module ApplicationTests
RUBY
assert_equal <<~MESSAGE, run_rake_routes
- Prefix Verb URI Pattern Controller#Action
- cart GET /cart(.:format) cart#show
- rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
-rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
- rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
-update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
- rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
+ Prefix Verb URI Pattern Controller#Action
+ cart GET /cart(.:format) cart#show
+ rails_amazon_inbound_emails POST /rails/action_mailbox/amazon/inbound_emails(.:format) action_mailbox/ingresses/amazon/inbound_emails#create
+ rails_mandrill_inbound_emails POST /rails/action_mailbox/mandrill/inbound_emails(.:format) action_mailbox/ingresses/mandrill/inbound_emails#create
+ rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create
+ rails_relay_inbound_emails POST /rails/action_mailbox/relay/inbound_emails(.:format) action_mailbox/ingresses/relay/inbound_emails#create
+ rails_sendgrid_inbound_emails POST /rails/action_mailbox/sendgrid/inbound_emails(.:format) action_mailbox/ingresses/sendgrid/inbound_emails#create
+ rails_mailgun_inbound_emails POST /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) action_mailbox/ingresses/mailgun/inbound_emails#create
+ rails_conductor_inbound_emails GET /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#index
+ POST /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#create
+ new_rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/new(.:format) rails/conductor/action_mailbox/inbound_emails#new
+ edit_rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id/edit(.:format) rails/conductor/action_mailbox/inbound_emails#edit
+ rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#show
+ PATCH /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update
+ PUT /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update
+ DELETE /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#destroy
+ rails_conductor_inbound_email_reroute POST /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format) rails/conductor/action_mailbox/reroutes#create
+ rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
+ rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
+ rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
+ update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
+ rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
MESSAGE
end
diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb
index ab9e910aed..f4bd09903a 100644
--- a/railties/test/application/server_test.rb
+++ b/railties/test/application/server_test.rb
@@ -18,20 +18,6 @@ module ApplicationTests
teardown_app
end
- test "deprecate support of older `config.ru`" do
- remove_file "config.ru"
- app_file "config.ru", <<-RUBY
- require_relative 'config/environment'
- run AppTemplate::Application
- RUBY
-
- server = Rails::Server.new(config: "#{app_path}/config.ru")
- server.app
-
- log = File.read(Rails.application.config.paths["log"].first)
- assert_match(/DEPRECATION WARNING: Using `Rails::Application` subclass to start the server is deprecated/, log)
- end
-
test "restart rails server with custom pid file path" do
skip "PTY unavailable" unless available_pty?
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 55ce72181f..fda6df500d 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -98,6 +98,17 @@ module ApplicationTests
end
end
+ def test_run_channels
+ create_test_file :channels, "foo_channel"
+ create_test_file :channels, "bar_channel"
+
+ rails("test:channels").tap do |output|
+ assert_match "FooChannelTest", output
+ assert_match "BarChannelTest", output
+ assert_match "2 runs, 2 assertions, 0 failures", output
+ end
+ end
+
def test_run_controllers
create_test_file :controllers, "foo_controller"
create_test_file :controllers, "bar_controller"
@@ -131,6 +142,18 @@ module ApplicationTests
end
end
+ def test_run_mailboxes
+ create_test_file :mailboxes, "foo_mailbox"
+ create_test_file :mailboxes, "bar_mailbox"
+ create_test_file :models, "foo"
+
+ rails("test:mailboxes").tap do |output|
+ assert_match "FooMailboxTest", output
+ assert_match "BarMailboxTest", output
+ assert_match "2 runs, 2 assertions, 0 failures", output
+ end
+ end
+
def test_run_functionals
create_test_file :mailers, "foo_mailer"
create_test_file :controllers, "bar_controller"
@@ -155,11 +178,11 @@ module ApplicationTests
end
def test_run_all_suites
- suites = [:models, :helpers, :unit, :controllers, :mailers, :functional, :integration, :jobs]
+ suites = [:models, :helpers, :unit, :channels, :controllers, :mailers, :functional, :integration, :jobs, :mailboxes]
suites.each { |suite| create_test_file suite, "foo_#{suite}" }
run_test_command("") .tap do |output|
suites.each { |suite| assert_match "Foo#{suite.to_s.camelize}Test", output }
- assert_match "8 runs, 8 assertions, 0 failures", output
+ assert_match "10 runs, 10 assertions, 0 failures", output
end
end
diff --git a/railties/test/command/base_test.rb b/railties/test/command/base_test.rb
index a49ae8aae7..9132c8b4af 100644
--- a/railties/test/command/base_test.rb
+++ b/railties/test/command/base_test.rb
@@ -4,10 +4,12 @@ require "abstract_unit"
require "rails/command"
require "rails/commands/generate/generate_command"
require "rails/commands/secrets/secrets_command"
+require "rails/commands/db/system/change/change_command"
class Rails::Command::BaseTest < ActiveSupport::TestCase
test "printing commands" do
assert_equal %w(generate), Rails::Command::GenerateCommand.printing_commands
assert_equal %w(secrets:setup secrets:edit secrets:show), Rails::Command::SecretsCommand.printing_commands
+ assert_equal %w(db:system:change), Rails::Command::Db::System::ChangeCommand.printing_commands
end
end
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index 0b2fe204f8..1941c83d6d 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -94,28 +94,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert_match(/\sspecial-production\s/, output)
end
- def test_rails_env_is_production_when_first_argument_is_p
- assert_deprecated do
- start ["p"]
- assert_match(/\sproduction\s/, output)
- end
- end
-
- def test_rails_env_is_test_when_first_argument_is_t
- assert_deprecated do
- start ["t"]
- assert_match(/\stest\s/, output)
- end
- end
-
- def test_rails_env_is_development_when_argument_is_d
- assert_deprecated do
- start ["d"]
- assert_match(/\sdevelopment\s/, output)
- end
- end
-
- def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
+ def test_rails_env_is_dev_when_environment_option_is_dev_and_dev_env_is_present
Rails::Command::ConsoleCommand.class_eval do
alias_method :old_environments, :available_environments
@@ -124,9 +103,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
end
end
- assert_deprecated do
- assert_match("dev", parse_arguments(["dev"])[:environment])
- end
+ assert_match("dev", parse_arguments(["-e", "dev"])[:environment])
ensure
Rails::Command::ConsoleCommand.class_eval do
undef_method :available_environments
diff --git a/railties/test/commands/db_system_change_test.rb b/railties/test/commands/db_system_change_test.rb
new file mode 100644
index 0000000000..2ff45a7878
--- /dev/null
+++ b/railties/test/commands/db_system_change_test.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require "isolation/abstract_unit"
+require "rails/command"
+require "rails/commands/db/system/change/change_command"
+
+class Rails::Command::Db::System::ChangeCommandTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ setup { build_app }
+
+ teardown { teardown_app }
+
+ test "change to existing database" do
+ change_database(to: "sqlite3")
+
+ output = change_database(to: "sqlite3")
+
+ assert_match "identical config/database.yml", output
+ assert_match "gsub Gemfile", output
+ end
+
+ test "change to invalid database" do
+ output = change_database(to: "invalid-db")
+
+ assert_match <<~MSG.squish, output
+ Invalid value for --to option.
+ Supported preconfigurations are:
+ mysql, postgresql, sqlite3, oracle, frontbase,
+ ibm_db, sqlserver, jdbcmysql, jdbcsqlite3,
+ jdbcpostgresql, jdbc.
+ MSG
+ end
+
+ test "change to postgresql" do
+ output = change_database(to: "postgresql")
+
+ assert_match "force config/database.yml", output
+ assert_match "gsub Gemfile", output
+ end
+
+ test "change to mysql" do
+ output = change_database(to: "mysql")
+
+ assert_match "force config/database.yml", output
+ assert_match "gsub Gemfile", output
+ end
+
+ test "change to sqlite3" do
+ change_database(to: "postgresql")
+ output = change_database(to: "sqlite3")
+
+ assert_match "force config/database.yml", output
+ assert_match "gsub Gemfile", output
+ end
+
+ private
+ def change_database(to:, **options)
+ args = ["--to", to]
+ rails "db:system:change", args, **options
+ end
+end
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
index ce048ac527..adb168f7a3 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -99,28 +99,12 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
ENV["RACK_ENV"] = nil
end
- def test_rails_env_is_development_when_argument_is_dev
- assert_deprecated do
- stub_available_environments([ "development", "test" ]) do
- assert_match("development", parse_arguments([ "dev" ])[:environment])
- end
- end
- end
-
def test_rails_env_is_development_when_environment_option_is_dev
stub_available_environments([ "development", "test" ]) do
assert_match("development", parse_arguments([ "-e", "dev" ])[:environment])
end
end
- def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
- assert_deprecated do
- stub_available_environments([ "dev" ]) do
- assert_match("dev", parse_arguments([ "dev" ])[:environment])
- end
- end
- end
-
def test_mysql
start(adapter: "mysql2", database: "db")
assert_not aborted
@@ -265,14 +249,14 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
stdout = capture(:stdout) do
Rails::Command.invoke(:dbconsole, ["-h"])
end
- assert_match(/rails dbconsole \[environment\]/, stdout)
+ assert_match(/rails dbconsole \[options\]/, stdout)
end
def test_print_help_long
stdout = capture(:stdout) do
Rails::Command.invoke(:dbconsole, ["--help"])
end
- assert_match(/rails dbconsole \[environment\]/, stdout)
+ assert_match(/rails dbconsole \[options\]/, stdout)
end
attr_reader :aborted, :output
diff --git a/railties/test/commands/routes_test.rb b/railties/test/commands/routes_test.rb
index 9efab27b38..b4f927060e 100644
--- a/railties/test/commands/routes_test.rb
+++ b/railties/test/commands/routes_test.rb
@@ -17,29 +17,28 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
end
RUBY
- expected_post_output = [" Prefix Verb URI Pattern Controller#Action",
- " new_post GET /post/new(.:format) posts#new",
- "edit_post GET /post/edit(.:format) posts#edit",
- " post GET /post(.:format) posts#show",
- " PATCH /post(.:format) posts#update",
- " PUT /post(.:format) posts#update",
- " DELETE /post(.:format) posts#destroy",
- " POST /post(.:format) posts#create\n"].join("\n")
-
- output = run_routes_command(["-c", "PostController"])
- assert_equal expected_post_output, output
-
- expected_perm_output = [" Prefix Verb URI Pattern Controller#Action",
- " new_user_permission GET /user_permission/new(.:format) user_permissions#new",
- "edit_user_permission GET /user_permission/edit(.:format) user_permissions#edit",
- " user_permission GET /user_permission(.:format) user_permissions#show",
- " PATCH /user_permission(.:format) user_permissions#update",
- " PUT /user_permission(.:format) user_permissions#update",
- " DELETE /user_permission(.:format) user_permissions#destroy",
- " POST /user_permission(.:format) user_permissions#create\n"].join("\n")
-
- output = run_routes_command(["-c", "UserPermissionController"])
- assert_equal expected_perm_output, output
+ assert_equal <<~OUTPUT, run_routes_command([ "-c", "PostController" ])
+ Prefix Verb URI Pattern Controller#Action
+ new_post GET /post/new(.:format) posts#new
+ edit_post GET /post/edit(.:format) posts#edit
+ post GET /post(.:format) posts#show
+ PATCH /post(.:format) posts#update
+ PUT /post(.:format) posts#update
+ DELETE /post(.:format) posts#destroy
+ POST /post(.:format) posts#create
+ rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create
+ OUTPUT
+
+ assert_equal <<~OUTPUT, run_routes_command([ "-c", "UserPermissionController" ])
+ Prefix Verb URI Pattern Controller#Action
+ new_user_permission GET /user_permission/new(.:format) user_permissions#new
+ edit_user_permission GET /user_permission/edit(.:format) user_permissions#edit
+ user_permission GET /user_permission(.:format) user_permissions#show
+ PATCH /user_permission(.:format) user_permissions#update
+ PUT /user_permission(.:format) user_permissions#update
+ DELETE /user_permission(.:format) user_permissions#destroy
+ POST /user_permission(.:format) user_permissions#create
+ OUTPUT
end
test "rails routes with global search key" do
@@ -51,25 +50,33 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
end
RUBY
- output = run_routes_command(["-g", "show"])
- assert_equal <<~MESSAGE, output
- Prefix Verb URI Pattern Controller#Action
- cart GET /cart(.:format) cart#show
- rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
- rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
- rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
+ assert_equal <<~MESSAGE, run_routes_command([ "-g", "show" ])
+ Prefix Verb URI Pattern Controller#Action
+ cart GET /cart(.:format) cart#show
+ rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#show
+ rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
+ rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
+ rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
MESSAGE
- output = run_routes_command(["-g", "POST"])
- assert_equal <<~MESSAGE, output
- Prefix Verb URI Pattern Controller#Action
- POST /cart(.:format) cart#create
- rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
+ assert_equal <<~MESSAGE, run_routes_command([ "-g", "POST" ])
+ Prefix Verb URI Pattern Controller#Action
+ POST /cart(.:format) cart#create
+ rails_amazon_inbound_emails POST /rails/action_mailbox/amazon/inbound_emails(.:format) action_mailbox/ingresses/amazon/inbound_emails#create
+ rails_mandrill_inbound_emails POST /rails/action_mailbox/mandrill/inbound_emails(.:format) action_mailbox/ingresses/mandrill/inbound_emails#create
+ rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create
+ rails_relay_inbound_emails POST /rails/action_mailbox/relay/inbound_emails(.:format) action_mailbox/ingresses/relay/inbound_emails#create
+ rails_sendgrid_inbound_emails POST /rails/action_mailbox/sendgrid/inbound_emails(.:format) action_mailbox/ingresses/sendgrid/inbound_emails#create
+ rails_mailgun_inbound_emails POST /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) action_mailbox/ingresses/mailgun/inbound_emails#create
+ POST /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#create
+ rails_conductor_inbound_email_reroute POST /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format) rails/conductor/action_mailbox/reroutes#create
+ rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
MESSAGE
- output = run_routes_command(["-g", "basketballs"])
- assert_equal " Prefix Verb URI Pattern Controller#Action\n" \
- "basketballs GET /basketballs(.:format) basketball#index\n", output
+ assert_equal <<~MESSAGE, run_routes_command([ "-g", "basketballs" ])
+ Prefix Verb URI Pattern Controller#Action
+ basketballs GET /basketballs(.:format) basketball#index
+ MESSAGE
end
test "rails routes with controller search key" do
@@ -113,35 +120,42 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
end
RUBY
- expected_post_output = [" Prefix Verb URI Pattern Controller#Action",
- " new_admin_post GET /admin/post/new(.:format) admin/posts#new",
- "edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit",
- " admin_post GET /admin/post(.:format) admin/posts#show",
- " PATCH /admin/post(.:format) admin/posts#update",
- " PUT /admin/post(.:format) admin/posts#update",
- " DELETE /admin/post(.:format) admin/posts#destroy",
- " POST /admin/post(.:format) admin/posts#create\n"].join("\n")
-
- output = run_routes_command(["-c", "Admin::PostController"])
- assert_equal expected_post_output, output
-
- output = run_routes_command(["-c", "PostController"])
- assert_equal expected_post_output, output
-
- expected_perm_output = [" Prefix Verb URI Pattern Controller#Action",
- " new_admin_user_permission GET /admin/user_permission/new(.:format) admin/user_permissions#new",
- "edit_admin_user_permission GET /admin/user_permission/edit(.:format) admin/user_permissions#edit",
- " admin_user_permission GET /admin/user_permission(.:format) admin/user_permissions#show",
- " PATCH /admin/user_permission(.:format) admin/user_permissions#update",
- " PUT /admin/user_permission(.:format) admin/user_permissions#update",
- " DELETE /admin/user_permission(.:format) admin/user_permissions#destroy",
- " POST /admin/user_permission(.:format) admin/user_permissions#create\n"].join("\n")
-
- output = run_routes_command(["-c", "Admin::UserPermissionController"])
- assert_equal expected_perm_output, output
-
- output = run_routes_command(["-c", "UserPermissionController"])
- assert_equal expected_perm_output, output
+ assert_equal <<~OUTPUT, run_routes_command([ "-c", "Admin::PostController" ])
+ Prefix Verb URI Pattern Controller#Action
+ new_admin_post GET /admin/post/new(.:format) admin/posts#new
+ edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit
+ admin_post GET /admin/post(.:format) admin/posts#show
+ PATCH /admin/post(.:format) admin/posts#update
+ PUT /admin/post(.:format) admin/posts#update
+ DELETE /admin/post(.:format) admin/posts#destroy
+ POST /admin/post(.:format) admin/posts#create
+ OUTPUT
+
+ assert_equal <<~OUTPUT, run_routes_command([ "-c", "PostController" ])
+ Prefix Verb URI Pattern Controller#Action
+ new_admin_post GET /admin/post/new(.:format) admin/posts#new
+ edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit
+ admin_post GET /admin/post(.:format) admin/posts#show
+ PATCH /admin/post(.:format) admin/posts#update
+ PUT /admin/post(.:format) admin/posts#update
+ DELETE /admin/post(.:format) admin/posts#destroy
+ POST /admin/post(.:format) admin/posts#create
+ rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create
+ OUTPUT
+
+ expected_permission_output = <<~OUTPUT
+ Prefix Verb URI Pattern Controller#Action
+ new_admin_user_permission GET /admin/user_permission/new(.:format) admin/user_permissions#new
+ edit_admin_user_permission GET /admin/user_permission/edit(.:format) admin/user_permissions#edit
+ admin_user_permission GET /admin/user_permission(.:format) admin/user_permissions#show
+ PATCH /admin/user_permission(.:format) admin/user_permissions#update
+ PUT /admin/user_permission(.:format) admin/user_permissions#update
+ DELETE /admin/user_permission(.:format) admin/user_permissions#destroy
+ POST /admin/user_permission(.:format) admin/user_permissions#create
+ OUTPUT
+
+ assert_equal expected_permission_output, run_routes_command([ "-c", "Admin::UserPermissionController" ])
+ assert_equal expected_permission_output, run_routes_command([ "-c", "UserPermissionController" ])
end
test "rails routes displays message when no routes are defined" do
@@ -151,12 +165,27 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
RUBY
assert_equal <<~MESSAGE, run_routes_command
- Prefix Verb URI Pattern Controller#Action
- rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
- rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
- rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
- update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
- rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
+ Prefix Verb URI Pattern Controller#Action
+ rails_amazon_inbound_emails POST /rails/action_mailbox/amazon/inbound_emails(.:format) action_mailbox/ingresses/amazon/inbound_emails#create
+ rails_mandrill_inbound_emails POST /rails/action_mailbox/mandrill/inbound_emails(.:format) action_mailbox/ingresses/mandrill/inbound_emails#create
+ rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create
+ rails_relay_inbound_emails POST /rails/action_mailbox/relay/inbound_emails(.:format) action_mailbox/ingresses/relay/inbound_emails#create
+ rails_sendgrid_inbound_emails POST /rails/action_mailbox/sendgrid/inbound_emails(.:format) action_mailbox/ingresses/sendgrid/inbound_emails#create
+ rails_mailgun_inbound_emails POST /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) action_mailbox/ingresses/mailgun/inbound_emails#create
+ rails_conductor_inbound_emails GET /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#index
+ POST /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#create
+ new_rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/new(.:format) rails/conductor/action_mailbox/inbound_emails#new
+ edit_rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id/edit(.:format) rails/conductor/action_mailbox/inbound_emails#edit
+ rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#show
+ PATCH /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update
+ PUT /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update
+ DELETE /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#destroy
+ rails_conductor_inbound_email_reroute POST /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format) rails/conductor/action_mailbox/reroutes#create
+ rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
+ rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
+ rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
+ update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
+ rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
MESSAGE
end
@@ -165,45 +194,120 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase
IO.console.winsize = [0, 27]
app_file "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- get '/cart', to: 'cart#show'
- end
- RUBY
-
- output = run_routes_command(["--expanded"])
-
- assert_equal <<~MESSAGE, output
- --[ Route 1 ]--------------
- Prefix | cart
- Verb | GET
- URI | /cart(.:format)
- Controller#Action | cart#show
- --[ Route 2 ]--------------
- Prefix | rails_service_blob
- Verb | GET
- URI | /rails/active_storage/blobs/:signed_id/*filename(.:format)
- Controller#Action | active_storage/blobs#show
- --[ Route 3 ]--------------
- Prefix | rails_blob_representation
- Verb | GET
- URI | /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format)
- Controller#Action | active_storage/representations#show
- --[ Route 4 ]--------------
- Prefix | rails_disk_service
- Verb | GET
- URI | /rails/active_storage/disk/:encoded_key/*filename(.:format)
- Controller#Action | active_storage/disk#show
- --[ Route 5 ]--------------
- Prefix | update_rails_disk_service
- Verb | PUT
- URI | /rails/active_storage/disk/:encoded_token(.:format)
- Controller#Action | active_storage/disk#update
- --[ Route 6 ]--------------
- Prefix | rails_direct_uploads
- Verb | POST
- URI | /rails/active_storage/direct_uploads(.:format)
- Controller#Action | active_storage/direct_uploads#create
- MESSAGE
+ Rails.application.routes.draw do
+ get '/cart', to: 'cart#show'
+ end
+ RUBY
+
+ # rubocop:disable Layout/TrailingWhitespace
+ assert_equal <<~MESSAGE, run_routes_command([ "--expanded" ])
+ --[ Route 1 ]--------------
+ Prefix | cart
+ Verb | GET
+ URI | /cart(.:format)
+ Controller#Action | cart#show
+ --[ Route 2 ]--------------
+ Prefix | rails_amazon_inbound_emails
+ Verb | POST
+ URI | /rails/action_mailbox/amazon/inbound_emails(.:format)
+ Controller#Action | action_mailbox/ingresses/amazon/inbound_emails#create
+ --[ Route 3 ]--------------
+ Prefix | rails_mandrill_inbound_emails
+ Verb | POST
+ URI | /rails/action_mailbox/mandrill/inbound_emails(.:format)
+ Controller#Action | action_mailbox/ingresses/mandrill/inbound_emails#create
+ --[ Route 4 ]--------------
+ Prefix | rails_postmark_inbound_emails
+ Verb | POST
+ URI | /rails/action_mailbox/postmark/inbound_emails(.:format)
+ Controller#Action | action_mailbox/ingresses/postmark/inbound_emails#create
+ --[ Route 5 ]--------------
+ Prefix | rails_relay_inbound_emails
+ Verb | POST
+ URI | /rails/action_mailbox/relay/inbound_emails(.:format)
+ Controller#Action | action_mailbox/ingresses/relay/inbound_emails#create
+ --[ Route 6 ]--------------
+ Prefix | rails_sendgrid_inbound_emails
+ Verb | POST
+ URI | /rails/action_mailbox/sendgrid/inbound_emails(.:format)
+ Controller#Action | action_mailbox/ingresses/sendgrid/inbound_emails#create
+ --[ Route 7 ]--------------
+ Prefix | rails_mailgun_inbound_emails
+ Verb | POST
+ URI | /rails/action_mailbox/mailgun/inbound_emails/mime(.:format)
+ Controller#Action | action_mailbox/ingresses/mailgun/inbound_emails#create
+ --[ Route 8 ]--------------
+ Prefix | rails_conductor_inbound_emails
+ Verb | GET
+ URI | /rails/conductor/action_mailbox/inbound_emails(.:format)
+ Controller#Action | rails/conductor/action_mailbox/inbound_emails#index
+ --[ Route 9 ]--------------
+ Prefix |
+ Verb | POST
+ URI | /rails/conductor/action_mailbox/inbound_emails(.:format)
+ Controller#Action | rails/conductor/action_mailbox/inbound_emails#create
+ --[ Route 10 ]-------------
+ Prefix | new_rails_conductor_inbound_email
+ Verb | GET
+ URI | /rails/conductor/action_mailbox/inbound_emails/new(.:format)
+ Controller#Action | rails/conductor/action_mailbox/inbound_emails#new
+ --[ Route 11 ]-------------
+ Prefix | edit_rails_conductor_inbound_email
+ Verb | GET
+ URI | /rails/conductor/action_mailbox/inbound_emails/:id/edit(.:format)
+ Controller#Action | rails/conductor/action_mailbox/inbound_emails#edit
+ --[ Route 12 ]-------------
+ Prefix | rails_conductor_inbound_email
+ Verb | GET
+ URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format)
+ Controller#Action | rails/conductor/action_mailbox/inbound_emails#show
+ --[ Route 13 ]-------------
+ Prefix |
+ Verb | PATCH
+ URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format)
+ Controller#Action | rails/conductor/action_mailbox/inbound_emails#update
+ --[ Route 14 ]-------------
+ Prefix |
+ Verb | PUT
+ URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format)
+ Controller#Action | rails/conductor/action_mailbox/inbound_emails#update
+ --[ Route 15 ]-------------
+ Prefix |
+ Verb | DELETE
+ URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format)
+ Controller#Action | rails/conductor/action_mailbox/inbound_emails#destroy
+ --[ Route 16 ]-------------
+ Prefix | rails_conductor_inbound_email_reroute
+ Verb | POST
+ URI | /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format)
+ Controller#Action | rails/conductor/action_mailbox/reroutes#create
+ --[ Route 17 ]-------------
+ Prefix | rails_service_blob
+ Verb | GET
+ URI | /rails/active_storage/blobs/:signed_id/*filename(.:format)
+ Controller#Action | active_storage/blobs#show
+ --[ Route 18 ]-------------
+ Prefix | rails_blob_representation
+ Verb | GET
+ URI | /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format)
+ Controller#Action | active_storage/representations#show
+ --[ Route 19 ]-------------
+ Prefix | rails_disk_service
+ Verb | GET
+ URI | /rails/active_storage/disk/:encoded_key/*filename(.:format)
+ Controller#Action | active_storage/disk#show
+ --[ Route 20 ]-------------
+ Prefix | update_rails_disk_service
+ Verb | PUT
+ URI | /rails/active_storage/disk/:encoded_token(.:format)
+ Controller#Action | active_storage/disk#update
+ --[ Route 21 ]-------------
+ Prefix | rails_direct_uploads
+ Verb | POST
+ URI | /rails/active_storage/direct_uploads(.:format)
+ Controller#Action | active_storage/direct_uploads#create
+ MESSAGE
+ # rubocop:enable Layout/TrailingWhitespace
ensure
IO.console.winsize = previous_console_winsize
end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index fbdd3f3ebb..25b89ecbd8 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -32,6 +32,12 @@ class Rails::Command::ServerCommandTest < ActiveSupport::TestCase
assert_match(/Could not find server "tin". Maybe you meant "thin"?/, run_command("--using", "tin"))
end
+ def test_using_server_mistype_without_suggestion
+ output = run_command("--using", "t")
+ assert_match(/Could not find server "t"/, output)
+ assert_no_match(/Maybe you meant/, output)
+ end
+
def test_using_positional_argument_deprecation
assert_match(/DEPRECATION WARNING/, run_command("tin"))
end
diff --git a/railties/test/credentials_test.rb b/railties/test/credentials_test.rb
deleted file mode 100644
index 11765b0de5..0000000000
--- a/railties/test/credentials_test.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-# frozen_string_literal: true
-
-require "isolation/abstract_unit"
-require "env_helpers"
-
-class Rails::CredentialsTest < ActiveSupport::TestCase
- include ActiveSupport::Testing::Isolation, EnvHelpers
-
- setup :build_app
- teardown :teardown_app
-
- test "reads credentials from environment specific path" do
- with_credentials do |content, key|
- Dir.chdir(app_path) do
- Dir.mkdir("config/credentials")
- File.write("config/credentials/production.yml.enc", content)
- File.write("config/credentials/production.key", key)
- end
-
- app("production")
-
- assert_equal "revealed", Rails.application.credentials.mystery
- end
- end
-
- test "reads credentials from customized path and key" do
- with_credentials do |content, key|
- Dir.chdir(app_path) do
- Dir.mkdir("config/credentials")
- File.write("config/credentials/staging.yml.enc", content)
- File.write("config/credentials/staging.key", key)
- end
-
- add_to_env_config("production", "config.credentials.content_path = config.root.join('config/credentials/staging.yml.enc')")
- add_to_env_config("production", "config.credentials.key_path = config.root.join('config/credentials/staging.key')")
- app("production")
-
- assert_equal "revealed", Rails.application.credentials.mystery
- end
- end
-
- test "reads credentials using environment variable key" do
- with_credentials do |content, key|
- Dir.chdir(app_path) do
- Dir.mkdir("config/credentials")
- File.write("config/credentials/production.yml.enc", content)
- end
-
- switch_env("RAILS_MASTER_KEY", key) do
- app("production")
-
- assert_equal "revealed", Rails.application.credentials.mystery
- end
- end
- end
-
- private
- def with_credentials
- key = "2117e775dc2024d4f49ddf3aeb585919"
- # secret_key_base: secret
- # mystery: revealed
- content = "vgvKu4MBepIgZ5VHQMMPwnQNsLlWD9LKmJHu3UA/8yj6x+3fNhz3DwL9brX7UA==--qLdxHP6e34xeTAiI--nrcAsleXuo9NqiEuhntAhw=="
- yield(content, key)
- end
-end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index af475400a1..4932100ea2 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -412,15 +412,6 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_capify_should_run_the_capify_command
- content = capture(:stderr) do
- assert_called_with(generator, :run, ["capify .", verbose: false]) do
- action :capify!
- end
- end
- assert_match(/DEPRECATION WARNING: `capify!` is deprecated/, content)
- end
-
def test_route_should_add_data_to_the_routes_block_in_config_routes
run_generator
route_command = "route '/login', controller: 'sessions', action: 'new'"
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 154cd3e80c..e0cd7f90ce 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -81,6 +81,7 @@ DEFAULT_APP_FILES = %w(
test/test_helper.rb
test/fixtures
test/fixtures/files
+ test/channels/application_cable/connection_test.rb
test/controllers
test/models
test/helpers
@@ -363,6 +364,8 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "#{app_root}/config/environments/production.rb" do |content|
assert_no_match(/config\.action_cable/, content)
end
+
+ assert_no_file "#{app_root}/test/channels/application_cable/connection_test.rb"
end
def test_app_update_does_not_generate_bootsnap_contents_when_skip_bootsnap_is_given
@@ -435,6 +438,36 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_file "#{app_root}/config/storage.yml"
end
+ def test_generator_skips_action_mailbox_when_skip_action_mailbox_is_given
+ run_generator [destination_root, "--skip-action-mailbox"]
+ assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_mailbox\/engine["']/
+ end
+
+ def test_generator_skips_action_mailbox_when_skip_active_record_is_given
+ run_generator [destination_root, "--skip-active-record"]
+ assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_mailbox\/engine["']/
+ end
+
+ def test_generator_skips_action_mailbox_when_skip_active_storage_is_given
+ run_generator [destination_root, "--skip-active-storage"]
+ assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_mailbox\/engine["']/
+ end
+
+ def test_generator_skips_action_text_when_skip_action_text_is_given
+ run_generator [destination_root, "--skip-action-text"]
+ assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_text\/engine["']/
+ end
+
+ def test_generator_skips_action_text_when_skip_active_record_is_given
+ run_generator [destination_root, "--skip-active-record"]
+ assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_text\/engine["']/
+ end
+
+ def test_generator_skips_action_text_when_skip_active_storage_is_given
+ run_generator [destination_root, "--skip-active-storage"]
+ assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_text\/engine["']/
+ end
+
def test_app_update_does_not_change_config_target_version
run_generator
@@ -743,6 +776,24 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_bundler_command_called("install")
end
+ def test_generation_use_original_bundle_environment
+ generator([destination_root], skip_webpack_install: true)
+
+ mock_original_env = -> do
+ { "BUNDLE_RUBYONRAILS__ORG" => "user:pass" }
+ end
+
+ ensure_environment_is_set = -> *_args do
+ assert_equal "user:pass", ENV["BUNDLE_RUBYONRAILS__ORG"]
+ end
+
+ Bundler.stub :original_env, mock_original_env do
+ generator.stub :exec_bundle_command, ensure_environment_is_set do
+ quietly { generator.invoke_all }
+ end
+ end
+ end
+
def test_dev_option
generator([destination_root], dev: true, skip_webpack_install: true)
diff --git a/railties/test/generators/channel_generator_test.rb b/railties/test/generators/channel_generator_test.rb
index 1cb8465539..1a25422c3c 100644
--- a/railties/test/generators/channel_generator_test.rb
+++ b/railties/test/generators/channel_generator_test.rb
@@ -67,12 +67,23 @@ class ChannelGeneratorTest < Rails::Generators::TestCase
assert_file "app/javascript/channels/consumer.js"
end
+ def test_invokes_default_test_framework
+ run_generator %w(chat -t=test_unit)
+
+ assert_file "test/channels/chat_channel_test.rb" do |test|
+ assert_match(/class ChatChannelTest < ActionCable::Channel::TestCase/, test)
+ assert_match(/# test "subscribes" do/, test)
+ assert_match(/# assert subscription.confirmed\?/, test)
+ end
+ end
+
def test_channel_on_revoke
run_generator ["chat"]
run_generator ["chat"], behavior: :revoke
assert_no_file "app/channels/chat_channel.rb"
assert_no_file "app/javascript/channels/chat_channel.js"
+ assert_no_file "test/channels/chat_channel_test.rb"
assert_file "app/channels/application_cable/channel.rb"
assert_file "app/channels/application_cable/connection.rb"
@@ -88,5 +99,8 @@ class ChannelGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/javascript/channels/chat_channel_channel.js"
assert_file "app/javascript/channels/chat_channel.js"
+
+ assert_no_file "test/channels/chat_channel_channel_test.rb"
+ assert_file "test/channels/chat_channel_test.rb"
end
end
diff --git a/railties/test/generators/db_system_change_generator_test.rb b/railties/test/generators/db_system_change_generator_test.rb
new file mode 100644
index 0000000000..d476bfd2dc
--- /dev/null
+++ b/railties/test/generators/db_system_change_generator_test.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require "generators/generators_test_helper"
+require "rails/generators/rails/db/system/change/change_generator"
+
+module Rails
+ module Generators
+ module Db
+ module System
+ class ChangeGeneratorTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+
+ setup do
+ copy_gemfile(
+ GemfileEntry.new("sqlite3", nil, "Use sqlite3 as the database for Active Record")
+ )
+ end
+
+ test "change to invalid database" do
+ output = capture(:stderr) do
+ run_generator ["--to", "invalid-db"]
+ end
+
+ assert_match <<~MSG.squish, output
+ Invalid value for --to option.
+ Supported preconfigurations are:
+ mysql, postgresql, sqlite3, oracle, frontbase,
+ ibm_db, sqlserver, jdbcmysql, jdbcsqlite3,
+ jdbcpostgresql, jdbc.
+ MSG
+ end
+
+ test "change to postgresql" do
+ run_generator ["--to", "postgresql"]
+
+ assert_file("config/database.yml") do |content|
+ assert_match "adapter: postgresql", content
+ assert_match "database: test_app", content
+ end
+
+ assert_file("Gemfile") do |content|
+ assert_match "# Use pg as the database for Active Record", content
+ assert_match "gem 'pg'", content
+ end
+ end
+
+ test "change to mysql" do
+ run_generator ["--to", "mysql"]
+
+ assert_file("config/database.yml") do |content|
+ assert_match "adapter: mysql2", content
+ assert_match "database: test_app", content
+ end
+
+ assert_file("Gemfile") do |content|
+ assert_match "# Use mysql2 as the database for Active Record", content
+ assert_match "gem 'mysql2'", content
+ end
+ end
+
+ test "change to sqlite3" do
+ run_generator ["--to", "sqlite3"]
+
+ assert_file("config/database.yml") do |content|
+ assert_match "adapter: sqlite3", content
+ assert_match "db/development.sqlite3", content
+ end
+
+ assert_file("Gemfile") do |content|
+ assert_match "# Use sqlite3 as the database for Active Record", content
+ assert_match "gem 'sqlite3'", content
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 25d5dba1d8..975a204af4 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -30,6 +30,12 @@ module GeneratorsTestHelper
include ActiveSupport::Testing::Stream
include ActiveSupport::Testing::MethodCallAssertions
+ GemfileEntry = Struct.new(:name, :version, :comment, :options, :commented_out) do
+ def initialize(name, version, comment, options = {}, commented_out = false)
+ super
+ end
+ end
+
def self.included(base)
base.class_eval do
destination File.join(Rails.root, "tmp")
@@ -63,4 +69,34 @@ module GeneratorsTestHelper
FileUtils.mkdir_p(destination)
FileUtils.cp routes, File.join(destination, "routes.rb")
end
+
+ def copy_gemfile(*gemfile_entries)
+ locals = gemfile_locals.merge(gemfile_entries: gemfile_entries)
+ gemfile = File.expand_path("../../lib/rails/generators/rails/app/templates/Gemfile.tt", __dir__)
+ gemfile = evaluate_template(gemfile, locals)
+ destination = File.join(destination_root)
+ File.write File.join(destination, "Gemfile"), gemfile
+ end
+
+ def evaluate_template(file, locals = {})
+ erb = ERB.new(File.read(file), nil, "-", "@output_buffer")
+ context = Class.new do
+ locals.each do |local, value|
+ class_attribute local, default: value
+ end
+ end
+ erb.result(context.new.instance_eval("binding"))
+ end
+
+ private
+ def gemfile_locals
+ {
+ skip_active_storage: true,
+ depend_on_bootsnap: false,
+ depend_on_listen: false,
+ spring_install: false,
+ depends_on_system_test: false,
+ options: ActiveSupport::OrderedOptions.new,
+ }
+ end
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 66286fc554..4ec656d18b 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -712,38 +712,6 @@ class PluginGeneratorTest < Rails::Generators::TestCase
Object.send(:remove_const, "ENGINE_ROOT")
end
- def test_after_bundle_callback
- path = "http://example.org/rails_template"
- template = +%{ after_bundle { run "echo ran after_bundle" } }
- template.instance_eval "def read; self; end" # Make the string respond to read
-
- check_open = -> *args do
- assert_equal [ path, "Accept" => "application/x-thor-template" ], args
- template
- end
-
- sequence = ["echo ran after_bundle"]
- @sequence_step ||= 0
- ensure_bundler_first = -> command do
- assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}"
- @sequence_step += 1
- end
-
- content = nil
- generator([destination_root], template: path).stub(:open, check_open, template) do
- generator.stub(:bundle_command, ensure_bundler_first) do
- generator.stub(:run, ensure_bundler_first) do
- silence_stream($stdout) do
- content = capture(:stderr) { generator.invoke_all }
- end
- end
- end
- end
-
- assert_equal 1, @sequence_step
- assert_match(/DEPRECATION WARNING: `after_bundle` is deprecated/, content)
- end
-
private
def action(*args, &block)
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 7441ab0603..26ce487c5f 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -127,6 +127,8 @@ module SharedGeneratorTests
"--skip-active-record",
"--skip-active-storage",
"--skip-action-mailer",
+ "--skip-action-mailbox",
+ "--skip-action-text",
"--skip-action-cable",
"--skip-sprockets"
]
@@ -138,6 +140,10 @@ module SharedGeneratorTests
assert_file "#{application_path}/config/application.rb", /^# require\s+["']active_storage\/engine["']/
assert_file "#{application_path}/config/application.rb", /^require\s+["']action_controller\/railtie["']/
assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_mailer\/railtie["']/
+ unless generator_class.name == "Rails::Generators::PluginGenerator"
+ assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_mailbox\/engine["']/
+ assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_text\/engine["']/
+ end
assert_file "#{application_path}/config/application.rb", /^require\s+["']action_view\/railtie["']/
assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_cable\/engine["']/
assert_file "#{application_path}/config/application.rb", /^# require\s+["']sprockets\/railtie["']/
@@ -200,7 +206,7 @@ module SharedGeneratorTests
unless generator_class.name == "Rails::Generators::PluginGenerator"
assert_file "#{application_path}/app/javascript/packs/application.js" do |content|
- assert_match(/^import \* as ActiveStorage from "activestorage"\nActiveStorage.start\(\)/, content)
+ assert_match(/^require\("@rails\/activestorage"\)\.start\(\)/, content)
end
end
@@ -261,7 +267,7 @@ module SharedGeneratorTests
assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']active_storage\/engine["']/
assert_file "#{application_path}/app/javascript/packs/application.js" do |content|
- assert_no_match(/^import * as ActiveStorage from "activestorage"\nActiveStorage.start\(\)/, content)
+ assert_no_match(/^require\("@rails\/activestorage"\)\.start\(\)/, content)
end
assert_file "#{application_path}/config/environments/development.rb" do |content|
@@ -340,11 +346,16 @@ module SharedGeneratorTests
run_generator
assert_file "#{application_path}/package.json", /dependencies/
assert_file "#{application_path}/bin/yarn"
+ assert_file "#{application_path}/config/initializers/assets.rb", /node_modules/
end
def test_generator_for_yarn_skipped
run_generator([destination_root, "--skip-javascript"])
assert_no_file "#{application_path}/package.json"
assert_no_file "#{application_path}/bin/yarn"
+
+ assert_file "#{application_path}/config/initializers/assets.rb" do |content|
+ assert_no_match(/node_modules/, content)
+ end
end
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index f98c1f78f7..abdc04a8d3 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -28,6 +28,7 @@ class GeneratorsTest < Rails::Generators::TestCase
output = capture(:stdout) { Rails::Generators.invoke name }
assert_match "Could not find generator '#{name}'", output
assert_match "`rails generate --help`", output
+ assert_no_match "Maybe you meant", output
end
def test_generator_suggestions
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 4523b66dda..01b6cb0a43 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -430,7 +430,7 @@ module TestHelpers
end
def use_frameworks(arr)
- to_remove = [:actionmailer, :activerecord, :activestorage, :activejob] - arr
+ to_remove = [:actionmailer, :activerecord, :activestorage, :activejob, :actionmailbox] - arr
if to_remove.include?(:activerecord)
remove_from_config "config.active_record.*"
@@ -476,7 +476,7 @@ Module.new do
`yarn build`
end
- `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --skip-listen --no-rc`
+ `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-bundle --skip-listen --no-rc`
File.open("#{app_template_path}/config/boot.rb", "w") do |f|
f.puts "require 'rails/all'"
end
diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb
index 849b183b37..0c1ee259b0 100644
--- a/railties/test/path_generation_test.rb
+++ b/railties/test/path_generation_test.rb
@@ -66,7 +66,7 @@ class PathGenerationTest < ActiveSupport::TestCase
super
app = self
@routes = TestSet.new ->(c) { app.controller = c }
- secrets.secret_token = "foo"
+ secrets.secret_key_base = "foo"
end
def app; routes; end
}
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index 6ab68f8333..878a238f8d 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -50,11 +50,6 @@ class InfoControllerTest < ActionController::TestCase
assert_select "table"
end
- test "info controller renders json with properties" do
- get :properties, format: :json
- assert_equal Rails::Info.to_json, response.body
- end
-
test "info controller renders with routes" do
get :routes
assert_response :success
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index d167a86e56..50522c1be6 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -43,18 +43,6 @@ class InfoTest < ActiveSupport::TestCase
end
end
- def test_json_includes_middleware
- Rails::Info.module_eval do
- property "Middleware", ["Rack::Lock", "Rack::Static"]
- end
-
- hash = JSON.parse(Rails::Info.to_json)
- assert_includes hash.keys, "Middleware"
- properties.value_for("Middleware").each do |value|
- assert_includes hash["Middleware"], value
- end
- end
-
private
def properties
Rails::Info.properties
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 4ac8f8d741..508367212b 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -90,8 +90,8 @@ module RailtiesTest
boot_rails
Dir.chdir(app_path) do
- # Install Active Storage migration file first so as not to affect test.
- `bundle exec rake active_storage:install`
+ # Install Active Storage, Action Mailbox, and Action Text migration files first so as not to affect test.
+ `bundle exec rake active_storage:install action_mailbox:install action_text:install`
output = `bundle exec rake bukkits:install:migrations`
["CreateUsers", "AddLastNameToUsers", "CreateSessions"].each do |migration_name|
@@ -177,8 +177,8 @@ module RailtiesTest
boot_rails
Dir.chdir(app_path) do
- # Install Active Storage migration file first so as not to affect test.
- `bundle exec rake active_storage:install`
+ # Install Active Storage, Action Mailbox, and Action Text migrations first so as not to affect test.
+ `bundle exec rake active_storage:install action_mailbox:install action_text:install`
output = `bundle exec rake railties:install:migrations`.split("\n")
assert_match(/Copied migration \d+_create_users\.core_engine\.rb from core_engine/, output.first)
@@ -879,6 +879,18 @@ YAML
assert Bukkits::Engine.config.bukkits_seeds_loaded
end
+ test "jobs are ran inline while loading seeds" do
+ app_file "db/seeds.rb", <<-RUBY
+ Rails.application.config.seed_queue_adapter = ActiveJob::Base.queue_adapter
+ RUBY
+
+ boot_rails
+ Rails.application.load_seed
+
+ assert_instance_of ActiveJob::QueueAdapters::InlineAdapter, Rails.application.config.seed_queue_adapter
+ assert_instance_of ActiveJob::QueueAdapters::AsyncAdapter, ActiveJob::Base.queue_adapter
+ end
+
test "skips nonexistent seed data" do
FileUtils.rm "#{app_path}/db/seeds.rb"
boot_rails
diff --git a/tasks/release.rb b/tasks/release.rb
index a13003aa27..2fdcea9d12 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -1,6 +1,20 @@
# frozen_string_literal: true
-FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack activejob actionmailer actioncable activestorage railties )
+# Order dependent. E.g. Action Mailbox depends on Active Record so it should be after.
+FRAMEWORKS = %w(
+ activesupport
+ activemodel
+ activerecord
+ actionview
+ actionpack
+ activejob
+ actionmailer
+ actioncable
+ activestorage
+ actionmailbox
+ actiontext
+ railties
+)
FRAMEWORK_NAMES = Hash.new { |h, k| k.split(/(?<=active|action)/).map(&:capitalize).join(" ") }
root = File.expand_path("..", __dir__)
@@ -122,16 +136,20 @@ namespace :changelog do
end
end
- task :release_summary, [:base_release] do |_, args|
+ task :release_summary, [:base_release, :release] do |_, args|
release_regexp = args[:base_release] ? Regexp.escape(args[:base_release]) : /\d+\.\d+\.\d+/
+ puts release
+
FRAMEWORKS.each do |fw|
puts "## #{FRAMEWORK_NAMES[fw]}"
fname = File.join fw, "CHANGELOG.md"
contents = File.readlines fname
contents.shift
changes = []
- until contents.first =~ /^## Rails #{release_regexp}.*$/
+ until contents.first =~ /^## Rails #{release_regexp}.*$/ ||
+ contents.first =~ /^Please check.*for previous changes\.$/ ||
+ contents.empty?
changes << contents.shift
end
@@ -159,15 +177,58 @@ namespace :all do
end
task verify: :install do
- app_name = "pkg/verify-#{version}-#{Time.now.to_i}"
+ require "tmpdir"
+
+ cd Dir.tmpdir
+ app_name = "verify-#{version}-#{Time.now.to_i}"
sh "rails _#{version}_ new #{app_name} --skip-bundle" # Generate with the right version.
cd app_name
+ substitute = -> (file_name, regex, replacement) do
+ File.write(file_name, File.read(file_name).sub(regex, replacement))
+ end
+
# Replace the generated gemfile entry with the exact version.
- File.write("Gemfile", File.read("Gemfile").sub(/^gem 'rails.*/, "gem 'rails', '#{version}'"))
+ substitute.call("Gemfile", /^gem 'rails.*/, "gem 'rails', '#{version}'")
+ substitute.call("Gemfile", /^# gem 'image_processing/, "gem 'image_processing")
sh "bundle"
+ sh "rails action_mailbox:install"
+ sh "rails action_text:install"
+
+ sh "rails generate scaffold user name description:text admin:boolean"
+ sh "rails db:migrate"
+
+ # Replace the generated gemfile entry with the exact version.
+ substitute.call("app/models/user.rb", /end\n\z/, <<~CODE)
+ has_one_attached :avatar
+ has_rich_text :description
+ end
+ CODE
+
+ substitute.call("app/views/users/_form.html.erb", /text_area :description %>\n <\/div>/, <<~CODE)
+ rich_text_area :description %>\n </div>
+
+ <div class="field">
+ Avatar: <%= form.file_field :avatar %>
+ </div>
+ CODE
+
+ substitute.call("app/views/users/show.html.erb", /description %>\n<\/p>/, <<~CODE)
+ description %>\n</p>
- sh "rails generate scaffold user name admin:boolean && rails db:migrate"
+ <p>
+ <% if @user.avatar.attached? -%>
+ <%= image_tag @user.avatar.representation(resize_to_limit: [500, 500]) %>
+ <% end -%>
+ </p>
+ CODE
+
+ # Permit the avatar param.
+ substitute.call("app/controllers/users_controller.rb", /:admin/, ":admin, :avatar")
+
+ if ENV["EDITOR"]
+ `#{ENV["EDITOR"]} #{File.expand_path(app_name)}`
+ end
puts "Booting a Rails server. Verify the release by:"
puts
diff --git a/version.rb b/version.rb
index 54bfbdd516..82df56559c 100644
--- a/version.rb
+++ b/version.rb
@@ -10,7 +10,7 @@ module Rails
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/yarn.lock b/yarn.lock
index 02d1463723..fdf508b9a3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5692,6 +5692,11 @@ trim-right@^1.0.1:
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
+trix@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/trix/-/trix-1.0.0.tgz#e9cc98cf6030c908f8d54e317b5b072f927b0c6b"
+ integrity sha512-feli9QVXe6gzZOCUfpPGpNDURW9jMciIRVQ5gkDudOctcA1oMtI5K/qEbsL2rFCoGl1rSoeRt+HPhIFGyQscKg==
+
tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"