diff --git a/.postgresql.travis.yml b/.postgresql.travis.yml new file mode 100644 index 000000000..fe8744287 --- /dev/null +++ b/.postgresql.travis.yml @@ -0,0 +1,46 @@ +dist: xenial + +addons: + apt: + sources: + - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' + key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' + - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' + key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' + packages: + - postgresql-11 + - chromium-chromedriver + - google-chrome-stable + - yarn + - redis-server + postgresql: '11' + +_test_gem_pg: &_test_gem_pg + before_install: + - echo 'installing postgresql' + - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf + - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo service postgresql stop + - sudo service postgresql start 11 + - postgres --version + - sudo rm -f /usr/local/bin/yarn + - nvm install 10 + - rvm install 2.6.3 # was 2.5.1 + - gem install bundler + - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver + - echo 'install completed' + + before_script: + - psql -c "create database ${DB};" -U postgres + - cd ruby/$COMPONENT + - bundle install --jobs=3 --retry=3 + - google-chrome --version + - which google-chrome + - yarn install + script: + - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_type_spec.rb:121 + +jobs: + include: + - <<: *_test_gem_pg + env: COMPONENT=hyper-model DB=hyper_mesh_test_db diff --git a/.rubocop.yml b/.rubocop.yml index 805c0fb68..db53e995b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,4 @@ AllCops: - TargetRubyVersion: 2.3 Exclude: - 'db/schema.rb' - 'db/seeds.rb' @@ -31,14 +30,11 @@ Metrics/BlockLength: Description: 'Avoid long blocks with many lines.' Enabled: false -Metrics/LineLength: - Max: 100 - ## Performance -Performance/RegexpMatch: - Enabled: false +# Performance/RegexpMatch: +# Enabled: false ## Style @@ -139,3 +135,23 @@ Style/MutableConstant: Style/SafeNavigation: Enabled: false + +Style/StringLiterals: + EnforcedStyle: double_quotes + Exclude: + - Gemfile + +Style/FrozenStringLiteralComment: + Enabled: false + Exclude: + - app/hyperstack/components + - app/hyperstack/libs + +Layout/MultilineMethodCallIndentation: + Exclude: + - 'bin/yarn' + - app/hyperstack/components + +Lint/ConstantDefinitionInBlock: + Exclude: + - !ruby/regexp /_spec\.rb$/ diff --git a/.travis.yml b/.travis.yml index 081e5ed1e..fc3867f3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,75 @@ -language: bash -cache: - bundler: true - directories: - - node_modules # NPM packages +dist: focal + +env: + global: + - PGUSER=postgres + - PGPORT=5432 + - PGHOST=localhost + +addons: + postgresql: '12' + apt: + sources: + - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' + key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' + - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' + key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' + packages: + - chromium-chromedriver + - google-chrome-stable + - yarn + - redis-server + - postgresql-12 + +_test_gem_pg: &_test_gem_pg + stage: test + + language: ruby + cache: + bundler: true + directories: + - node_modules # NPM packages + + before_install: + - sudo sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/*/main/pg_hba.conf + - sudo service postgresql restart + - sleep 1 + - postgres --version + - sudo rm -f /usr/local/bin/yarn + - nvm install 10 + - rvm install 2.6.3 # was 2.5.1 + - gem install bundler + - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver + - echo 'install completed' + + before_script: + - echo before_script $COMPONENT + - echo updating chrome driver + - cd ruby/$COMPONENT + - echo creating psql database + - psql -c 'create database hyper_mesh_test_db;' -U postgres + - bundle install --jobs=3 --retry=3 + - bundle exec ruby -e 'require "webdrivers"; Webdrivers::Chromedriver.update; puts Webdrivers::Chromedriver.current_version' + - ls -la ~/.webdrivers + - sudo cp ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver + - bundle exec rake spec:prepare + - google-chrome --version + - which google-chrome + - yarn install + + script: + - echo running script $COMPONENT + - DRIVER=travis bundle exec rake $TASK _test_gem: &_test_gem stage: test + + language: ruby + cache: + bundler: true + directories: + - node_modules # NPM packages + addons: apt: sources: @@ -17,23 +81,38 @@ _test_gem: &_test_gem - chromium-chromedriver - google-chrome-stable - yarn + - redis-server mariadb: '10.3' + services: + - redis-server + before_install: - echo installing $COMPONENT + - sudo apt-get remove --purge mysql-server mysql-client mysql-common + - sudo apt-get autoremove + - sudo apt-get autoclean + - sudo rm -rf /var/lib/mysql + - sudo rm -rf /etc/mysql + - sudo apt install mariadb-server mariadb-client -y + - sudo apt-get install libmysqlclient-dev + # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package # must remove this zombie for new yarn to work - sudo rm -f /usr/local/bin/yarn - - nvm install 10 - - rvm install 2.5.1 + - nvm install 14 + - rvm install 2.6.3 # was 2.5.1 - gem install bundler - - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver + - echo 'install completed' + before_script: - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - - bundle exec rake spec:prepare - google-chrome --version - - which google-chrome + - bundle exec ruby -e 'require "webdrivers"; Webdrivers::Chromedriver.update; puts Webdrivers::Chromedriver.current_version' + - ls -la ~/.webdrivers + - sudo cp ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver + - bundle exec rake spec:prepare - yarn install script: - echo running script $COMPONENT @@ -54,52 +133,133 @@ _deploy_gem: &_deploy_gem jobs: include: - - <<: *_test_gem - env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 - <<: *_test_gem env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 - - - <<: *_deploy_gem - env: COMPONENT=hyper-i18n - - <<: *_deploy_gem - env: COMPONENT=hyper-trace - - <<: *_deploy_gem - env: COMPONENT=hyper-state - - <<: *_deploy_gem - env: COMPONENT=hyper-component - - <<: *_deploy_gem - env: COMPONENT=hyper-model - - <<: *_deploy_gem - env: COMPONENT=hyper-operation - - <<: *_deploy_gem - env: COMPONENT=hyper-router - - <<: *_deploy_gem - env: COMPONENT=hyper-spec - - <<: *_deploy_gem - env: COMPONENT=hyper-store - - <<: *_deploy_gem - env: COMPONENT=rails-hyperstack - - <<: *_deploy_gem - env: COMPONENT=hyperstack-config + # - <<: *_test_gem_pg + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 DB=hyper_mesh_test_db + # - <<: *_test_gem_pg + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 DB=hyper_mesh_test_db + # - <<: *_test_gem_pg + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 DB=hyper_mesh_test_db + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 + + # # - <<: *_test_gem + # # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' + # # - <<: *_test_gem_pg + # # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' TASK=part1 DB=hyper_mesh_test_db + # # - <<: *_test_gem_pg + # # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' TASK=part2 DB=hyper_mesh_test_db + # # - <<: *_test_gem_pg + # # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' TASK=part3 DB=hyper_mesh_test_db + # # - <<: *_test_gem + # # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' RAILS_VERSION='~>5.0' + + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' + # - <<: *_test_gem_pg + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' TASK=part1 DB=hyper_mesh_test_db + # - <<: *_test_gem_pg + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' TASK=part2 DB=hyper_mesh_test_db + # - <<: *_test_gem_pg + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' TASK=part3 DB=hyper_mesh_test_db + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>6.0.0' + + # # - <<: *_test_gem + # # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # # - <<: *_test_gem + # # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # # - <<: *_test_gem_pg + # # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 DB=hyper_mesh_test_db + # # - <<: *_test_gem_pg + # # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 DB=hyper_mesh_test_db + # # - <<: *_test_gem_pg + # # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 DB=hyper_mesh_test_db + # # - <<: *_test_gem + # # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + + # # - <<: *_deploy_gem + # # env: COMPONENT=hyper-i18n + # # - <<: *_deploy_gem + # # env: COMPONENT=hyper-trace + # # - <<: *_deploy_gem + # # env: COMPONENT=hyper-state + # # - <<: *_deploy_gem + # # env: COMPONENT=hyper-component + # # - <<: *_deploy_gem + # # env: COMPONENT=hyper-model + # # - <<: *_deploy_gem + # # env: COMPONENT=hyper-operation + # # - <<: *_deploy_gem + # # env: COMPONENT=hyper-router + # # - <<: *_deploy_gem + # # env: COMPONENT=hyper-spec + # # - <<: *_deploy_gem + # # env: COMPONENT=hyper-store + # # - <<: *_deploy_gem + # # env: COMPONENT=rails-hyperstack + # # - <<: *_deploy_gem + # # env: COMPONENT=hyperstack-config diff --git a/RELEASE-PROCESS.md b/RELEASE-PROCESS.md new file mode 100644 index 000000000..820b4a853 --- /dev/null +++ b/RELEASE-PROCESS.md @@ -0,0 +1,11 @@ +To release a new gem set: + +1. Make sure CI is passing +2. Do a global search for `VERSION = '1.0.alpha1.'` and replace with the next point release. +3. Add a new row to `/current-status.md` with the current data +4. Add a new file in `/release-notes` (see existing files for format) +5. Update `README.md` with last entry from `current-status.md` +6. Commit all the above. <- VERY IMPORTANT TO DO THIS BEFORE ADDING THE TAG +7. `git tag 1.0.alpha1.` +8. `git push --tags origin edge` <- once build passes gems will be released!!! +9. Add a new release note (add release in git hub): Copy contents of the release note you created diff --git a/current-status.md b/current-status.md index b71db28b2..5f3b632cc 100644 --- a/current-status.md +++ b/current-status.md @@ -1,176 +1,25 @@ -### Current Status: Working towards 0.1 release. See Roadmap for details. +### Current Status -[![Build Status](https://travis-ci.org/hyperstack-org/hyperstack.svg?branch=edge)](https://travis-ci.org/hyperstack-org/hyperstack) +[![Build Status](https://travis-ci.com/hyperstack-org/hyperstack.svg?branch=edge)](https://travis-ci.com/hyperstack-org/hyperstack) +[![Gem Version](https://badge.fury.io/rb/rails-hyperstack.svg)](https://badge.fury.io/rb/rails-hyperstack) +[![Slack](https://img.shields.io/badge/slack-hyperstack.org/slack-yellow.svg?logo=slack)]([![Slack](https://img.shields.io/badge/slack-hyperstack.org/slack-yellow.svg?logo=slack)](https://join.slack.com/t/hyperstack-org/shared_invite/enQtNTg4NTI5NzQyNTYyLWQ4YTZlMGU0OGIxMDQzZGIxMjNlOGY5MjRhOTdlMWUzZWYyMTMzYWJkNTZmZDRhMDEzODA0NWRkMDM4MjdmNDE)) -`hyperstack-config`, `hyper-store`, and `hyper-component` are now following the public interface conventions. +We now are issuing 1.0 release candidates weekly until all issues are either closed or moved to post 1.0 release status. **Your opinion matters, plus take some time to up/down vote or comment on issues of interest.** -I.e. to create a component you now do this: -```ruby -class MyComponent - include Hyperstack::Component - ... -end -``` +| Release
Date | Version | Open
Issues | Documentation
Sections
Draft Ready | Documentation
Sections
WIP | +|--------------|---------|-------------|-------|------| +| April 12, 2021 | 1.0.alpha1.8 | 128 | 36 | 9 | +| April 5, 2021 | 1.0.alpha1.7 | 147 | 35 | 10 | +| March 29, 2021 | 1.0.alpha1.6 | 167 | 35 | 10 | -The philosophy is that you will probably have a base class defined like this: +> Open issues includes enhancements, documentation, and discussion issues as well as few bugs. +> +> The documentation WIP (work in progress) numbers are approx, as more sections may be added. -```ruby -class HyperComponent - include Hyperstack::Component -end -``` +### Contributing -Which you can then inherit from. - -The bigger change is that the state mechanism has now been greatly simplified, but you can choose when to move -into the future by your choice of which module to include: - -```ruby -class HyperComponent - include Hyperstack::Component - # to use the current state/store syntax in your components: - include Hyperstack::Legacy::Store -end - -class HyperStore - # to use the legacy state store syntax in your stores: - include Hyperstack::Legacy::Store -end -``` - -To use the new hotness change the includes: - -```ruby -class HyperComponent - include Hyperstack::Component - # to use the current state/store syntax in your components: - include Hyperstack::State::Observable -end - -class HyperStore - # to use the legacy state store syntax in your stores: - include Hyperstack::State::Observable -end -``` - -In summary you will need to update your hyperloop/hyperstack components and store folders to have `hyper_component.rb` -and `hyper_store.rb` files. And then update your components and stores to reference your application defined `HyperComponent` -and `HyperStore` classes. - -### The new world of state: - -Its great, its exciting, and its sooo much easier: - -Each ruby object has *state*, defined by its instance variables. Hyperstack does not define *any new state concepts*. From -now on you just use instance variables in your components and other objects as you normally would. - -The one caveat is that you have to *tell* the system when you are *mutating* state, and when some external entity is -*observing* your state. - -The `Hyperstack::State::Observable` module provides a handful of methods to make this very easy. - -Here is an example (compare to the state example on the [Hyperstack.org home page](https://hyperstack.org/)) - -```ruby -class UsingState < HyperComponent - - # Our component has two instance variables to keep track of what is going on - # @show - if true we will show an input box, otherwise the box is hidden - # @input_value - tracks what the user is typing into the input box. - # We use the mutate method to signal all observers when the state changes. - - render(DIV) do - # the button method returns an HTML element - # .on(:click) is an event handeler - button.on(:click) { mutate @show = !@show } - DIV do - input - output - easter_egg - end if @show - end - - def button - BUTTON(class: 'ui primary button') do - @show ? 'Hide' : 'Show' - end - end - - def input - DIV(class: 'ui input fluid block') do - INPUT(type: :text).on(:change) do |evt| - # we are updating the value per keypress - mutate @input_value = evt.target.value - end - end - end - - def output - # this will re-render whenever input_value changes - P { "#{@input_value}" } - end - - def easter_egg - H2 {'you found it!'} if @input_value == 'egg' - end -end -``` - -So to make our instance variables work with components we just need to call `mutate` when the state changes. - -Here is a very simple store that is just a global click counter - -```ruby -class Click - include Hyperstack::State::Observable - class << self - def count - observe @count ||= 0 - end - def inc - mutate @count = count + 1 - end - def count=(x) - mutate @count = x - end - def reset - mutate @count = 0 - end - end -end -``` - -Now any component can access and change the counter by calling `Click.count`, `Click.inc` and `Click.reset` as needed. - -The `observe` and `mutate` methods take no params (handy for adding to the end of a method), a single param as shown above, -or a block in which case the entire block will be executed before signaling the rest of the system. - -That is all there is to it, but to make things easier `Observable` contains some other helper methods which we can use: - -```ruby -class Click - include Hyperstack::State::Observable - class << self - observer(:count) { @count ||= 0 } - state_writer :count - mutator(:inc) { count = count + 1 } - mutator(:reset) { count = 0 } - end -end -``` - -The `observer` and `mutator` methods create a method wrapped in `observe` or `mutate` block. - -In addition there are `state_accessor`, `state_reader` and `state_writer` methods that work just like `attr_accessor` methods -except access is wrapped in the appropriate `mutate` or `observe` method. - -The methods can be used either at the class or instance level as needed. - -Because stateful components use the same `Observable` module all the above methods are available to help structure your -components nicely. - -Notice in the component example we never use `observe` that is because by definition components always `observe` their own -state automatically so you don't need to. +Any and all contributions are welcome. Pull requests on documentation issues large are small are easy to do, and of great help. +There are also a number of issues marked as `Good First Issue`. diff --git a/docs/.bookignore b/docs/.bookignore new file mode 100644 index 000000000..a6de900e3 --- /dev/null +++ b/docs/.bookignore @@ -0,0 +1 @@ +specs/ diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..df6ce0e64 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +_book/ +node_modules/ diff --git a/docs/README.md b/docs/README.md index 81826a073..de3c244e0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,5 @@ # Welcome -## Hyperstack - Hyperstack is a Ruby-based DSL and modern web toolkit for building spectacular, interactive web applications fast! * **One language** throughout the client and server. All Ruby code is compiled by [Opal](https://opalrb.com/) into JavaScript automatically. @@ -23,17 +21,20 @@ end In the code above, if the `good_books` scope changed \(even on the server\), the UI would update automatically. That's the magic of React and Isomorphic Models with bi-directional data at work! -## Website and documentation +## Website and Documentation + + -* Website: [hyperstack.org](https://hyperstack.org) +While we have over 1000 specs passing in 3 different configurations, and several large apps using Hyperstack, documentation is lagging. If you see this icon it means we are working hard to +get the docs up to the same state as the code. -Our website serves as a Hyperstack example application. All the doc content is loaded dynamically from this repo and converted to HTML on the fly. It uses React Semantic UI and a client-side JavaScript full-text search engine. Its a Rails app hosted on Heroku. +Chapters without the work-in-progress flag are still draft, and any issues are greatly appreciated, or better yet follow the `Edit on Github` link, make your proposed corrections, and submit a pull request. ## Setup and installation You can be up and running in **less than 5 minutes**. Just follow the simple setup guide for a new Rails application all correctly configured and ready to go with Hyperstack. -* Setup and Installation: https://docs.hyperstack.org/installation/man-installation +* Setup and Installation: https://docs.hyperstack.org/rails-installation Beyond the installation we strongly suggest new developers work trough the [todo tutorial](https://docs.hyperstack.org/tutorial). As it gives a minimal understanding of the Hyperstack framework. @@ -45,17 +46,15 @@ Hyperstack is supported by a friendly, helpful community, both for users, and co ## Roadmap -Hyperstack is evolving; we are improving it all the time. As much as we love Ruby today, we see ourselves embracing new languages in the future. [Crystal](https://crystal-lang.org/) perhaps? We are also watching [Wasm](https://webassembly.org/) carefully. +We are currently driving towards our 1.0 release. Currently we are at 1.0.alpha1.5 release candidate. There is a list of 163 open issues including some bugs, documentation issues and many requested enhancements. The plan is to triage the issues and do a weekly release until the issues are closed or deemed not needed for a 1.0 release. -Please see the [ROADMAP](https://github.com/hyperstack-org/hyperstack/blob/edge/ROADMAP.md) for more information. +Please consider contributing by grabbing a "good first issue", or just adding your thoughts, thumbs up, or down on any issues that interest you. ## Contributing In general, if you would like to help in any way, please read the [CONTRIBUTING](https://github.com/hyperstack-org/hyperstack/blob/edge/CONTRIBUTING.md) file for suggestions. System setup for the development of Hyperstack itself is documented in this file. -More specifically, we have a [Feature Matrix](https://github.com/hyperstack-org/hyperstack/blob/edge/docs/feature_matrix.md) that needs to be filled with missing features. The idea is that you can check here what the implementation status is of a Ruby \(on Rails\) feature. And if you have the time and skill you're more then encouraged to implement or fix one or two. But if you're not in a position to contribute code, just expanding and maintaining this table would be excellent. - ## Links * Rubygems: [https://rubygems.org/profiles/hyperstack](https://rubygems.org/profiles/hyperstack) @@ -66,14 +65,3 @@ More specifically, we have a [Feature Matrix](https://github.com/hyperstack-org/ ## License Hyperstack is developed and released under the MIT License. See the [LICENSE](https://github.com/hyperstack-org/hyperstack/blob/edge/LICENSE) file for further details. - -## History - -Hyperstack is an evolution of [Ruby-Hyperloop](https://github.com/ruby-hyperloop). We decided to rename the project to drop the Ruby suffix and also took the opportunity to simplify the repos and project overall. -Hyperloop was started by the developers of the reactrb gem. - -* Old website: [http://ruby-hyperloop.org/](http://ruby-hyperloop.org/) -* Old Github: [https://github.com/ruby-hyperloop](https://github.com/ruby-hyperloop) -* Legacy branch: [https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy](https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy) -* Legacy install script: [https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy/install](https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy/install) - diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 479a46bd8..3a639f989 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,32 +1,47 @@ # Table of contents * [Welcome](README.md) -* [Installation](installation/README.md) - * [Installation](installation/man-installation.md) - * [Configuration](installation/config.md) - * [Upgrading from legacy Hyperloop](installation/upgrading.md) -* [Client DSL](client-dsl/README.md) - * [HTML & CSS DSL](client-dsl/html-css.md) - * [Component DSL](client-dsl/components.md) +* [Rails Installation and Configuration](rails-installation/README.md) + * [Prerequisites](rails-installation/prerequisites.md) + * [Using the Hyperstack Installer](rails-installation/using-the-installer.md) + * [Using the Generators](rails-installation/generators.md) + * [File Structure](rails-installation/file-structure.md) + * [Routing and Mounting Components](rails-installation/routing-and-mounting-components.md) + * [Other Rails Configuration Details](rails-installation/other-details.md) + * [Why Rails? Other Frameworks?](rails-installation/why-rails.md) +* [HyperComponent](client-dsl/README.md) + * [Component Classes](client-dsl/component-basics.md) + * [HTML Tags & CSS Classes](client-dsl/html-css.md) + * [Component Children, Keys and Fragments](client-dsl/component-details.md) + * [Component Params](client-dsl/params.md) * [Lifecycle Methods](client-dsl/lifecycle-methods.md) - * [State](client-dsl/state.md) - * [Event Handlers](client-dsl/event-handlers.md) + * [Component State](client-dsl/state.md) + * [Events and Callbacks](client-dsl/events-and-callbacks.md) + * [Interlude: Tic Tac Toe](client-dsl/interlude-tic-tac-toe.md) + * [Recovering from Errors](client-dsl/error-recovery.md) * [JavaScript Components](client-dsl/javascript-components.md) - * [Client-side Routing](client-dsl/hyper-router.md) - * [Stores](client-dsl/hyper-store.md) * [Elements and Rendering](client-dsl/elements-and-rendering.md) + * [Summary of Methods](client-dsl/methods.md) + * [List of Predefined Tags & Components](client-dsl/predefined-tags.md) + * [Predefined Events](client-dsl/predefined-events.md) + * [Notes](client-dsl/notes.md) * [Further Reading](client-dsl/further-reading.md) -* [Isomorphic DSL](isomorphic-dsl/README.md) - * [Isomorphic Models](isomorphic-dsl/hyper-model.md) - * [Isomorphic Operations](isomorphic-dsl/hyper-operation.md) - * [Policies](isomorphic-dsl/hyper-policy.md) -* [Development Workflow](development-workflow/README.md) +* [HyperState](hyper-state/README.md) +* [HyperRouter](hyper-router/README.md) +* [HyperModel](hyper-model/README.md) +* [Operations](operations/README.md) +* [Policies](policies/README.md) +* [Internationalization](internationalization/README.md) +* [Development Tools, Workflow and Procedures](development-workflow/README.md) * [Debugging](development-workflow/debugging.md) - * [Internationalization](development-workflow/hyper-i18n.md) - * [HyperSpec](development-workflow/hyper-spec.md) - * [Tools](development-workflow/tools.md) + * [HyperTrace](development-workflow/hyper-trace.md) + * [HyperSpec](development-workflow/hyper-spec/README.md) + * [Installation](development-workflow/hyper-spec/01-installation.md) + * [Tutorial](development-workflow/hyper-spec/02-tutorial.md) + * [Methods and Features](development-workflow/hyper-spec/03-methods-and-features.md) + * [Using with Rack](development-workflow/hyper-spec/04-using-with-rack.md) + * [Deploy To Heroku](development-workflow/deploy-to-heroku.md) * [Tutorial](tutorial/README.md) * [TodoMVC Tutorial Part I](tutorial/todo.md) * [TodoMVC Tutorial Part II](tutorial/todo-part-2.md) * [Community](community.md) - diff --git a/docs/book.json b/docs/book.json new file mode 100644 index 000000000..ed92e2c6e --- /dev/null +++ b/docs/book.json @@ -0,0 +1,3 @@ +{ + "plugins": ["folding-content"] +} diff --git a/docs/client-dsl/README.md b/docs/client-dsl/README.md index c4c706201..22aaa021d 100644 --- a/docs/client-dsl/README.md +++ b/docs/client-dsl/README.md @@ -1,19 +1,31 @@ -# Client DSL +Your Hyperstack Application is built from a series of *Components* which are Ruby Classes that display portions of the UI. Hyperstack Components are implemented using [React](https://reactjs.org/), and can interoperate with existing React components and libraries. Here is a simple example that displays a ticking clock: -## HTML DSL and Hyperstack Component classes - -A key design goal of the DSL \(Domain Specific Language\) is to make it work seamlessly with the rest of Ruby and easy to work with HTML elements and Components. Additionally, the DSL provides an abstraction layer between your code and the underlying \(fast moving\) technology stack. Hyperstack always uses the very latest versions of React and React Router yet our DSL does not change often. We believe that a stable DSL abstraction is an advantage. - -This documentation will cover the following core concepts: - -+ [HTML & CSS DSL](html-css.md) which provided Ruby implementations of all of the HTML and CSS elements -+ [Component DSL](components.md) is a Ruby DSL which wraps ReactJS Components -+ [Lifecycle Methods](lifecycle-methods.md) are methods which are invoked before, during and after rendering -+ [State](state.md) governs all rendering in ReactJS -+ [Event Handlers](event-handlers.md) allow any HTML element or Component can respond to an event -+ [JavaScript Components](javascript-components.md) for the full universe of JS libraries in your Ruby code -+ [Client-side Routing](hyper-router.md) a Ruby DSL which wraps ReactRouter -+ [Stores](hyper-store.md) for application level state and Component communication -+ [Elements and Rendering](elements-and-rendering.md) which are seldom used but useful to know -+ [Further Reading](further-reading.md) on React and Opal +```ruby +# Components inherit from the HyperComponent base class +# which supplies the DSL to translate from Ruby into React +# function calls +class Clock < HyperComponent + # Components can be parameterized. + # in this case you can override the default + # with a different format + param format: "%m/%d/%Y %I:%M:%S" + # After_mount is an example of a life cycle method. + after_mount do + # Before the component is first rendered (mounted) + # we setup a periodic timer that will update the + # current_time instance variable every second. + # The mutate method signals a change in state + every(1.second) { mutate @current_time = Time.now } + end + # every component has a render block which describes what will be + # drawn on the UI + render do + # Components can render other components or primitive HTML or SVG + # tags. Components also use their state to determine what to render, + # in this case the @current_time instance variable + DIV { @current_time.strftime(format) } + end +end +``` +The following chapters cover these aspects and more in detail. diff --git a/docs/client-dsl/component-basics.md b/docs/client-dsl/component-basics.md new file mode 100644 index 000000000..e780ababc --- /dev/null +++ b/docs/client-dsl/component-basics.md @@ -0,0 +1,159 @@ +The Hyperstack Component DSL is a set of class and instance methods that are used to describe React components and render the user-interface. + +The following sections give a brief orientation to the structure of a component and the methods that are used to define and control its behavior. + +## Defining a Component + +Hyperstack Components are Ruby classes that inherit from the `HyperComponent` base class: + +```ruby +class MyComponent < HyperComponent + ... +end +``` +> **[More on the HyperComponent base class](notes.md#the-hypercomponent-base-class)** + +## The `render` Callback + +At a minimum every *concrete* component class must define a `render` block which generates one or more child elements. Those children may in turn have an arbitrarily deep structure. **[More on concrete and abstract components...](notes.md#abstract-and-concrete-components)** + +```ruby +class Component < HyperComponent + render do + DIV { } # render an empty div + end +end +``` +> The code between `do` and `end` and { .. } are called blocks. **[More here...](notes.md#blocks-in-ruby)** + +To save a little typing you can also specify the top level element to be rendered: + +```ruby +class Component < HyperComponent + render(DIV, class: 'my-special-class') do + # everything will be rendered in a div + end +end +``` + +To create a component instance, you reference its class name as a method call from another component. This creates a new instance, passes any parameters and proceeds with the component lifecycle. + +> **[The actual type created is an Element, read on for details...](notes.md#component-instances)** + +```ruby +class FirstComponent < HyperComponent + render do + NextComponent() # ruby syntax requires either () or {} following the class name + end +end +``` + +> While a component is defined as a class, and a rendered component is an instance of that class, we do not in general use the `new` method, or need to modify the components `initialize` method. + +### Invoking Components + +> Note: when invoking a component **you must have** a \(possibly empty\) parameter list or \(possibly empty\) block. +> ```ruby +MyCustomComponent() # ok +MyCustomComponent {} # ok +MyCustomComponent # <--- breaks +> ``` + +## Component Params + +A component can receive params to customize its look and behavior: + +```Ruby +class SayHello < HyperComponent + param :to + render(DIV, class: :hello) do + "Hello #{to}!" + end +end + +... + + SayHello(to: "Joe") +``` + +Components can receive new params, causing the component to update. **[More on Params ...](params.md)**. + +## Component State + +Components also have *state*, which is stored in instance variables. You signal a state change using the `mutate` method. Component state is a fundamental concept covered **[here](state.md)**. + + +## Life Cycle Callbacks + +A component may be updated during its life time due to either changes in state or receiving new params. You can hook into the components life cycle using the +the life cycle methods. Two of the most common lifecycle methods are `before_mount` and `after_mount` that are called before a component first renders, and +just after a component first renders respectively. + +```RUBY +class Clock < HyperComponent + param format: "%m/%d/%Y %I:%M:%S" + after_mount do + every(1.second) { mutate @current_time = Time.now } + end + render do + DIV { @current_time.strftime(format) } + end +end +``` + +The complete list of life cycle methods and their syntax is discussed in detail in the **[Lifecycle Methods](lifecycle-methods.md)** section. + +## Events, Event Handlers, and Component Callbacks + +Events such as mouse clicks trigger callbacks, which can be attached using the `on` method: + +```ruby +class ClickCounter < HyperComponent + before_mount { @clicks = 0 } + def adverb + @clicks.zero? ? 'please' : 'again' + end + render(DIV) do + BUTTON { "click me #{adverb}" } + .on(:click) { mutate @clicks += 1 } # attach a callback + DIV { "I've been clicked #{pluralize(@clicks, 'time')}" } if @clicks > 0 + end +end +``` + +This example also shows how events and state mutations work together to change the look of the display. It also demonstrates that because a HyperComponent +is just a Ruby class you can define helper methods, use conditional logic, and call on predefined methods like `pluralize`. + +In addition components can fire custom events, and make callbacks to the upper level components. **[More details ...](events-and-callbacks.md)** + +## Application Structure + +Your Application is built out of many smaller components using the above features to control the components behavior and communicate between components. To conclude this section let's create a simple Avatar component which shows a profile picture and username using the Facebook Graph API. + +```ruby +class Avatar < HyperComponent + param :user_name + + render(DIV) do + # for each param a method with the same name is defined + ProfilePic(user_name: user_name) + ProfileLink(user_name: user_name) + end +end + +class ProfilePic < HyperComponent + param :user_name + + # note that in Ruby blocks can use do...end or { ... } + render { IMG(src: "https://graph.facebook.com/#{user_name}/picture") } +end + +class ProfileLink < HyperComponent + param :user_name + render do + A(href: "https://www.facebook.com/#{user_name}") do + user_name + end + end +end +``` diff --git a/docs/client-dsl/component-details.md b/docs/client-dsl/component-details.md new file mode 100644 index 000000000..3b4f27365 --- /dev/null +++ b/docs/client-dsl/component-details.md @@ -0,0 +1,128 @@ +### Children + +Components often have child components. If you consider HTML tags like `DIV`, `UL`, and `TABLE` +you will see you are already familiar with this concept: + +```ruby +DIV(id: 1) do + SPAN(class: :span_1) { 'hi' } + SPAN(class: :span_2) { 'there' } +end +``` +Here we have a `DIV` that receives one param, an id equal to 1 and has two child *elements* - the two spans. + +The `SPAN`s each have one param (its css class) and has one child *element* - a string to render. + +Hopefully at this point the DSL is intuitive to read, and you can see that this will generate the following HTML: +```HTML +
+ hi + there +
+``` + +### Dynamic Children + +Children do not have to be statically generated. Let's sort a string of text +into individual word counts and display it in a list: + +```ruby +# assume text is a string of text +UL do + word_count(text).each_with_index do |word, count| + LI { "#{count} - #{word}" } + end +end +``` +In this case you can see that we don't determine the actual number or contents of the `LI` children until runtime. + +>**[The `word_count` method...](notes.md#word-count-method)** + +>Dynamically generating components creates a new concept called ownership. **[More here...](notes.md#ownership)** + +### Keys + +In the above example what would happen if the contents of `text` were dynamically changing? For +example if it was associated with a text box that the user was typing into, and we updated `text` +whenever a word was entered. + +In this case as the user typed new words, the `word_count` would be updated and the list would change. +However actually only the contents of one of the list items (`LI` blocks) would actually change, and +perhaps the sort order. We don't need to redraw the whole list, just the one list item that changed, +and then perhaps shuffle two of the items. This is going to be much faster than redrawing the whole +list. + +Like React, Hyperstack provides a special `key` param that can identify child elements so that the +rendering engine will know that while the content and order may change on some children, it can easily +identify the ones that are the same: + +```ruby + LI(key: word) { "#{count} - #{word}"} +``` + +You don't have to stress out too much about keys, its easy to add them later. Just keep the concept in +mind when you are generating long lists, tables, and divs with many children. + +> **[More on how Hyperstack generates keys...](notes.md#generating-keys)** + +### Rendering Children + +Application defined components can also receive and render children. +A component's `children` method returns an enumerable that is used to access the *unrendered* children of a component. The children can then be rendered +using the `render` method which will merge any additional parameters and +render the child. + +```ruby +class Indenter < HyperComponent + render(DIV) do + IndentLine(by: 10) do # see IndentLine below + DIV {"Line 1"} + DIV {"Line 2"} + DIV {"Line 3"} + end + end +end + +class IndentLine < HyperComponent + param by: 20, type: Integer + + render(DIV) do + children.each_with_index do |child, i| + child.render(style: {"margin-left" => by*i}) + end + end +end +``` + +### Rendering Multiple Values and the FRAGMENT component + +A render block may generate multiple values. React assumes when a Component generates multiple items, the item order and quantity may +change over time and so will give a warning unless each element has a key: + +```ruby +class ListItems < HyperComponent + render do + # without the keys you would get a warning + LI(key: 1) { 'item 1' } + LI(key: 2) { 'item 2' } + LI(key: 3) { 'item 3' } + end +end + +# somewhere else: + UL do + ListItems() + end +``` + +If you are sure that the order and number of elements will not change over time you may wrap the items in the `FRAGMENT` pseudo component: + +```ruby +class ListItems < HyperComponent + render(FRAGMENT) do + LI { 'item 1' } + LI { 'item 2' } + LI { 'item 3' } + end +end +``` diff --git a/docs/client-dsl/components.md b/docs/client-dsl/components.md deleted file mode 100644 index 8b28ce662..000000000 --- a/docs/client-dsl/components.md +++ /dev/null @@ -1,427 +0,0 @@ -# Hyperstack Component DSL - -Hyperstack Component DSL is a set of class and instance methods that are used to describe React components and render the user-interface. - -The DSL has the following major areas: - -* The `Hyperstack::Component` mixin or your own `HyperComponent` class -* HTML DSL elements -* Component Lifecycle Methods \(`before_mount`, `render`, `after_mount`, `after_update`, `after_error`\) -* The `param` and `render` methods -* Event handlers -* Miscellaneous methods - -## HyperComponent - -Hyperstack Components classes include the `Hyperstack::Component` mixin or \(for ease of use\) are a subclass of a `HyperComponent` class which includes the mixin: - -```ruby -class HyperComponent - include Hyperstack::Component -end - -class AnotherComponent < HyperComponent -end -``` - -At a minimum every component class must define a `render` method which returns **one single** child element. That child may in turn have an arbitrarily deep structure. - -```ruby -class Component < HyperComponent - render do - DIV { } # render an empty div - end -end -``` - -You may also include the top level element to be rendered: - -```ruby -class Component < HyperComponent - render(DIV, class: 'my-special-class') do - # everything will be rendered in a div - end -end -``` - -To render a component, you reference its class name in the DSL as a method call. This creates a new instance, passes any parameters proceeds with the component lifecycle. - -```ruby -class FirstComponent < HyperComponent - render do - NextComponent() # ruby syntax requires either () or {} following the class name - end -end -``` - -Note that you should never redefine the `new` or `initialize` methods, or call them directly. The equivalent of `initialize` is the `before_mount` method. - -### Invoking Components - -> Note: when invoking a component **you must have** a \(possibly empty\) parameter list or \(possibly empty\) block. - -```ruby -MyCustomComponent() # ok -MyCustomComponent {} # ok -MyCustomComponent # <--- breaks -``` - -## Multiple Components - -So far, we've looked at how to write a single component to display data. Next let's examine one of React's finest features: composability. - -### Motivation: Separation of Concerns - -By building modular components that reuse other components with well-defined interfaces, you get much of the same benefits that you get by using functions or classes. Specifically you can _separate the different concerns_ of your app however you please simply by building new components. By building a custom component library for your application, you are expressing your UI in a way that best fits your domain. - -### Composition Example - -Let's create a simple Avatar component which shows a profile picture and username using the Facebook Graph API. - -```ruby -class Avatar < HyperComponent - param :user_name - - render(DIV) do - # the user_name param has been converted to @UserName immutable instance variable - ProfilePic(user_name: @UserName) - ProfileLink(user_name: @UserName) - end -end - -class ProfilePic < HyperComponent - param :user_name - - render do - IMG(src: "https://graph.facebook.com/#{@UserName}/picture") - end -end - -class ProfileLink < HyperComponent - param :user_name - render do - A(href: "https://www.facebook.com/#{@UserName}") do - @UserName - end - end -end -``` - -### Ownership - -In the above example, instances of `Avatar` _own_ instances of `ProfilePic` and `ProfileLink`. In React, **an owner is the component that sets the `params` of other components**. More formally, if a component `X` is created in component `Y`'s `render` method, it is said that `X` is _owned by_ `Y`. As discussed earlier, a component cannot mutate its `params` — they are always consistent with what its owner sets them to. This fundamental invariant leads to UIs that are guaranteed to be consistent. - -It's important to draw a distinction between the owner-ownee relationship and the parent-child relationship. The owner-ownee relationship is specific to React, while the parent-child relationship is simply the one you know and love from the DOM. In the example above, `Avatar` owns the `div`, `ProfilePic` and `ProfileLink` instances, and `div` is the **parent** \(but not owner\) of the `ProfilePic` and `ProfileLink` instances. - -### Children - -When you create a React component instance, you can include additional React components or JavaScript expressions between the opening and closing tags like this: - -```ruby -Parent { Child() } -``` - -`Parent` can iterate over its children by accessing its `children` method. - -### Child Reconciliation - -**Reconciliation is the process by which React updates the DOM with each new render pass.** In general, children are reconciled according to the order in which they are rendered. For example, suppose we have the following render method displaying a list of items. On each pass the items will be completely re-rendered: - -```ruby -param :items -render do - # notice how the items param is accessed in CamelCase (to indicate that it is read-only) - items.each do |item| - PARA do - item[:text] - end - end -end -``` - -What if the first time items was `[{text: "foo"}, {text: "bar"}]`, and the second time items was `[{text: "bar"}]`? Intuitively, the paragraph `

foo

` was removed. Instead, React will reconcile the DOM by changing the text content of the first child and destroying the last child. React reconciles according to the _order_ of the children. - -### Dynamic Children - -The situation gets more complicated when the children are shuffled around \(as in search results\) or if new components are added onto the front of the list \(as in streams\). In these cases where the identity and state of each child must be maintained across render passes, you can uniquely identify each child by assigning it a `key`: - -```ruby - param :results, type: [Hash] # each result is a hash of the form {id: ..., text: ....} - render do - OL do - results.each do |result| - LI(key: result[:id]) { result[:text] } - end - end - end -``` - -When React reconciles the keyed children, it will ensure that any child with `key` will be reordered \(instead of clobbered\) or destroyed \(instead of reused\). - -The `key` should _always_ be supplied directly to the components in the array, not to the container HTML child of each component in the array: - -```ruby -# WRONG! -class ListItemWrapper < HyperComponent - param :data - render do - LI(key: data[:id]) { data[:text] } - end -end - -class MyComponent < HyperComponent - param :results - render do - UL do - result.each do |result| - ListItemWrapper data: result - end - end - end -end -``` - -```ruby -# CORRECT -class ListItemWrapper < HyperComponent - param :data - render do - LI { data[:text] } - end -end - -class MyComponent < HyperComponent - param :results - render do - UL do - results.each do |result| - ListItemWrapper key: result[:id], data: result - end - end - end -end -``` - -### The children method - -Along with params components may be passed a block which is used to build the components children. - -The instance method `children` returns an enumerable that is used to access the unrendered children of a component. - -```ruby -class Indenter < HyperComponent - render(DIV) do - IndentEachLine(by: 100) do # see IndentEachLine below - DIV {"Line 1"} - DIV {"Line 2"} - DIV {"Line 3"} - end - end -end - -class IndentEachLine < HyperComponent - param by: 20, type: Integer - - render(DIV) do - children.each_with_index do |child, i| - child.render(style: {"margin-left" => by*i}) - end - end -end -``` - -### Data Flow - -In React, data flows from owner to owned component through the params as discussed above. This is effectively one-way data binding: owners bind their owned component's param to some value the owner has computed based on its `params` or `state`. Since this process happens recursively, data changes are automatically reflected everywhere they are used. - -### Stores - -Managing state between components is best done using Stores as many Components can access one store. This saves passing data btween Components. Please see the [Store documentation](https://github.com/hyperstack-org/hyperstack/tree/a530e3955296c5bd837c648fd452617e0a67a6ed/docs/dsl-client/hyper-store/README.md) for details. - -### Reusable Components - -When designing interfaces, break down the common design elements \(buttons, form fields, layout components, etc.\) into reusable components with well-defined interfaces. That way, the next time you need to build some UI, you can write much less code. This means faster development time, fewer bugs, and fewer bytes down the wire. - -## Params - -The `param` method gives _read-only_ access to each of the scalar params passed to the Component. Params are accessed as instance methods on the Component. - -Within a React Component the `param` method is used to define the parameter signature of the component. You can think of params as the values that would normally be sent to the instance's `initialize` method, but with the difference that a React Component gets new parameters when it is re-rendered. - -Note that the default value can be supplied either as the hash value of the symbol, or explicitly using the `:default_value` key. - -Examples: - -```ruby -param :foo # declares that we must be provided with a parameter foo when the component is instantiated or re-rerendered. -param :foo, alias: :something # the alias name will be used for the param (instead of @Foo) -param :foo => "some default" # declares that foo is optional, and if not present the value "some default" will be used. -param foo: "some default" # same as above using ruby 1.9 JSON style syntax -param :foo, default: "some default" # same as above but uses explicit default key -param :foo, type: String # foo is required and must be of type String -param :foo, type: [String] # foo is required and must be an array of Strings -param foo: [], type: [String] # foo must be an array of strings, and has a default value of the empty array. -``` - -#### Accessing param values - -Params are accessible in the Component class as instance methods. - -For example: - -```ruby -class Hello < HyperComponent - # an immutable parameter, with a default of type String - param visitor: "World", type: String - - render do - "Hello #{visitor}" - end -end -``` - -### Immutable params - -A core design concept taken from React is that data flows down to child Components via params and params \(called props in React\) are immutable. - -In Hyperstack, there are **two exceptions** to this rule: - -* An instance of a **Store** \(passed as a param\) is mutable and changes to the state of the Store will cause a re-render -* An instance of a **Model** \(discussed in the Isomorphic section of these docs\) will also case a re-render when changed - -In the example below, clicking on the button will cause the Component to re-render \(even though `book` is a `param`\) because `book` is a Model. If `book` were not a Model \(or Store\) then the Component would not re-render. - -```ruby -class Likes < HyperComponent - param :book # book is an instance of the Book model - - render(DIV) do - P { "#{book.likes.count} likes" } - BUTTON { "Like" }.on(:click) { book.likes += 1} - end -end -``` - -> Note: Non-scalar params \(objects\) which are mutable through their methods are not read only. Care should be taken here as changes made to these objects will **not** cause a re-render of the Component. Specifically, if you pass a non-scalar param into a Component, and modify the internal data of that param, Hyperstack will not be notified to re-render the Component \(as it does not know about the internal structure of your object\). To achieve a re-render in this circumstance you will need to ensure that the parts of your object which are mutable are declared as state in a higher-order parent Component so that data can flow down from the parent to the child as per the React pattern. - -### Param Validation - -As your app grows it's helpful to ensure that your components are used correctly. We do this by allowing you to specify the expected ruby class of your parameters. When an invalid value is provided for a param, a warning will be shown in the JavaScript console. Note that for performance reasons type checking is only done in development mode. Here is an example showing typical type specifications: - -```ruby -class ManyParams < HyperComponent - param :an_array, type: [] # or type: Array - param :a_string, type: String - param :array_of_strings, type: [String] - param :a_hash, type: Hash - param :some_class, type: SomeClass # works with any class - param :a_string_or_nil, type: String, allow_nil: true -end -``` - -Note that if the param can be nil, add `allow_nil: true` to the specification. - -### Default Param Values - -React lets you define default values for your `params`: - -```ruby -class ManyParams < HyperComponent - param :an_optional_param, default: "hello", type: String, allow_nil: true -``` - -If no value is provided for `:an_optional_param` it will be given the value `"hello"` - -### Params of type Proc - -A Ruby `Proc` can be passed to a component like any other object. - -```ruby -param :all_done, type: Proc -... - # typically in an event handler -all_done(data).call -``` - -Proc params can be optional, using the `default: nil` and `allow_nil: true` options. Invoking a nil proc param will do nothing. This is handy for allowing optional callbacks. - -```ruby -class Alarm < HyperComponent - param :at, type: Time - param :notify, type: Proc - - after_mount do - @clock = every(1) do - if Time.now > at - notify.call - @clock.stop - end - force_update! - end - end - - render do - "#{Time.now}" - end -end -``` - -If for whatever reason you need to get the actual proc instead of calling it use `params.method(*symbol name of method*)` - -### Components as Params - -You can pass a Component as a `param` and then render it in the receiving Component. To create a Component without rendering it you use `.as_node`. This technique is used extensively in JavaScript libraries. - -```ruby -# in the parent Component... -button = MyButton().as_node -ButtonBar(button: button) - -class ButtonBar < HyperComponent - param :button - - render do - button.render - end -end -``` - -`as_node` can be attached to a component or tag, and removes the element from the rendering buffer and returns it. This is useful when you need store an element in some data structure, or passing to a native JS component. When passing an element to another Hyperstack Component `.as_node` will be automatically applied so you normally don't need it. - -`render` can be applied to the objects returned by `as_node` and `children` to actually render the node. - -```ruby -class Test < HyperComponent - param :node - - render do - DIV do - children.each do |child| - node.render - child.render - end - node.render - end - end -end -``` - -### Other Params - -A common type of React component is one that extends a basic HTML element in a simple way. Often you'll want to copy any HTML attributes passed to your component to the underlying HTML element. - -To do this use the `collect_other_params_as` method which will gather all the params you did not declare into a hash. Then you can pass this hash on to the child component - -```ruby -class CheckLink < HyperComponent - collect_other_params_as :attributes - render do - # we just pass along any incoming attributes - a(attributes) { '√ '.span; children.each &:render } - end -end -# CheckLink(href: "/checked.html") -``` - -Note: `collect_other_params_as` builds a hash, so you can merge other data in or even delete elements out as needed. - diff --git a/docs/client-dsl/elements-and-rendering.md b/docs/client-dsl/elements-and-rendering.md index acc024af4..52f7c8234 100644 --- a/docs/client-dsl/elements-and-rendering.md +++ b/docs/client-dsl/elements-and-rendering.md @@ -1,80 +1,77 @@ -# Elements and Rendering +This section documents some technical details of the interface between React and Hyperstack as well as some useful low level methods. -### React.create\_element +## The `Hyperstack::Component` Module -**Note: You almost never need to directly call `create_element`, the DSL, Rails, and jQuery interfaces take care of this for you.** +The `Hyperstack::Component` module can be included in any Ruby class, and will add the methods that interface between that class and React. Specifically it will -A React Element is a component class, a set of parameters, and a group of children. When an element is rendered the parameters and used to initialize a new instance of the component. ++ Define the class level methods such as param, render and the other lifecycle methods, ++ Provide the render DSL which has the same role as JSX but uses Ruby methods, ++ Provide a suitable Javascript class constructor that so that React will recognize the instances of the Component as React Elements -`React.create_element` creates a new element. It takes either the component class, or a string \(representing a built in tag such as div, or span\), the parameters \(properties\) to be passed to the element, and optionally a block that will be evaluated to build the enclosed children elements +The only major difference between the systems is that JSX compiles directly to React API calls (such as `createElement`) while Hyperstack executes an expression like `MyBigComponent(class: :red, some_param: :foo)` and directly calls `createElement` passing the `MyBigComponent` react class, and translating it as needed from Ruby to JS conventions. -```ruby -React.create_element("div", prop1: "foo", prop2: 12) { para { "hello" }; para { "goodby" } ) - # when rendered will generates

hello

goodby

-``` +As each React element is generated it is stored by Hyperstack in a *rendering buffer*, and when the component finishes the rendering block, the buffer is returned as the result of the components render callback. If the expression has a child block (like `DIV { 'hello' }`) the block is passed to the `createElement` as a the child function the same +as JSX would do. +When an expression like this is evaluated (**[see the full example in the section on params...](params.md#named-child-components-as-params)**) ```ruby -# dsl - creates element and pushes it into the rendering buffer -MyComponent(...params...) { ...optional children... } - -# dsl - component will NOT be placed in the rendering buffer -MyComponent(...params...) { ... }.as_node - -# in a rails controller - renders component as the view -render_component("MyComponent", ...params...) + Reveal(content: DIV { 'I came from the App' }) +``` +we need to *remove* the generated `DIV` element *out* of the rendering buffer before passing it to `Reveal`. This is done automatically by applying the `~` (remove) operator to the +`DIV` as it is passed on. -# in a rails view helper - renders component into the view (like a partial) -react_component("MyComponent", ...) +In general you will never have to manually use the remove (`~`) operator, as React's declarative nature makes storing elements for later use not as necessary as in more +procedural frameworks. -# from jQuery (Note Element is the Opal jQuery wrapper, not be confused with React::Element) -Element['#container'].render { MyComponent(...params...) { ...optional children... } } -``` +#### Creating Elements Programmatically -### React.is\_valid\_element? +Component classes (including tags like `DIV`) respond to two methods for programmatically creating elements: -```ruby -is_valid_element?(object) +```Ruby +# component_class evaluates to some Component Class +component_class.create_element() { } +component_class.insert_element() { } ``` +both methods return the generated element, the second also inserts into the current rendering buffer. -Verifies `object` is a valid react element. Note that `React::Element` wraps the React.js native class, `React.is_valid_element?` returns true for both classes unlike `object.is_a? React::Element` +#### Rendering to the DOM -### React.render +Sooner or later it has to end up in the DOM. If you are using Rails then Hyperstack includes several +methods to *mount* your components onto the display. See the Rails installation section for details. -```ruby -React.render(element, container) { puts "element rendered" } +Otherwise if using jQuery then you can use the `render` method: +```Ruby +Document.ready? do # ready runs when document is loaded + jQ['div#mount_point'].render(App) +end ``` -Render an `element` into the DOM in the supplied `container` and return a [reference](https://github.com/hyperstack-org/hyperstack/tree/a530e3955296c5bd837c648fd452617e0a67a6ed/docs/more-about-refs.html) to the component. +or do it completely yourself with the low level ReactAPI -The container can either be a DOM node or a jQuery selector \(i.e. Element\['\#container'\]\) in which case the first element is the container. - -If the element was previously rendered into `container`, this will perform an update on it and only mutate the DOM as necessary to reflect the latest React component. - -If the optional block is provided, it will be executed after the component is rendered or updated. +```Ruby +# somewhere in a JS onload page handler: +Hyperstack::Component::ReactAPI.render(App.create_element, `getElementById('mount_point')`) +``` -> Note: -> -> `React.render()` controls the contents of the container node you pass in. Any existing DOM elements inside are replaced when first called. Later calls use React’s DOM diffing algorithm for efficient updates. -> -> `React.render()` does not modify the container node \(only modifies the children of the container\). In the future, it may be possible to insert a component to an existing DOM node without overwriting the existing children. +#### React.unmount\_component\_at\_node -### React.unmount\_component\_at\_node +To remove a element that has been mounted: ```ruby -React.unmount_component_at_node(container) +Hyperstack::Component::ReactAPI.unmount_component_at_node(dom_container) ``` -Remove a mounted React component from the DOM and clean up its event handlers and state. If no component was mounted in the container, calling this function does nothing. Returns `true` if a component was unmounted and `false` if there was no component to unmount. +This removes a mounted component from the DOM and cleans up its event handlers and state. If no component was mounted in the container, calling this function does nothing. Returns `true` if a component was unmounted and `false` if there was no component to unmount. ### React.render\_to\_string ```ruby -React.render_to_string(element) +Hyperstack::Component::ReactAPI.render_to_string(element) ``` Render an element to its initial HTML. This is should only be used on the server for prerendering content. React will return a string containing the HTML. You can use this method to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes. -If you call `React.render` on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience. +If you call `ReactAPI.render` on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience. If you are using rails, then the prerendering functions are automatically performed. Otherwise you can use `render_to_string` to build your own prerendering system. @@ -86,50 +83,6 @@ React.render_to_static_markup(element) Similar to `render_to_string`, except this doesn't create extra DOM attributes such as `data-react-id`, that React uses internally. This is useful if you want to use React as a simple static page generator, as stripping away the extra attributes can save lots of bytes. -## Prerendering - -Prerendering will render your page on the server before sending it to the client. There are some limitations as there is no browser context and no JQuery available. Prerendering is very useful for static sites with complex HTML pages \(like this website\). - -**Prerendering is controllable at three levels:** - -* In the rails Hyperstack initializer you can say: - - ```ruby - Hyperstack.configuration do |config| - config.prerendering = :on # :off by default - end - ``` - -* In a route you can override the config setting by setting a default for Hyperstack\_prerendering: - -```ruby -get '/some_page', to: 'Hyperstack#some_page', defaults: {Hyperstack_prerendering: :off} # or :on -``` - -This allows you to override the prerendering option for specific pages. For example the application may have prererendering off by default \(via the config setting\) but you can still turn it on for a specific page. - -* You can override the route, and config setting using the Hyperstack-prerendering query param: - -```markup -http://localhost:3000/my_hyper_app/some_page?Hyperstack-prerendering=off -``` - -This is useful for development and testing. - -> Note: in the route you say Hyperstack\_prererendering but in the query string its Hyperstack-prerendering \(underscore vs. dash\). This is because of rails security protection when using defaults. - -## DSL Gotchas - -There are few gotchas with the DSL you should be aware of: - -React has implemented a browser-independent events and DOM system for performance and cross-browser compatibility reasons. We took the opportunity to clean up a few rough edges in browser DOM implementations. - -* All DOM properties and attributes \(including event handlers\) should be snake\_cased to be consistent with standard Ruby style. We intentionally break with the spec here since the spec is inconsistent. **However**, `data-*` and `aria-*` attributes [conform to the specs](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#data-*) and should be lower-cased only. -* The `style` attribute accepts a Hash with camelCased properties rather than a CSS string. This is more efficient, and prevents XSS security holes. -* All event objects conform to the W3C spec, and all events \(including submit\) bubble correctly per the W3C spec. See [Event System](hyper-component.md#event-handling-and-synthetic-events) for more details. -* The `onChange` event \(`on(:change)`\) behaves as you would expect it to: whenever a form field is changed this event is fired rather than inconsistently on blur. We intentionally break from existing browser behavior because `onChange` is a misnomer for its behavior and React relies on this event to react to user input in real time. -* Form input attributes such as `value` and `checked`, as well as `textarea`. - ### HTML Entities If you want to display an HTML entity within dynamic content, you will run into double escaping issues as React.js escapes all the strings you are displaying in order to prevent a wide range of XSS attacks by default. @@ -158,4 +111,3 @@ DIV("data-custom-attribute" => "foo") ```ruby DIV("aria-hidden" => true) ``` - diff --git a/docs/client-dsl/error-recovery.md b/docs/client-dsl/error-recovery.md new file mode 100644 index 000000000..357bd13e2 --- /dev/null +++ b/docs/client-dsl/error-recovery.md @@ -0,0 +1,83 @@ +### When things go wrong... + +The `rescues` lifecycle callbacks run when an error is raised from within or below a component. + +At the end of the rescue the component tree will be completely re-rendered from scratch. In its +simplest form we need nothing but an empty block. + +```ruby +class App < HyperComponent + render(DIV) do + H1 { "Welcome to Our App" } + ContentWhichFailsSometimes() + end + + rescues do + end +end +``` +When an error occurs it will be caught by +the rescues block, and `App` and all lower components will be re-generated (not just re-rendered). + +In most cases you may want to warn the user that something is going wrong, and also record some data +about the event: +```ruby +class App < HyperComponent + render(DIV) do + H1 { "Welcome to Our App" } + if @failure_fall_back + DIV { 'Whoops we had a little problem' } + BUTTON { 'retry' }.on(:click) { mutate @failure_fall_back = false } + else + ContentWhichFailsSometimes() + end + end + + rescues do |err| + @failure_fall_back = true + ReportError.run(err: err) + end +end +``` + +If you don't want to involve the user, then be careful: To prevent infinite loops the React engine +will not rescue failures occurring during the re-generation of the component tree. If not involving +the user you may want to consider how to insure that system state is completely reset in the rescue. + +The rescues method can also take explicit Error types to be rescued: + +```ruby + rescues StandardError, MyInternalError do |err| + ... + end +``` + +Like other lifecycle methods you can have multiple rescues in the same component: + +```ruby + rescues StandardError do |err| + # code for handling StandardError + end + rescues MyInternalError do |err| + # different code for handling MyInternalError + end +``` + +Like Ruby's rescue keyword, errors will be caught by the innermost component with a `rescues` callback that +handles that error. + +The data passed to the rescue handler is an array of two items, the Ruby error that was thrown, and details generated by the React engine. + +```ruby + rescues do |e, info| + # e is a Ruby error, and responds to backtrace, message, etc + # info is a hash currently with the single key :componentStack + # which is string representing the backtrace through the component + # hierarchy + end +``` + +## Caveats + +1. You cannot rescue errors raised in lifecycle handlers in the same component. Errors raised by lifecycle handlers in inner components are fine, just not in the same component as the rescue. +2. Errors raised in event handlers will neither stop the rendering cycle, nor will they be caught by a rescue callback. diff --git a/docs/client-dsl/events-and-callbacks.md b/docs/client-dsl/events-and-callbacks.md new file mode 100644 index 000000000..29f679b38 --- /dev/null +++ b/docs/client-dsl/events-and-callbacks.md @@ -0,0 +1,173 @@ +Params pass data downwards from owner to owned-by component. Data comes back upwards asynchronously +via *callbacks*, which are simply *Procs* passed as params into the owned-by component. + +> **[More on Ruby Procs here ...](notes.md#ruby-procs)** + +The upwards flow of data via callbacks is triggered by some event such as a mouse click, or input change: + +```ruby +class ClickDemo2 < HyperComponent + render do + BUTTON { "click" }.on(:click) { |evt| puts "I was clicked"} + end +end +``` + +When the `BUTTON` is clicked, the event (evt) is passed to the attached click handler. + +The details of the event object will be discussed below. + +### Firing Events from Components + +You can also define events in your components to communicate back to the owner: + +```ruby +class Clicker < HyperComponent + param title: "click" + fires :clicked + before_mount { @clicks = 0 } + render do + BUTTON { title }.on(:click) { clicked!(@clicks += 1) } + end +end + +class ClickDemo3 < HyperComponent + render(DIV) do + DIV { "I have been clicked #{pluralize(@clicks, 'times')}" } if @clicks + Clicker().on(:clicked) { |clicks| mutate @clicks = clicks } + end +end +``` + +Each time the `Clicker's` button is clicked it *fires* the clicked event, indicated +by the event name followed by a bang (!). + +The `clicked` event is received by `ClickDemo3`, and it updates its state. As you +can see events can send arbitrary data back out. + +> Notice also that Clicker does not call mutate. It could, but since the change in +`@clicks` is not used anywhere to control its display there is no need for Clicker +to mutate. + +### Relationship between Events and Params + +Notice how events (and callbacks in general as we will see) move data upwards, while +params move data downwards. We can emphasize this by updating our example: + +```ruby +class ClickDemo4 < HyperComponent + def title + @clicks ? "Click me again!" : "Let's start clicking!" + end + + render(DIV) do + DIV { "I have been clicked #{pluralize(@clicks, 'times')}" } if @clicks + Clicker(title: title).on(:clicked) { |clicks| mutate @clicks = clicks } + end +end +``` + +When `ClickDemo4` is first rendered, the `title` method will return "Let's start clicking!", and +will be passed to `Clicker`. + +The user will (hopefully so we can get on with this chapter) click the button, which will +fire the event. The handler in `ClickDemo4` will mutate its state, causing title to change +to "Click me again!". The new value of the title param will be passed to `Clicker`, and `Clicker` +will re-render with the new title. + +**Events (and callbacks) push data up, params move data down.** + +### Callbacks and Proc Params + +Under the hood Events are simply params of type `Proc`, with the `on` and `fires` method +using some naming conventions to clean things up: + +```ruby +class IntemittentButton < HyperComponent + param :frequency + param :pulse, type: Proc + before_mount { @clicks = 0 } + render do + BUTTON( + on_click: lambda {} do + @clicks += 1 + pulse(@clicks) if (@clicks % frequency).zero? + end + ) { 'click me' } + end +end + +class ClickDemo5 < HyperComponent + render do + IntermittentButton( + frequency: 5, + pulse: -> (total_clicks) { alert "you are clicking a lot" } + ) + end +end +``` + +There is really no reason not to use the `fires` method to declare Proc params, and +no reason not use the `on` method to attach handlers. Both will keep your code clean and tidy. + +### Naming Conventions + +The notation `on(:click)` is short for passing a proc to a param named `on_click`. In general `on(:xxx)` will pass the +given block as the `on_xxx` parameter in a Hyperstack component and `onXxx` in a JS component. + +All the built-in events and many React libraries follow the `on_xxx` (or `onXxx` in JS) convention. However even if a library does not use +this convention you can still attach the handler using `on('')`. Whatever string is inside the `<..>` brackets will +be used as the param name. + +Likewise the `fires` method is shorthand for creating a `Proc` param following the `on_xxx` naming convention: + +`fires :foo` is short for +`param :on_foo, type: Proc, alias: :foo!` + +### The `Event` Object + +UI events like `click` send an object of class `Event` to the handler. Some of the data you can get from `Event` objects are: + ++ `target` : the DOM object that was the target of the UI interaction ++ `target.value` : the value of the DOM object ++ `key_code` : the key pressed (for key_down and key_up events) + +> **[Refer to the Predefined Events section for complete details...](predefined-events.md)** + +### Other Sources of Events + +Besides the UI there are several other sources of events: + ++ Timers ++ HTTP Requests ++ Hyperstack Operations ++ Websockets ++ Web Workers + +The way you receive events from these sources depends on the event. Typically though the method will either take a block, or callback proc, or in many cases will return a Promise. +Regardless, the event handler will do one of three things: mutate some state within the component, fire an event to a higher level component, or update some shared store. + +> For details on updating shared stores, which is often the best answer **[see the chapter on HyperState...](../hyper-state/README.md)** + +You have seen the `every` method used to create events throughout this chapter, here is an example with an HTTP post (which returns a promise.) + +```ruby +class SaveButton < HyperComponent + fires :saved + fires :failed + render do + BUTTON { "Save" } + .on(:click) do + # Posting to some non-hyperstack endpoint for example + # Data is our class holding some data + Hyperstack::HTTP.post( + END_POINT, payload: Data.to_payload + ).then do |response| + saved!(response.json) + end.fail do |response| + failed!(response.json) + end + end + end +end +``` diff --git a/docs/client-dsl/html-css.md b/docs/client-dsl/html-css.md index df6ed1876..4dca9ec02 100644 --- a/docs/client-dsl/html-css.md +++ b/docs/client-dsl/html-css.md @@ -1,32 +1,28 @@ # HTML and CSS DSL -## HTML DSL - ### HTML elements -A Hyperstack user-interface is composed of HTML elements, conditional logic and Components. +Each Hyperstack component renders a series of HTML (and SVG) elements, using Ruby expressions to control the output. ```ruby UL do - 10.times { |n| LI { "Number #{n}" }} + 5.times { |n| LI { "Number #{n}" }} end ``` -> **Notice that the HTML elements \(BUTTON, DIV, etc.\) are in CAPS**. We know this is bending the standard Ruby style rules, but we think it reads better this way. - -For example, to render a `
`: +For example ```ruby DIV(class: 'green-text') { "Let's gets started!" } ``` -Would create the following HTML: +would create the following HTML: ```markup
Let's gets started!
``` -And to render a table: +And this would render a table: ```ruby TABLE(class: 'ui celled table') do @@ -47,21 +43,19 @@ TABLE(class: 'ui celled table') do end ``` -The following HTML elements are available: +**[See the predefined tags summary for the complete list of HTML and SVG elements.](predefined-tags.md)** -```markup -A ABBR ADDRESS AREA ARTICLE ASIDE AUDIO B BASE BDI BDO BIG BLOCKQUOTE BODY BR BUTTON CANVAS CAPTION CITE CODE COL COLGROUP DATA DATALIST DD DEL DETAILS DFN DIALOG DIV DL DT EM EMBED FIELDSET FIGCAPTION FIGURE FOOTER FORM H1 H2 H3 H4 H5 H6 HEAD HEADER HR HTML I IFRAME IMG INPUT INS KBD KEYGEN LABEL LEGEND LI LINK MAIN MAP MARK MENU MENUITEM META METER NAV NOSCRIPT OBJECT OL OPTGROUP OPTION OUTPUT P PARAM PICTURE PRE PROGRESS Q RP RT RUBY S SAMP SCRIPT SECTION SELECT SMALL SOURCE SPAN STRONG STYLE SUB SUMMARY SUP TABLE TBODY TD TEXTAREA TFOOT TH THEAD TIME TITLE TR TRACK U UL VAR VIDEO WBR -``` +### Naming Conventions -And also the SVG elements: +To distinguish between HTML and SVG tags, builtin components and application defined components the following +naming conventions are followed: -```markup -CIRCLE CLIPPATH DEFS ELLIPSE G LINE LINEARGRADIENT MASK PATH PATTERN POLYGON POLYLINE RADIALGRADIENT RECT STOP SVG TEXT TSPAN -``` ++ `ALLCAPS` denotes a HTML, SVG or builtin React psuedo components such as `FRAGMENT`. ++ `CamelCase` denotes an application defined component class like `TodoList`. ### HTML parameters -You can pass any expected parameter to a HTML element: +You can pass any expected parameter to a HTML or SVG element: ```ruby A(href: '/') { 'Click me' } # Click me @@ -82,8 +76,25 @@ P(class: :bright) { } P(class: [:bright, :blue]) { } # class='bright blue' ``` -For `style` you need to pass a hash: +For `style` you need to pass a hash using the **[React style conventions](https://reactjs.org/docs/dom-elements.html#style):** + +```ruby +P(style: { display: item[:some_property] == "some state" ? :block : :none }) +``` + +### Complex Arguments + +You can pass multiple hashes which will be merged, and any individual symbols +(or strings) will be treated as `=true`. For example ```ruby -PARA(style: { display: item[:some_property] == "some state" ? :block : :none }) -``` \ No newline at end of file +A(:flag, {href: '/'}, class: 'my_class') +``` + +will generate + +```HTML + +``` + +> **[more on passing hashes to methods](notes.md#ruby-hash-params)** diff --git a/docs/client-dsl/hyper-store.md b/docs/client-dsl/hyper-store.md deleted file mode 100644 index 2ea22eba0..000000000 --- a/docs/client-dsl/hyper-store.md +++ /dev/null @@ -1,123 +0,0 @@ -# Stores - -A core concept behind React is that Components contain their own state and pass state down to their children as params. React re-renders the interface based on those state changes. Each Component is discreet and only needs to worry about how to render itself and pass state down to its children. - -Sometimes however, at an application level, Components need to be able to share information or state in a way which does not adhere to this strict parent-child relationship. - -Some examples of where this can be necessary are: - -* Where a child needs to pass a message back to its parent. An example would be if the child component is an item in a list, it might need to inform it's parent that it has been clicked on. -* When Hyperstack models are passed as params, child components might change the values of fields in the model, which might be rendered elsewhere on the page. -* There has to be a place to store non-persisted, global application-level data; like the ID of the currently logged in user or a preference or variable that affects the whole UI. - -Taking each of these examples, there are ways to accomplish each: - -* Child passing a message to parent: the easiest way is to pass a `Proc` as a param to the child from the parent that the child can `call` to pass a message back to the parent. This model works well when there is a simple upward exchange of information \(a child telling a parent that it has been selected for example\). You can read more about Params of type Proc in the Component section of these docs. If howevere, you find yourself adding overusing this method, or passing messages from child to grandparent then you have reached the limits of this method and a Store would be a better option \(read about Stores in this section.\) -* Models are stores. An instance of a model can be passed between Components, and any Component using the data in a Model to render the UI will re-render when the Model data changes. As an example, if you had a page displaying data from a Model and let's say you have an edit button on that page \(which invokes a Dialog \(Modal\) based Component which receives the model as a param\). As the user edits the Model fields in Dialog, the underlying page will show the changes as they are made as the changes to Model fields will be observed by the parent Components. In this way, Models act very much like Stores. -* Stores are where global, application wide state can exist in singleton classes that all Components can access or as class instances objects which hold data and state. **A Store is a class or an instance of a class which holds state variables which can affect a re-render of any Component observing that data.** - -In technical terms, a Store is a class that includes the `include Hyperstack::State::Observable` mixin, which just adds the `mutate` and `observe` primitive methods \(plus helpers built on top of them\). - -In most cases, you will want class level instance variables that share data across components. Occasionally you might need multiple instances of a store that you can pass between Components as params \(much like a Model\). - -As an example, let's imagine we have a filter field on a Menu Bar in our application. As the user types, we want the user interface to display only the items which match the filter. As many of the Components on the page might depend on the filter, a singleton Store is the perfect answer. - -```ruby -# app/hyperstack/stores/item_store.rb -class ItemStore - include Hyperstack::State::Observable - - class << self - def filter=(f) - mutate @filter = f - end - - def filter - observe @filter || '' - end - end -end -``` - -In Our application code, we would use the filter like this: - -```ruby -# the TextField on the Menu Bar could look like this: -TextField(label: 'Filter', value: ItemStore.filter).on(:change) do |e| - ItemStore.filter = e.target.value -end - -# elsewhere in the code we could use the filter to decide if an item is added to a list -show_item(item) if item.name.include?(ItemStore.filter) -``` - -## The observe and mutate methods - -As with Components, you `mutate` an instance variable to notify React that the Component might need to be re-rendered based on the state change of that object. Stores are the same. When you `mutate` and instance variable in Store, all Components that are observing that variable will be re-rendered. - -`observe` records that a Component is observing an instance variable in a Store and might need to be re-rendered if the variable is mutated in the future. - -> If you `mutate` an instance variable outside of a Component, you need to `observe` it because, for simplicity, a Component observe their own instance vaibales. - -The `observe` and `mutate` methods take: - -* a single param as shown above -* a string of params \(`mutate a=1, b=2`\) -* or a block in which case the entire block will be executed before signalling the rest of the system -* no params \(handy for adding to the end of a method\) - -## Helper methods - -To make things easier the `Hyperstack::State::Observable` mixin contains some useful helper methods: - -The `observer` and `mutator` methods create a method wrapped in `observe` or `mutate` block. - -* `observer` -* `mutator` - -```ruby -mutator(:inc) { @count = @count + 1 } -mutator(:reset) { @count = 0 } -``` - -The `state_accessor`, `state_reader` and `state_writer` methods work just like `attr_accessor` methods except access is wrapped in the appropriate `mutate` or `observe` method. These methods can be used either at the class or instance level as needed. - -* `state_reader` -* `state_writer` -* `state_accessor` - -Finally there is the `toggle` method which does what it says on the tin. - -* `toggle` toggle\(:foo\) === mutate @foo = !@foo - -```ruby -class ClickStore - include Hyperstack::State::Observable - - class << self - observer(:count) { @count ||= 0 } - state_writer :count - mutator(:inc) { @count = @count + 1 } - mutator(:reset) { @count = 0 } - end -end -``` - -### Initializing class variables in singleton Store - -You can keep the logic around initialization in your Store. Remember that in Ruby your class instance variables can be initialized as the class is defined: - -```ruby -class CardStore - include Hyperstack::State::Observable - - @show_card_status = true - @show_card_details = false - - class << self - state_accessor :show_card_status - state_accessor :show_card_details - end -end -``` - diff --git a/docs/client-dsl/interlude-tic-tac-toe.md b/docs/client-dsl/interlude-tic-tac-toe.md new file mode 100644 index 000000000..ec0058ee3 --- /dev/null +++ b/docs/client-dsl/interlude-tic-tac-toe.md @@ -0,0 +1,193 @@ +At this point if you have been reading sequentially through these chapters you know enough to put together a simple tic-tac-toe game. + +### The Game Board + +The board is represented by an array of 9 cells. Cell 0 is the top left square, and cell 8 is the bottom right. + +Each cell will contain nil, an `:X` or an `:O`. + +### Displaying the Board + +The `DisplayBoard` component displays a board. `DisplayBoard` accepts a `board` param, and will fire back a `clicked_at` event when the user clicks one of the squares. + +A small helper function `draw_squares` draws an individual square which is displayed as a `BUTTON`. A click handler is attached which +will fire the `clicked_at` event with the appropriate cell id. + +Notice that `DisplayBoard` has no internal state of its own. That is handled by the `DisplayGame` component. + +```ruby +class DisplayBoard < HyperComponent + param :board + fires :clicked_at + + def draw_square(id) + BUTTON(class: :square, id: id) { board[id] } + .on(:click) { clicked_at!(id) } + end + + render(DIV) do + (0..6).step(3) do |row| + DIV(class: :board_row) do + (row..row + 2).each { |id| draw_square(id) } + end + end + end +end +``` + +### The Game State + +The `DisplayGame` component has two state variables: ++ `@history` which is an array of boards, each board being the array of cells. ++ `@step` which is the current step in the history (we begin at zero) + +`@step` and `@history` allows the player to move backwards or forwards and replay parts of the game. + +These are initialized in the `before_mount` callback. Because Ruby will adjust the array size as needed +and return nil if an array value is not initialized, we can simply initialize the board to an empty array. + +There are three *reader* methods that read the state: + ++ `player` returns the current player's token. The first player is always `:X` so even steps +are `:X`, and odd steps are `:O`. ++ `current` returns the board at the current step. ++ `history` uses state_reader to encapsulate the history state. + +Encapsulated access to state in reader methods like this is not necessary but is good practice + +```ruby +class DisplayGame < HyperComponent + before_mount do + @history = [[]] + @step = 0 + end + + def player + @step.even? ? :X : :O + end + + def current + @history[@step] + end + + state_reader :history +end +``` + +### Calculating the Winner Based on the Game State + +We also have a `current_winner?` method that will return the winning player or nil based on the value of the current board: + +```ruby +class DisplayGame < HyperComponent + WINNING_COMBOS = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6] + ] + + def current_winner? + WINNING_COMBOS.each do |a, b, c| + return current[a] if current[a] && current[a] == current[b] && current[a] == current[c] + end + false + end +end +``` + +### Mutating the Game State + +There are two mutator methods that change state: ++ `handle_click!` is called with the id of the square when a user clicks on a square. ++ `jump_to!` moves the user back and forth through the history. + +The `handle_click!` mutator first checks to make sure that no one has already won at the current step, and that +no one has played in the cell that the user clicked on. If either of these conditions is true `handle_click!` +returns, no mutation is signaled and nothing changes. + +> If we had wanted to return AND signal a state mutation we would use the Ruby `next` keyword instead of `return`.s + +To update the board `handle_click!` duplicates the squares; adds the player's token to the cell; makes a new +history with the new squares on the end, and finally updates the value of `@step`. + +> We like to use the convention where practical of ending mutator methods with a bang (!) so that readers of the +code are aware that these will change state. + +```ruby + +class DisplayGame < HyperComponent + mutator :handle_click! do |id| + board = history[@step] + return if current_winner? || board[id] + + board = board.dup + board[id] = player + @history = history[0..@step] + [board] + @step += 1 + end + + mutator(:jump_to!) { |step| @step = step } +end +``` + +### The Game Display + +Now we have a couple of helper methods to build parts of the game display. + ++ `moves` creates the list items that allow the user to move back and forth through the history. ++ `status` provides the play state + +```ruby +class DisplayGame < HyperComponent + def moves + return unless history.length > 1 + + history.length.times do |move| + LI(key: move) { move.zero? ? "Go to game start" : "Go to move ##{move}" } + .on(:click) { jump_to!(move) } + end + end + + def status + if (winner = current_winner?) + "Winner: #{winner}" + else + "Next player: #{player}" + end + end +end +``` + +And finally our render method which displays the Board and the game info: + +```ruby +class DisplayGame < HyperComponent + render(DIV, class: :game) do + DIV(class: :game_board) do + DisplayBoard(board: current) + .on(:clicked_at, &method(:handle_click!)) + end + DIV(class: :game_info) do + DIV { status } + OL { moves } + end + end +end +``` + +> `&method` turns an instance method into a Proc rather than having to say `{ |id| handle_click(id) }` + +### Summary + +This small game uses everything covered in the previous sections: HTML Tags, Component Classes, Params, Events and Callbacks, and State. +The project was derived from this React tutorial: https://reactjs.org/tutorial/tutorial.html. +You may want to compare our Ruby code with the React original. + + +The following sections cover reference materials, and some advanced information. You may want to skip to the HyperState section which +will use this example to show how state can be encapsulated, extracted and shared resulting in easier to understand and maintain code. diff --git a/docs/client-dsl/javascript-components.md b/docs/client-dsl/javascript-components.md index 8ee31bc39..106f72336 100644 --- a/docs/client-dsl/javascript-components.md +++ b/docs/client-dsl/javascript-components.md @@ -2,98 +2,57 @@ Hyperstack gives you full access to the entire universe of JavaScript libraries and components directly within your Ruby code. -Everything you can do in JavaScript is simple to do in Ruby; this includes passing parameters between Ruby and JavaScript and even passing Ruby methods as JavaScript callbacks. See the JavaScript section for more information. +Everything you can do in JavaScript is simple to do in Opal-Ruby; this includes passing parameters between Ruby and JavaScript and even passing Ruby methods as JavaScript callbacks. -While it is quite possible to develop large applications purely in Hyperstack Components with a ruby back end like rails, you may eventually find you want to use some pre-existing React Javascript library. Or you may be working with an existing React-JS application, and want to just start adding some Hyperstack Components. - -Either way you are going to need to import Javascript components into the Hyperstack namespace. Hyperstack provides both manual and automatic mechanisms to do this depending on the level of control you need. - -## Importing React Components - -Let's say you have an existing React Component written in Javascript that you would like to access from Hyperstack. - -Here is a simple hello world component: - -```javascript -window.SayHello = React.createClass({ - displayName: "SayHello", - render: function render() { - return React.createElement("div", null, "Hello ", this.props.name); - } -}) -``` - -Assuming that this component is loaded some place in your assets, you can then access this from Hyperstack by creating a wrapper Component: - -```ruby -class SayHello < HyperComponent - imports 'SayHello' -end - -class MyBigApp < HyperComponent - render(DIV) do - # SayHello will now act like any other Hyperstack component - SayHello name: 'Matz' - end -end -``` - -The `imports` directive takes a string \(or a symbol\) and will simply evaluate it and check to make sure that the value looks like a React component, and then set the underlying native component to point to the imported component. +> **[For more information on writing Javascript within your Ruby code...](notes.md#javascript)** ## Importing Javascript or React Libraries Importing and using React libraries from inside Hyperstack is very simple and very powerful. Any JavaScript or React based library can be accessible in your Ruby code. -Using Webpacker \(or Webpack\) there are just a few simple steps: +Using Webpacker there are just a few simple steps: * Add the library source to your project using `yarn` or `npm` * Import the JavaScript objects you require -* Package your bundle with `webpack` * Use the JavaScript or React component as if it were a Ruby class -Here is an example of setting up [Material UI](https://material-ui.com/): +Here is an example using the [Material UI](https://material-ui.com/) library: Firstly, you install the library: ```text -// with yarn yarn add @material-ui/core - -// or with npm -npm install @material-ui/core ``` -Next you import the objects you plan to us \(or you can import the whole library\) +Next you import the objects you plan to use: -```ruby -# app/javascript/packs/hyperstack.js - -# to import the whole library -import * as Mui from '@material-ui/core'; -global.Mui = Mui; +```javascript +// app/javascript/packs/client_and_server.js -# or if you just want one component from the library -import Button from '@material-ui/core/Button'; -global.Button = Button; +// to import the whole library +Mui = require('@material-ui/core') +// or to import a single component +Button = require('@material-ui/core/Button') ``` -The run webpack to build your bundle: +Theoretically webpacker will detect the change and rebuild everything, but you might have to do the following: -```text -bin/webpack +``` +bin/webpack # rebuild the webpacks +rm -rf tmp/cache # clear the cached sprockets files ``` Now you can use Material UI Components in your Ruby code: ```ruby # if you imported the whole library -Mui.Button(variant: :contained, color: :primary) { "Click me" }.on(:click) do - alert 'you clicked the button!' +Mui::Button(variant: :contained, color: :primary) { "Click me" }.on(:click) do + alert 'you clicked the primary button!' end # if you just imported the Button component -Button(variant: :contained, color: :primary) { "Click me" }.on(:click) do - alert 'you clicked the button!' +Button(variant: :contained, color: :secondary) { "Click me" }.on(:click) do + alert 'you clicked the secondary button!' end ``` @@ -103,12 +62,19 @@ Libraries used often with Hyperstack projects: * [Semantic UI](https://react.semantic-ui.com/) A React wrapper for the Semantic UI stylesheet * [ReactStrap](https://reactstrap.github.io/) Bootstrap 4 React wrapper +### Making Custom Wrappers - WORK IN PROGRESS ... + + Hyperstack will automatically import Javascript components and component libraries as discussed above. Sometimes for +complex libraries that you will use a lot it is useful to add some syntactic sugar to the wrapper. + +This can be done using the `imports` directive and the `Hyperstack::Component::NativeLibrary` superclass. + ### Importing Image Assets via Webpack -If you store your images in app/javascript/images directory and want to display them in components, please add the following code to app/javascript/packs/application.js +If you store your images in `app/javascript/images` directory and want to display them in components, please add the following code to `app/javascript/packs/application.js` ```javascript -var webpackImagesMap = {}; +webpackImagesMap = {}; var imagesContext = require.context('../images/', true, /\.(gif|jpg|png|svg)$/i); function importAll (r) { @@ -116,8 +82,6 @@ function importAll (r) { } importAll(imagesContext); - -global.webpackImagesMap = webpackImagesMap; ``` The above code creates an images map and stores it in webpackImagesMap variable. It looks something like this @@ -129,26 +93,20 @@ The above code creates an images map and stores it in webpackImagesMap variable. } ``` -Add the following helper +Add the following helpers to your HyperComponent class ```ruby # app/hyperstack/helpers/images_import.rb -module ImagesImport - def img_src(filepath) - img_map = Native(`webpackImagesMap`) - img_map["./#{filepath}"] - end -end -``` - -Include it into HyperComponent - -```ruby -require 'helpers/images_import' class HyperComponent + def self.img_src(file_path) # for use outside a component + @img_map ||= Native(`webpackImagesMap`) + @img_map["./#{file_path}"] + end + def img_src(file_path) # for use in a component + HyperComponent.img_src(file_path) + end ... - include ImagesImport end ``` @@ -159,210 +117,42 @@ IMG(src: img_src('logo.png')) # app/javascript/images/logo.png IMG(src: img_src('landing/some_image.png')) # app/javascript/images/landing/some_image.png ``` -## The dom\_node method - -Returns the HTML dom\_node that this component instance is mounted to. Typically used in the `after_mount` method to setup linkages to external libraries. - -Example: - -TODO - write example - -## The `as_node` and `to_n` methods - -Sometimes you need to create a Component without rendering it so you can pass it as a parameter of a method. This model is used often in the React world. - -The example below is taken from Semantic UI, building a [Tab Component](https://react.semantic-ui.com/modules/tab/#types-basic) with multiple tabs: - -Here is the Javascript example: +## jQuery +Hyperstack comes with a jQuery wrapper that you can optionally load. First add jQuery using yarn: +```shell +yarn add jquery +``` +then insure jQuery is required in your client_only.js packs file: ```javascript -import React from 'react' -import { Tab } from 'semantic-ui-react' - -const panes = [ - { menuItem: 'Tab 1', render: () => Tab 1 Content }, - { menuItem: 'Tab 2', render: () => Tab 2 Content }, -] - -const TabExampleBasic = () => - -export default TabExampleBasic +// app/javascript/packs/client_only.js +jQuery = require('jquery'); ``` - -And here is the same example converted to Ruby: - +finally require it in your hyper_component.rb file: ```ruby -# notice we use .as_node to create the Component without rendering it -tab_1 = Sem.TabPane do - P { 'Tab 1 Content' } -end.as_node - -tab_2 = Sem.TabPane do - P { 'Tab 2 Content' } -end.as_node - -# notice how we use .to_n to convert the Ruby component to a native JS object -panes = [ {menuItem: 'Tab 1', render: -> { tab_1.to_n } }, - {menuItem: 'Tab 2', render: -> { tab_2.to_n } } -] - -Sem.Tab(panes: panes.to_n ) -``` - -## Including React Source - -If you are in the business of importing components with a tool like Webpack, then you will need to let Webpack \(or whatever dependency manager you are using\) take care of including the React source code. Just make sure that you are _not_ including it on the ruby side of things. Hyperstack is currently tested with React versions 13, 14, and 15, so its not sensitive to the version you use. - -However it gets a little tricky if you are using the react-rails gem. Each version of this gem depends on a specific version of React, and so you will need to manually declare this dependency in your Javascript dependency manager. Consult this [table](https://github.com/reactjs/react-rails/blob/master/VERSIONS.md) to determine which version of React you need. For example assuming you are using `npm` to install modules and you are using version 1.7.2 of react-rails you would say something like this: +# app/hyperstack/hyper_component.rb -```bash -npm install react@15.0.2 react-dom@15.0.2 --save +require 'hyperstack/component/jquery' ``` +You can access jQuery anywhere in your code using the `jQ` method. +For details see https://github.com/opal/opal-jquery -## Single Page Application CRUD example +> Note most of the time you will not need to manipulate the dom directly. -Rails famously used scaffolding for Model CRUD \(Create, Read, Update and Delete\). There is no scaffolding in Hyperstack today, so here is an example which demonstrates how those simple Rails pages would work in Hyperstack. -This example uses components from the [Material UI](https://material-ui.com/) framework, but the principals would be similar for any framework \(or just HTML elements\). - -In this example, we will have a table of users and the ability to add new users or edit a user from the list. As the user edits the values in the UserDialog, they will appear in the underlying table. You can avoid this behaviour if you choose by copying the values in the `before_mount` of the UserDialog, so you are not interacting with the model directly. Firstly the table of users: - -```ruby -class UserIndex < HyperComponent - render(DIV, class: 'mo-page') do - UserDialog(user: User.new) # this will render as a button to add users - Table do - TableHead do - TableRow do - TableCell { 'Name' } - TableCell { 'Gender' } - TableCell { 'Edit' } - end - end - TableBody do - user_rows - end - end - end - - def user_rows - User.each do |user| # note User is a Hyperstack model (see later in the Isomorphic section) - TableRow do - TableCell { "#{user.first_name} #{user.last_name}" } - TableCell { user.is_female ? 'Female' : 'Male' } - # note the use of key so React knows which Component this refers to - # this is very important for performance and to ensure the component is used - TableCell { UserDialog(user: user, key: user.id) } # this will render as an edit button - end - end - end -end -``` +## The dom\_node method -Then we need the actual Dialog \(Modal\) component: +Returns the HTML dom\_node that this component instance is mounted to. For example you can use `dom_node` to +set the focus on an input after its mounted. ```ruby -class UserDialog < HyperComponent - param :user - - before_mount do - @open = false +class FocusedInput < HyperComponent + others :others + after_mount do + jQ[dom_node].focus end - render do - if @open - render_dialog - else - edit_or_new_button.on(:click) { mutate @open = true } - end - end - - def render_dialog - Dialog(open: @open, fullWidth: false) do - DialogTitle do - 'User' - end - DialogContent do - content - error_messages if user.errors.any? - end - DialogActions do - actions - end - end - end - - def edit_or_new_button - if user.new? - Fab(size: :small, color: :primary) { Icon { 'add' } } - else - Fab(size: :small, color: :secondary) { Icon { 'settings' } } - end - end - - def content - FormGroup(row: true) do - # note .to_s to specifically cast to a String - TextField(label: 'First Name', value: user.first_name.to_s).on(:change) do |e| - user.first_name = e.target.value - end - TextField(label: 'Last Name', value: user.last_name.to_s).on(:change) do |e| - user.last_name = e.target.value - end - end - - BR() - - FormLabel(component: 'legend') { 'Gender' } - RadioGroup(row: true) do - FormControlLabel(label: 'Male', - control: Radio(value: false, checked: !is_checked(user.is_female)).as_node.to_n) - FormControlLabel(label: 'Female', - control: Radio(value: true, checked: is_checked(user.is_female)).as_node.to_n) - end.on(:change) do |e| - user.is_female = e.target.value - end - end - - def is_checked value - # we need a true or false and not an object - value ? true : false - end - - def actions - Button { 'Cancel' }.on(:click) { cancel } - - if user.changed? && validate_content - Button(color: :primary, variant: :contained, disabled: (user.saving? ? true : false)) do - 'Save' - end.on(:click) { save } - end - end - - def save - user.save(validate: true).then do |result| - mutate @open = false if result[:success] - end - end - - def cancel - user.revert - mutate @open = false - end - - def error_messages - user.errors.full_messages.each do |message| - Typography(variant: :h6, color: :secondary) { message } - end - end - - def validate_content - return false if user.first_name.to_s.empty? - return false if user.last_name.to_s.empty? - return false if user.is_female.nil? - - true + INPUT(others) end end ``` - diff --git a/docs/client-dsl/lifecycle-methods.md b/docs/client-dsl/lifecycle-methods.md index dc397fa7c..4ea48e32d 100644 --- a/docs/client-dsl/lifecycle-methods.md +++ b/docs/client-dsl/lifecycle-methods.md @@ -5,14 +5,14 @@ A component may define lifecycle methods for each phase of the components lifecy * `before_mount` * `render` * `after_mount` -* `before_receive_props` +* `before_new_params` * `before_update` +* `render` will be called again here * `after_update` * `before_unmount` +* `rescues` The `rescues` callback is described **[here...](error-recovery.md)** -> Note: At a minimum, one `render` method must be defined and must return just one HTML element. - -All the Component Lifecycle methods may take a block or the name of an instance method to be called. +All the Component Lifecycle methods (except `render`) may take a block or the name(s) of instance method(s) to be called. The `render` method always takes a block. ```ruby class MyComponent < HyperComponent @@ -21,7 +21,7 @@ class MyComponent < HyperComponent end render do - # return just one HTML element + # return some elements end before_unmount :cleanup # call the cleanup method before unmounting @@ -29,18 +29,15 @@ class MyComponent < HyperComponent end ``` -Except for the render method, multiple lifecycle methods may be defined for each lifecycle phase, and will be executed in the order defined, and from most deeply nested subclass outwards. - -## Lifecycle Methods - -A component class may define lifecycle methods for specific points in a component's lifecycle. +Except for `render`, multiple lifecycle callbacks may be defined for each lifecycle phase, and will be executed in the order defined, and from most deeply nested subclass outwards. Note the implication that the callbacks are inherited, which can be useful in creating **[abstract component classes](notes#abstract-and-concrete-components)**. ### Rendering -The lifecycle revolves around rendering the component. As the state or parameters of a component changes, its render method will be called to generate the new HTML. +The lifecycle revolves around rendering the component. As the state or parameters of a component change, its render callback will be executed to generate the new HTML. ```ruby -render do .... +render do + ... end ``` @@ -62,73 +59,55 @@ render do end ``` -The purpose of the render method is syntactic. Many components consist of a static outer container with possibly some parameters, and most component's render method by necessity will be longer than the normal _10 line_ ruby style guideline. The render method solves both these problems by allowing the outer container to be specified as part of the method parameter \(which reads very nicely\) and because the render code is now specified as a block you avoid the 10 line limitation, while encouraging the rest of your methods to adhere to normal ruby style guides - ### Before Mounting \(first render\) ```ruby -before_mount do ... +before_mount do + ... end ``` Invoked once when the component is first instantiated, immediately before the initial rendering occurs. This is where state variables should be initialized. -This is the only life cycle method that is called during `render_to_string` used in server side pre-rendering. +This is the only life cycle callback run during `render_to_string` used in server side pre-rendering. ### After Mounting \(first render\) ```ruby -after_mount do ... +after_mount do + ... end ``` -Invoked once, only on the client \(not on the server\), immediately after the initial rendering occurs. At this point in the lifecycle, you can access any refs to your children \(e.g., to access the underlying DOM representation\). The `after_mount` methods of children components are invoked before that of parent components. +Invoked once, only on the client \(not on the server during prerendering\), immediately after the initial rendering occurs. At this point in the lifecycle, you can access any refs to your children \(e.g., to access the underlying DOM representation\). The `after_mount` callbacks of child components are invoked before that of parent components. -If you want to integrate with other JavaScript frameworks, set timers using the `after` or `every` methods, or send AJAX requests, perform those operations in this method. Attempting to perform such operations in before\_mount will cause errors during prerendering because none of these operations are available in the server environment. +If you want to integrate with other JavaScript frameworks, set timers using the `after` or `every` methods, or send AJAX requests, perform those operations in this callback. Attempting to perform such operations in before\_mount will cause errors during prerendering because none of these operations are available in the server environment. ### Before Receiving New Params ```ruby -before_receive_props do |new_params_hash| ... +before_new_params do |new_params_hash| + ... end ``` -Invoked when a component is receiving _new_ params \(React.js props\). This method is not called for the initial render. +Invoked when a component is receiving _new_ params \(React props\). This method is not called for the initial render. -Use this as an opportunity to react to a prop transition before `render` is called by updating any instance or state variables. The new\_props block parameter contains a hash of the new values. +Use this as an opportunity to react to receiving new param values before `render` is called by updating any instance variables. The new\_params block parameter contains a hash of the new values. ```ruby -before_receive_props do |next_props| - mutate @likes_increasing = (next_props[:like_count] > @LikeCount) +before_new_params do |next_params| + @likes_increasing = (next_params[:like_count] > like_count) end ``` > Note: There is no analogous method `before_receive_state`. An incoming param may cause a state change, but the opposite is not true. If you need to perform operations in response to a state change, use `before_update`. -TODO: The above needs to be checked and a better example provided. PR very welcome. - -### Controlling Updates - -Normally Hyperstack will only update a component if some state variable or param has changed. To override this behavior you can redefine the `should_component_update?` instance method. For example, assume that we have a state called `funky` that for whatever reason, we cannot update using the normal `state.funky!` update method. So what we can do is override `should_component_update?` call `super`, and then double check if the `funky` has changed by doing an explicit comparison. - -```ruby -class RerenderMore < HyperComponent - def should_component_update?(new_params_hash, new_state_hash) - super || new_state_hash[:funky] != state.funky - end -end -``` - -Why would this happen? Most likely there is integration between new Hyperstack Components and other data structures being maintained outside of Hyperstack, and so we have to do some explicit comparisons to detect the state change. - -Note that `should_component_update?` is not called for the initial render or when `force_update!` is used. - -> Note to react.js readers. Essentially Hyperstack assumes components are "well behaved" in the sense that all state changes will be explicitly declared using the state update \("!"\) method when changing state. This gives similar behavior to a "pure" component without the possible performance penalties. To achieve the standard react.js behavior add this line to your class `def should_component_update?; true; end` - ### Before Updating \(re-rendering\) ```ruby -before_update do ... +before_update do + ... end ``` @@ -137,7 +116,8 @@ Invoked immediately before rendering when new params or state are being received ### After Updating \(re-rendering\) ```ruby -after_update do ... +after_update do + ... end ``` @@ -148,17 +128,23 @@ Use this as an opportunity to operate on the DOM when the component has been upd ### Unmounting ```ruby -before_unmount do ... +before_unmount do + ... end ``` Invoked immediately before a component is unmounted from the DOM. -Perform any necessary cleanup in this method, such as invalidating timers or cleaning up any DOM elements that were created in the `after_mount` method. +Perform any necessary cleanup in this method, such as cleaning up any DOM elements that were created in the `after_mount` method. Note that periodic timers and +broadcast receivers are automatically cleaned up when the component is unmounted. + +### The `before_render` and `after_render` convenience methods + +These call backs occur before and after all renders (first and re-rerenders.) They provide no added functionality but allow you to keep +your render methods focused on generating components. ### The force\_update! method `force_update!` is a component instance method that causes the component to re-rerender. This method is seldom \(if ever\) needed. The `force_update!` instance method causes the component to re-render. Usually this is not necessary as rendering will occur when state variables change, or new params are passed. - diff --git a/docs/client-dsl/methods.md b/docs/client-dsl/methods.md new file mode 100644 index 000000000..63a9017c8 --- /dev/null +++ b/docs/client-dsl/methods.md @@ -0,0 +1,94 @@ +### Class Methods + +The following methods are used to define the interface and behavior of instances of the component +class. You may also use any other Ruby constructs such as method definition as +you would in any Ruby class. + +**Interface Definition** +These methods define the signature of the component's params. ++ `param(*args)` - specifies params and creates accessor methods ++ `fires(name, alias: internal_name)` - specifies an event call-back ++ `others(name)` accessor method that collects all params not otherwise specified ++ `other, other_params, opts, collect_other_params_as` aliases for `others` + +**Lifecycle Methods** +These methods define the behavior of the component through its lifecycle. All +components must have a `render` callback. If no signature is specified the method will +take a list of method names, and/or a callback block. Except for render, you may +define multiple handlers for each callback. ++ `render(opt_comp_name, opt_params, &block)` ++ `before_mount` ++ `after_mount` ++ `before_new_params` ++ `before_update` ++ `after_update` ++ `before_render` before all renders ++ `after_render` after all renders ++ `rescues(*klasses_to_rescue, &block)` + +**State Management** +Each component instance has internal state represented by the contents of instance variables: +changes to the state is signaled using the `mutate` method. The following methods +define additional instance methods that access state with built-in calls to the mutate method. ++ `mutator` defines an *instance* method with a built-in mutate. ++ `state_reader` creates an *instance* state read only accessor method. ++ `state_writer` creates an *instance* state write only accessor method. ++ `state_accessor` creates *instance* reader and writer methods. + +**Other Class Level Methods** ++ `mounted_components` - returns an array of all currently mounted components of this class and subclasses ++ `force_update!` forces all components in this class and its subclasses to update ++ `create_element(*params, &children)` - create an element from this class ++ `insert_element(*params, &children)` - create and insert an element into the rendering buffer + +### Instance Methods + +**Inserting Elements** + +All HTML and SVG tags, and all other Components visible to this instance can be inserted into the +rendering buffer by using the tag or component class name followed either by parens and/or a block: + +`DIV(...)` or `DIV { ... }` or `DIV(...) { ... }` + +Note that this is just short for `DIV.insert_element(...) { ... }` + +Parameters can be passed as a combination of strings and symbols followed by any number of hashes. + +Any symbols or strings will be treated as a hash key with a value of true and will be merged with the rest of the +hashes: + +```Ruby +MyComp(:foo, 'bar-ski', {class: :joe, id: 12}, data: 123) +# is the same as +MyComp(foo: true, 'bar-ski' => true, class: :joe, id: 12, data: 123) +``` + +**Attaching Callback Handlers** + +Component params that expect `Procs` can passed as normal or using the `.on` method: + +```Ruby +BUTTON { 'click me' }.on(:click) { alert('you clicked?') } +# is the same as +BUTTON(on_click: -> { alert('you clicked') }) { 'click me' } +``` + +**State Management** + +When an event occurs it will probably change state. The mutate method is used to signal the +state change. + ++ `mutate` signals that this instance's state has been mutated ++ `toggle(:foo)` is short for `mutate @foo = !@foo` + +**Other Methods** + ++ `children` enumerates the children of this component ++ `alert(message)` js alert ++ `after(time, &block)` run block after `time` seconds ++ `every(time, &block)` run block every `time` seconds ++ `pluralize(count, singular, plural = nil)` equivalent to Rails pluralize helper ++ `dom_node` returns the DOM node of this component ++ `jq_node` short for `jQ[dom_node]` ++ `force_update!` Almost always components should render due to a state change or when receiving new params. ++ `~` Removes the instance from the current rendering buffer. This is done automatically in most cases when needed. diff --git a/docs/client-dsl/notes.md b/docs/client-dsl/notes.md new file mode 100644 index 000000000..f1d7038a9 --- /dev/null +++ b/docs/client-dsl/notes.md @@ -0,0 +1,318 @@ +## Notes + +### Blocks in Ruby + +Ruby methods may *receive* a block which is simply an anonymous function. + +The following code in Ruby: + +```ruby +some_method(1, 2, 3) { |x| puts x } +``` +is roughly equivilent to this Javascript +```JavaScript +some_method(1, 2, 3 function(x) { console.log(x) }) +``` +In Ruby blocks may be specified either using `do ... end` or with `{ ... }`: + +```Ruby +some_method { an_expression } +# or +some_method do + several + expressions +end +``` +Standard style reserves the `{ ... }` notation for single line blocks, and `do ... end` for multiple line blocks. + +### Component Instances + +Currently when generating a component the actual value returned after processing by React is an instance of class +`Element`. The long term plan is to merge these two concepts back together so that Element instances and +Component instances will be the same. The major difference at the moment is that an Element carries all the data +needed to create a Component Instance, but has not yet been rendered. Through out this document we will use +element and component instance interchangeably. + +### Ruby Hash Params + +In Ruby if the final argument to a method is a hash you may leave the `{...}` off: + +```RUBY +some_method(1, 2, {a: 2, b: 3}) # same as +some_method(1, 2, a: 2, b: 3) +``` + +### The HyperComponent Base Class + +By convention all your components inherit from the `HyperComponent` base class, which would typically look like this: + +```ruby +# components/hyper_component.rb +class HyperComponent + # All component classes must include Hyperstack::Component + include Hyperstack::Component + # The Observable module adds state handling + include Hyperstack::State::Observable + # The following turns on the new style param accessor + # i.e. param :foo is accessed by the foo method + param_accessor_style :accessors +end +``` +> The Hyperstack Rails installer and generators will create this class for you if it does not exist, or you may copy the +above to your `components` directory. + +Having an application wide `HyperComponent` class allows you to modify component behavior on an application basis, similar to the way Rails uses `ApplicationRecord` and `ApplicationController` classes. + +> This is just a convention. Any class that includes the `Hyperstack::Component` module can be used as a Component. You also do not have +to name it `HyperComponent`. For example some teams prefer `ApplicationComponent` more closely following the +Rails convention. If you use a different name for this class be sure to set the `Hyperstack.component_base_class` setting so the +Rails generators will use the proper name when generating your components. **[more details...](../rails-installation/generators.md#specifying-the-base-class)** + +### Abstract and Concrete Components + +An *abstract* component class is intended to be the base class of other components, and thus does not have a render block. +A class that defines a render block is a concrete class. The +distinction between *abstract* and *concrete* is useful to distinguish classes like `HyperComponent` that are intended +to be subclassed. + +Abstract classes are often used to share common code between subclasses. + +### Word Count Method + +```RUBY +def word_count(text) + text.downcase # all lower case + .gsub(/\W/, ' ') # get rid of special chars + .split(' ') # divide into an array of words + .group_by(&:itself) # group into arrays of the same words + .map{|k, v| [k, v.length]} # convert to [word, # of words] + .sort { |a, b| b[1] <=> a[1] } # sort descending (that was fun!) +end +``` + +### Ownership + +In the Avatar example instances of `Avatar` _own_ instances of `ProfilePic` and `ProfileLink`. In Hyperstack (like React), **an owner is the component that sets the `params` of other components**. More formally, if a component `X` is created in component `Y`'s `render` method, it is said that `X` is _owned by_ `Y`. As will be discussed later a component cannot mutate its `params` — they are always consistent with what its owner sets them to. This fundamental invariant leads to UIs that are guaranteed to be consistent. + +It's important to draw a distinction between the owner-owned-by relationship and the parent-child relationship. The owner-owned-by relationship is specific to Hyperstack/React, while the parent-child relationship is simply the one you know and love from the DOM. In the example above, `Avatar` owns the `DIV`, `ProfilePic` and `ProfileLink` instances, and `DIV` is the **parent** \(but not owner\) of the `ProfilePic` and `ProfileLink` instances. + +```ruby +class Avatar < HyperComponent + param :user_name + + render do # this can be shortened to render(DIV) do - see the previous section + DIV do + ProfilePic(user_name: user_name) # belongs to Avatar, owned by DIV + ProfileLink(user_name: user_name) # belongs to Avatar, owned by DIV + end + end +end + +class ProfilePic < HyperComponent + param :user_name + render { IMG(src: "https://graph.facebook.com/#{user_name}/picture") } +end + +class ProfileLink < HyperComponent + param :user_name + render do + A(href: "https://www.facebook.com/#{user_name}") do + user_name + end + end +end +``` + +### Generating Keys + +Every Hyperstack object whether its a string, integer, or some complex class responds to the `to_key` method. +When you provide a component's key parameter with any object, the object's `to_key` method will be called, and +return a unique key appropriate to that object. + +For example strings, and numbers return themselves. Other complex objects return the internal `object_id`, and +some classes provide their own `to_key` method that returns some invariant value for each instance of that class. HyperModel records +return the database id for example. + +If you are creating your own data classes keep this in mind. You simply define a `to_key` method on the class +that returns some value that will be unique to that instance. And don't worry if you don't define a method, it will +default to the one provided by Hyperstack. + +### Proper Use Of Keys + +For best results the `key` is supplied at highest level possible. +> NOTE THIS MAY NO LONGER BE AN ISSUE IN LATEST REACT) + +```ruby +# WRONG! +class ListItemWrapper < HyperComponent + param :data + render do + LI(key: data[:id]) { data[:text] } + end +end + +class MyComponent < HyperComponent + param :results + render do + UL do + result.each do |result| + ListItemWrapper data: result + end + end + end +end +``` + +```ruby +# CORRECT +class ListItemWrapper < HyperComponent + param :data + render do + LI { data[:text] } + end +end + +class MyComponent < HyperComponent + param :results + render do + UL do + results.each do |result| + ListItemWrapper key: result[:id], data: result + end + end + end +end +``` + +### Ruby Procs + +A core class of objects in Ruby is the *Proc*. A Proc (Procedure) is an +object that can be *called*. + +```Ruby +some_proc.call(1, 2, 3) +``` + +Ruby has several ways to create procs: + +```Ruby +# create a proc that will add its three parameters together +# using Proc.new +some_proc = Proc.new { |a, b, c| a + b + c } +# using the lambda method: +some_proc = lambda { |a, b, c| a + b + c } +# or the hash rocket notation: +some_proc = -> (a, b, c) { a + b + c } +# using a method (assuming self responds to foo) +some_proc = method(:foo) +``` + +And there are several more ways, each with its differences and uses. You can +find lots of details on Procs by searching online. **[Here is a good article to get you started...](https://blog.appsignal.com/2018/09/04/ruby-magic-closures-in-ruby-blocks-procs-and-lambdas.html)** + +The most common ways you will use Procs in your Hyperstack code is to define +either lifecycle or component callbacks: + +```Ruby +class Foo < HyperComponent + before_mount :do_it_before + after_mount { puts "I did it after" } + render do + BUTTON(on_click: ->() { puts "clicked Using a lambda" } ) { "click me" } + BUTTON { "no click me" }.on(:click) { puts "clicked using the on method" } + end + def do_it_before + puts "I did it before" + end +end +``` + +The different ways of specifying callbacks allow you to keep your code clear and consise, but in the end they do the same thing. + +> Note that there are subtle differences between Proc.new and lambda, that are beyond the scope of this note. + +### Javascript + +Opal-Ruby uses the backticks and `%x{ ... }` to drop blocks of Javascript code directly into your Ruby code. + +```ruby +def my_own_console(message) + # crab-claws can be used to escape back out to Ruby + `console.log(#{message})` +end +``` + +Both the backticks and `%x{ ... }` work the same, but the `%{ ... }` notation is useful for multiple lines of code. + +### How Importing Works + +Hyperstack automates as much of the process as possible for bridging between React and Javascript, however you do have +lower level control as needed. + +Let's say you have an existing React Component written in Javascript that you would like to access from Hyperstack. + +Here is a simple hello world component: + +```javascript +window.SayHello = class extends React.Component { + constructor(props) { + super(props); + this.displayName = "SayHello" + } + render() { return React.createElement("div", null, "Hello ", this.props.name); } +} +``` + +> I'm sorry I can't resist. Really? +```ruby +class SayHello < HyperComponent + param :name + render(DIV) { "Hello #{name}"} +end +``` +In what world is the Ruby not much better than that JS hot mess. + +Assuming that this component is loaded some place in your assets, you can then access this from Hyperstack by creating a wrapper Component: + +```ruby +class SayHello < HyperComponent + imports 'SayHello' +end + +class MyBigApp < HyperComponent + render(DIV) do + # SayHello will now act like any other Hyperstack component + SayHello name: 'Matz' + end +end +``` + +The `imports` directive takes a string \(or a symbol\) and will simply evaluate it and check to make sure that the value looks like a React component, and then set the underlying native component to point to the imported component. + +Normally you do not have to use `imports` explicitly. When Hyperstack finds a component named in your code that is undefined it searches for +a Javascript class whose matches, and which acts like a React component class. Once find it creates the class and imports for you. + +You may also turn off the autoimport function if necessary in your `hyperstack.rb` initializer: + +```ruby +# do not use the auto-import module +Hyperstack.cancel_import 'hyperstack/component/auto-import' +``` + +### The Enter Event +The :enter event is short for catching :key_down and then checking for a key code of 13. +```ruby +class YouSaid < HyperComponent + state_accessor :value + render(DIV) do + INPUT(value: value) + .on(:enter) do + alert "You said: #{value}" + self.value = "" + end + .on(:change) do |e| + self.value = e.target.value + end + end +end +``` diff --git a/docs/client-dsl/params.md b/docs/client-dsl/params.md new file mode 100644 index 000000000..934ced086 --- /dev/null +++ b/docs/client-dsl/params.md @@ -0,0 +1,199 @@ +The `param` class method gives _read-only_ access to each of the params passed to the component. Params are accessed as instance methods of the component. + +>In React params are called `props`, but Hyperstack uses the more common Rails term `param`. + +Within a component class the `param` method is used to define the parameter signature of the component. You can think of params as the values that would normally be sent to the instance's `initialize` method, but with the difference that a component will get new parameters during its lifecycle. + +The param declaration has several options providing a default value, expected type, and an internal alias name. + +Examples: + +```ruby +param :foo # declares that we must provide a parameter foo when the component is instantiated or re-rerendered. +param :foo => "some default" # declares that foo is optional, and if not present the value "some default" will be used. +param foo: "some default" # same as above using ruby 1.9 JSON style syntax +param :foo, default: "some default" # same as above but uses explicit default key +param :foo, type: String # foo is required and must be of type String +param :foo, type: [String] # foo is required and must be an array of Strings +param foo: [], type: [String] # foo must be an array of strings, and has a default value of the empty array. +param :foo, alias: :something # the alias name will be used for the param (instead of foo) +``` + +#### Accessing param values + +Params are accessible in the component as instance methods. For example: + +```ruby +class Hello < HyperComponent + # visitor has a default value (so its not required) + # and must be of type (i.e. instance of) String + param visitor: "World", type: String + + render do + "Hello #{visitor}" + end +end +``` + +### Param Validation + +As your app grows it's helpful to ensure that your components are used correctly.You do this by specifying the expected ruby class of your parameters. When an invalid value is provided for a param, a warning will be shown in the JavaScript console. Note that for performance reasons type checking is only done in development mode. Here is an example showing typical type specifications: + +```ruby +class ManyParams < HyperComponent + param :an_array, type: [] # or type: Array + param :a_string, type: String + param :array_of_strings, type: [String] + param :a_hash, type: Hash + param :some_class, type: SomeClass # works with any class + param :a_string_or_nil, type: String, allow_nil: true +end +``` + +Note that if the param has a type but can also be nil, add `allow_nil: true` to the specification. + +### Default Param Values + +You can define default values for your `params`: + +```ruby +class ManyParams < HyperComponent + param :an_optional_param, default: "hello", type: String, allow_nil: true +``` + +If no value is provided for `:an_optional_param` it will be given the value `"hello"`, it may also be given the value `nil`. + +Defaults can be provided by the `default` key or using the syntax `param foo: 12` which would default `foo` to 12. + +### Component Instances as Params + +You can pass an instance of a component as a `param` and then render it in the receiving component. + +```ruby +class Reveal < HyperComponent + param :content + render do + BUTTON { "#{@show ? 'hide' : 'show'} me" } + .on(:click) { mutate @show = !@show } + content.render if @show + end +end +class App < HyperComponent + render do + Reveal(content: DIV { 'I came from the App' }) + end +end +``` +[see the spec...](https://github.com/hyperstack-org/hyperstack/blob/24131990ea1cdacfc9efc328d4994a7c2d86a0f4/docs/specs/spec/client-dsl/params_spec.rb#L4-L25) + +`render` is used to render the child components. **[For details ...](component-details.md#rendering-children)** + +> Notice that this is just a way to pass a child to a component but instead of sending it to the "block" with other children you are passing it as a single named child. + +### Other Params + +A common type of component is one that extends a basic HTML element in a simple way. Often you'll want to copy any HTML attributes passed to your component to the underlying HTML element. + +To do this use the `others` method which will gather all the params you did not declare into a hash. Then you can pass this hash on to the child component + +```ruby +class CheckLink < HyperComponent + others :attributes + render do + # we just pass along any incoming attributes + A(attributes) { '√ '.span; children.each &:render } + end +end + + # elsewhere + CheckLink(href: "/checked.html") +``` + +Note: `others` builds a hash, so you can merge other data in or even delete elements out as needed. + +### Aliasing Param Names + +Sometimes we can make our component code more readable by using a different param name inside the component than the owner component will use. + +```ruby +class Hello < HyperComponent + param :name + param include_time: true, alias: :time? + render { SPAN { "Hello #{name}#{'the time is '+Time.now if time?}" } } +end +``` + +This way we can keep the interface very clear, but keep our component code short and sweet. + +### Updating Params + +Each time a component is rendered any of the components it owns may be re-rendered as well **but only if any of the params will change in value.** +If none of the params change in value, then the owned-by component will not be rerendered as no parameters have changed. + +Hyperstack determines if a param has changed through a simple Ruby equality check. If `old_params == new_params` then no update is needed. + +For strings, numbers and other scalar values equality is straight forward. Two hashes are equal if they each contain the same number of keys +and if each key-value pair is equal to the corresponding elements in the other hash. Two arrays are equal if they contain the same number of +elements and if each element is equal to the corresponding element in the other array. + +For other objects unless the object defines its own equality method the objects are equal only if they are the same instance. + +Also keep in mind that if you pass an array or hash (or any other non-scalar object) you are passing a reference to the object **not a copy**. + +Lets look at a simple (but contrived) example and see how this all plays out: + +```RUBY +class App < HyperComponent + render do + DIV do + BUTTON { "update" }.on(:click) { force_update! } + new_hash = {foo: {bar: [12, 13]}} + Comp2(param: new_hash) + end + end +end + +class Comp2 < HyperComponent + param :param + render do + DIV { param } + end +end +``` + +Even though we have not gotten to event handlers yet, you can see what is going on: When we click the update button we call `force_update!` +which will force the `App` component to rerender. +> By the way `force_update!` is almost never used, but we are using it here +just to make the example clear. Its also one of the reasons this example gets into trouble. Read on! + +Will `Comp2` rerender? No - because even though we are creating a new hash, the old hash and new hash are equal in value. + +What if we change the hash to be `{foo: {bar: [12, Time.now]}}`. Will `Comp2` re-render now? Yes because the old and new hashes are no longer equal. + +What if we changed `App` like this: + +```RUBY +class App < HyperComponent + # initialize an instance variable before rendering App + before_mount { @hash = {foo: {bar: [12, 13]}} } + render do + DIV do + BUTTON { "update" }.on(:click) do + @hash[:foo][:bar][2] = Time.now + force_update! + end + Comp2(param: @hash) + end + end +end +``` + +Will `Comp2` still update? No. `Comp2` received the value of `@hash` on the first render, and so `Comp2` is rendering the same copy of `@hash` that `App` is changing. +So when we compare *old* verses *new* we are comparing the same object, so the values are equal even though the contents of the hash has changed. + +### Conclusion + +That does not seem like a very happy ending, but the case we used was not very realistic. If you stick to passing simple scalars, or hashes and arrays +whose values don't change after they have been passed, things will work fine. And for situations where you do need to store and +manipulate complex data, you can use the **[the Hyperstack::Observable module](../hyper-state/README.md)** to build safe classes that don't +have the problems seen above. diff --git a/docs/client-dsl/event-handlers.md b/docs/client-dsl/predefined-events.md similarity index 71% rename from docs/client-dsl/event-handlers.md rename to docs/client-dsl/predefined-events.md index 0b93681f2..c3cd108b9 100644 --- a/docs/client-dsl/event-handlers.md +++ b/docs/client-dsl/predefined-events.md @@ -6,7 +6,7 @@ Event Handlers are attached to tags and components using the `on` method. SELECT ... do ... end.on(:change) do |e| - mutate mode = e.target.value.to_i + mutate @mode = e.target.value.to_i end ``` @@ -21,49 +21,54 @@ H1(class: :cursor_hand) { 'Click me' }.on(:click) { do_something } Event handlers can be chained like so ```ruby -INPUT ... do + INPUT ... do ... end.on(:key_up) do |e| ... end.on(:change) do |e| ... -end + end ``` ### Event Handling and Synthetic Events -With React you attach event handlers to elements using the `on` method. React ensures that all events behave identically in IE8 and above by implementing a synthetic event system. That is, React knows how to bubble and capture events according to the spec, and the events passed to your event handler are guaranteed to be consistent with [the W3C spec](http://www.w3.org/TR/DOM-Level-3-Events/), regardless of which browser you're using. +The React engine ensures that all events behave identically in IE8 and above by implementing a synthetic event system. That is, React knows how to bubble and capture events according to the spec, and the events passed to your event handler are guaranteed to be consistent with [the W3C spec](http://www.w3.org/TR/DOM-Level-3-Events/), regardless of which browser you're using. ### Under the Hood: Event Delegation -React doesn't actually attach event handlers to the nodes themselves. When React starts up, it starts listening for all events at the top level using a single event listener. When a component is mounted or unmounted, the event handlers are simply added or removed from an internal mapping. When an event occurs, React knows how to dispatch it using this mapping. When there are no event handlers left in the mapping, React's event handlers are simple no-ops. To learn more about why this is fast, see [David Walsh's excellent blog post](http://davidwalsh.name/event-delegate). +React doesn't actually attach event handlers to the nodes themselves. When React starts up, it starts listening for all events at the top level using a single event listener. When a component is mounted or unmounted, the event handlers are simply added or removed from an internal mapping. When an event occurs, React knows how to dispatch it using this mapping. When there are no event handlers left in the mapping, React's event handlers are simple no-ops. To learn more about why this is fast, see **[David Walsh's excellent blog post ...](http://davidwalsh.name/event-delegate).** ### React::Event -Your event handlers will be passed instances of `React::Event`, a wrapper around react.js's `SyntheticEvent` which in turn is a cross browser wrapper around the browser's native event. It has the same interface as the browser's native event, including `stopPropagation()` and `preventDefault()`, except the events work identically across all browsers. +Your event handlers will be passed instances of `Hyperstack::Component::Event`, a wrapper around react.js's `SyntheticEvent` which in turn is a cross browser wrapper around the browser's native event. It has the same interface as the browser's native event, including `stop_propagation()` and `prevent_default()`, except the events work identically across all browsers. For example: ```ruby class YouSaid < HyperComponent - + state_accessor :value render(DIV) do - INPUT(value: state.value). - on(:key_down) do |e| - alert "You said: #{state.value}" if e.key_code == 13 - end. - on(:change) do |e| - mutate value = e.target.value + INPUT(value: value) + .on(:key_down) do |e| + next unless e.key_code == 13 + + alert "You said: #{value}" + self.value = "" + end + .on(:change) do |e| + self.value = e.target.value end end end ``` -If you find that you need the underlying browser event for some reason use the `native_event`. +> Hyperstack also includes an `enter` event that fires on key_down **when** the key_code == 13. **[See that version here ...](notes.md#the-enter-event)** + +If you find that you need the underlying browser event for some reason use the `native_event` method (i.e. `evt.native_event`). In the following responses shown as \(native ...\) indicate the value returned is a native object with an Opal wrapper. In some cases there will be opal methods available \(i.e. for native DOMNode values\) and in other cases you will have to convert to the native value with `.to_n` and then use javascript directly. -Every `React::Event` has the following methods: +Every `Event` has the following methods: ```ruby bubbles -> Boolean @@ -84,11 +89,8 @@ type -> String ### Event pooling -The underlying React `SyntheticEvent` is pooled. This means that the `SyntheticEvent` object will be reused and all properties will be nullified after the event method has been invoked. This is for performance reasons. As such, you cannot access the event in an asynchronous way. - -### Supported Events - -React normalizes events so that they have consistent properties across different browsers. +The underlying React `SyntheticEvent` is pooled. This means that the `SyntheticEvent` object will be reused and all properties will be nullified after the event method has been invoked. This is for performance reasons. As such, you cannot access the event in an asynchronous way - don't store the whole event and +think you can use it later after you ate breakfast. ### Clipboard Events @@ -123,9 +125,11 @@ data -> String Event names: ```ruby -:key_down, :key_press, :key_up +:key_down, :key_press, :key_up, :enter ``` +> The `enter` event is fired on key_down where key_code == 13 (the enter key) + Available Methods: ```ruby @@ -200,21 +204,21 @@ shift_key -> Boolean ### Drag and Drop example -Here is a Hyperstack version of this [w3schools.com](https://www.w3schools.com/html/html5_draganddrop.asp) example: +Here is a Hyperstack version of this **[w3schools.com](https://www.w3schools.com/html/html5_draganddrop.asp)** example: ```ruby -DIV(id: "div1", style: {width: 350, height: 70, padding: 10, border: '1px solid #aaaaaa'}) - .on(:drop) do |ev| - ev.prevent_default - data = `#{ev.native_event}.native.dataTransfer.getData("text")` - `#{ev.target}.native.appendChild(document.getElementById(data))` - end - .on(:drag_over) { |ev| ev.prevent_default } +DIV(id: :div1, style: { width: 350, height: 70, padding: 10, border: '1px solid #aaaaaa' }) +.on(:drop) do |evt| + evt.prevent_default + data = `#{evt.native_event}.native.dataTransfer.getData("text")` + `#{evt.target}.native.appendChild(document.getElementById(data))` +end +.on(:drag_over, &:prevent_default) -IMG(id: "drag1", src: "https://www.w3schools.com/html/img_logo.gif", draggable: "true", width: 336, height: 69) - .on(:drag_start) do |ev| - `#{ev.native_event}.native.dataTransfer.setData("text", #{ev.target}.native.id)` - end +IMG(id: :drag1, src: "https://www.w3schools.com/html/img_logo.gif", draggable: "true", width: 336, height: 69) +.on(:drag_start) do |evt| + `#{evt.native_event}.native.dataTransfer.setData("text", #{evt.target}.native.id)` +end ``` ### Selection events @@ -295,4 +299,3 @@ Event names: ```ruby :load, :error ``` - diff --git a/docs/client-dsl/predefined-tags.md b/docs/client-dsl/predefined-tags.md new file mode 100644 index 000000000..75e72c5a0 --- /dev/null +++ b/docs/client-dsl/predefined-tags.md @@ -0,0 +1,41 @@ +#### HTML Tags +``` +A ABBR ADDRESS AREA ARTICLE ASIDE AUDIO +B BASE BDI BDO BIG BLOCKQUOTE BODY BR BUTTON +CANVAS CAPTION CITE CODE COL COLGROUP +DATA DATALIST DD DEL DETAILS DFN DIALOG DIV DL DT +EM EMBED +FIELDSET FIGCAPTION FIGURE FOOTER FORM +H1 H2 H3 H4 H5 H6 HEAD HEADER HR HTML +I IFRAME IMG INPUT INS +KBD KEYGEN +LABEL LEGEND LI LINK +MAIN MAP MARK MENU MENUITEM META METER +NAV NOSCRIPT +OBJECT OL OPTGROUP OPTION OUTPUT +P PARAM PICTURE PRE PROGRESS +Q +RP RT RUBY +S SAMP SCRIPT SECTION SELECT SMALL SOURCE SPAN STRONG STYLE SUB SUMMARY SUP +TABLE TBODY TD TEXTAREA TFOOT TH THEAD TIME TITLE TR TRACK +U UL +VAR VIDEO +WBR +``` +#### SVG Tags +``` +CIRCLE CLIPPATH +DEFS +ELLIPSE +G +LINE LINEARGRADIENT +MASK +PATH PATTERN POLYGON POLYLINE +RADIALGRADIENT RECT +STOP +SVG +TEXT TSPAN +``` +#### The FRAGMENT Tag + +The `FRAGMENT` tag is used to return multiple *static* children from a component or block. The only valid param to `FRAGMENT` is `key`. See the **[React documentation](https://reactjs.org/docs/fragments.html)** for more details. If a render block returns dynamic children that will change in number and order, each child should be assigned a unique key, and the FRAGMENT tag does not have to be used. diff --git a/docs/client-dsl/state.md b/docs/client-dsl/state.md index b4f55deb3..c81a5a0c2 100644 --- a/docs/client-dsl/state.md +++ b/docs/client-dsl/state.md @@ -1,215 +1,124 @@ -# State +When a component is rendered what it displays depends on some combination of three things: -In React \(and Hyperstack\) state is mutable. Changes \(mutations\) to state variables cause Components to re-render. Where state is passed into a child Component as a `param`, it will cause a re-rendering of that child Component. Change flows from a parent to a child - change does not flow upward and this is why params are not mutable. ++ the value of the params passed to the component ++ the state of the component ++ the state of some other objects on which a component depends -State variables are normal instance variables or objects. When a state variable changes, we use the `mutate` method to get React's attention and cause a re-render. Like normal instance variables, state variables are created when they are first accessed, so there is no explicit declaration. +Whenever one of these three things change the component will need to re-render. In this section we +discuss how a component's *internal* state is managed within Hyperstack. Params were covered **[here...](params.md)** and sharing state +between components will be covered **[here...](../hyper-state/README.md)** -The syntax of `mutate` is simple - its `mutate` and any other number of parameters and/or a block. Normal evaluation means the parameters are going to be evaluated first, and then `mutate` gets called. +The idea of state is built into Ruby and is represented by the *instance* variables of an object instance. -* `mutate @foo = 12, @bar[:zap] = 777` executes the two assignments first, then calls mutate -* or you can say `mutate { @foo = 12; @bar[:zap] = 777 }` which is more explicit, and does the same thing +Components very often have state. For example, is an item being displayed or edited? What is the current +value of a text box? A checkbox? The time that an alarm should go off? All these are state and will be +represented as values stored somewhere in instance variables. -Here are some examples: +Lets look at a simple clock component: -```ruby -class Counter < HyperComponent - before_mount do - @count = 0 # optional initialization - end - - render(DIV) do - # note how we mutate count - BUTTON { "+" }.on(:click) { mutate @count += 1) } - P { @count.to_s } - end -end -``` - -```ruby -class LikeButton < HyperComponent - render(DIV) do - BUTTON do - "You #{@liked ? 'like' : 'haven\'t liked'} this. Click to toggle." - end.on(:click) do - mutate @liked = !@liked +```RUBY +class Clock < HyperComponent + after_mount do + every(1.second) do + mutate @time = Time.now end end -end -``` - -### Components are Just State Machines - -React thinks of UIs as simple state machines. By thinking of a UI as being in various states and rendering those states, it's easy to keep your UI consistent. - -In React, you simply update a component's state, and then the new UI will be rendered on this new state. React takes care of updating the DOM for you in the most efficient way. - -### What Components Should Have State? - -Most of your components should simply take some params and render based on their value. However, sometimes you need to respond to user input, a server request or the passage of time. For this you use state. - -**Try to keep as many of your components as possible stateless.** By doing this you'll isolate the state to its most logical place and minimize redundancy, making it easier to reason about your application. - -A common pattern is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via `param`s. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data in a declarative way. - -State can be held in any object \(not just a Component\). For example: - -```ruby -class TestIt - def self.swap_state - @@test = !@@test - end - def self.result - @@test ? 'pass' : 'fail' - end -end - -class TestResults < HyperComponent - render(DIV) do - P { "Test is #{TestIt.result}" } - BUTTON { 'Swap' }.on(:click) do - mutate TestIt::swap_state - end - end + render(DIV) { "The time is #{@time}" } end ``` -In the example above, the singleton class `TestIt` holds its own internal state which is changed through a `swap_state` class method. The `TestResults` Component has no knowledge of the internal workings of the `TestIt` class. - -When the BUTTON is pressed, we call `mutate`, passing the object which is being mutated. The actual mutated value is not important, it is the fact that the _observed_ object \(our `TestIt` class\) is being mutated that will cause a re-render of the _observing_ `TestResults` Component. Think about `mutate` as a way of telling React that the Component needs to be re-rendered as the state has changed. - -In the example above, we could also move the _observing_ and _mutating_ behaviour out of the Component completely and manage it in the `TestIt` class - in this case, we would call it a Store. Stores are covered in the Hyper-Store documentation later. - -### What Should Go in State? - -**State should contain data that a component's instance variables, event handlers, timers, or http requests may change and trigger a UI update.** - -When building a stateful component, think about the minimal possible representation of its state, and only store those properties in `state`. Add to your class methods to compute higher level values from your state variables. Avoid adding redundant or computed values as state variables as these values must then be kept in sync whenever state changes. - -### What Shouldn't Go in State? +The after_mount call back sets up a periodic timer that goes off every second and updates the +`@time` instance variable with the current time. The assignment to `@time` is wrapped in the `mutate` method +which signals the React Engine that the state of `Clock` has been mutated, this in turn will add `Clock` to +the list of components that need to be re-rendered. -State should contain the minimal amount of data needed to represent your UI's state. As such, it should not contain: +### It's that Simple Really -* **Computed data:** Don't worry about precomputing values based on state — it's easier to ensure that your UI is consistent if you do all computation during rendering. For example, if you have an array of list items in state and you want to render the count as a string, simply render `"#{@list_items.length} list items'` in your `render` method rather than storing the count as another state. -* **Data that does not effect rendering:** Changing an instance variable \(or any object\) that does not affect rendering does not need to be mutated \(i.e you do not need to call `mutate`\). +To reiterate: Components (and other Ruby objects) have state, and the state + the params will determine what +is rendered. When state changes we signal this using the mutate method, and any components depending on the state +will be re-rendered. -The rule is simple: anytime you are updating a state variable use `mutate` and your UI will be re-rendered appropriately. +### State Mutation Always Drives Rendering -### State and user input +It is always a mutation of state that triggers the UI to begin a render cycle. That mutation may in turn cause components +to render and send different params to lower level components, but it begins with a state mutation. -Often in a UI you gather input from a user and re-render the Component as they type. For example: +### What Causes State To Mutate? -```ruby -class UsingState < HyperComponent +Right! Good question! State is mutated by your code's reaction to some external event. A button click, text being typed, +or the arrival of data from the server. We will cover these in upcoming sections, but once an event occurs your +code will probably mutate some state as a result, causing component depending on this state to update. - render(DIV) do - # the button method returns an HTML element - # .on(:click) is an event handeler - # notice how we use the mutate method to get - # React's attention. This will cause a - # re-render of the Component - button.on(:click) { mutate(@show = !@show) } - DIV do - input - output - easter_egg - end if @show - end - - def button - BUTTON(class: 'ui primary button') do - @show ? 'Hide' : 'Show' - end - end +### Details on the `mutate` Syntax - def input - DIV(class: 'ui input fluid block') do - INPUT(type: :text).on(:change) do |evt| - # we are updating the value per keypress - # using mutate will cause a rerender - mutate @input_value = evt.target.value - end - end - end - - def output - # rerender whenever input_value changes - P { "#{@input_value}" } - end +The main purpose of `mutate` is to signal that state has changed, but it also useful to clarify how your code works. +Therefore `mutate` can be used in a number of flexible ways: - def easter_egg - H2 {'you found it!'} if @input_value == 'egg' - end ++ It can take any number of expressions: +```RUBY +mutate @state1 = 'something', @state2 = 'something else' +``` ++ or it can take a block: +```Ruby +mutate do + ... compute the new state ... + @state = ... end ``` -### State and HTTP responses +In both cases the result returned by `mutate` will be the last expression executed. -Often your UI will re-render based on the response to a HTTP request to a remote service. Hyperstack does not need to understand the internals of the HTTP response JSON, but does need to _observe_ the object holding that response so we call `mutate` when updating our response object in the block which executes when the HTTP.get promise resolves. +### The `mutator` Class Method -```ruby -class FaaS < HyperComponent - render(DIV) do - BUTTON { 'faastruby.io' }.on(:click) do - faast_ruby - end +This pattern: - DIV(class: :block) do - P { @hello_response['function_response'].to_s } - P { "executed in #{@hello_response['execution_time']} ms" } - end if @hello_response - end - - def faast_ruby - HTTP.get('https://api.faastruby.io/paulo/hello-world', - data: {time: true} - ) do |response| - # this code executes when the promise resolves - # notice that we call mutate when updating the state instance variable - mutate @hello_response = response.json if response.ok? - end +```RUBY +class SomeComponent < HyperComponent + def update_some_state(some_args) + ... compute new state ... + mutate ... end + ... end ``` +is common enough that Hyperstack provides two ways to shorten this code. The first is the +`mutator` class method: +```Ruby + ... + mutator :update_some_state do |some_args| + ...compute new state ... + end + ... +``` +In other words `mutator` defines a method that is wrapped in a call to `mutate`. It also has +the advantage of clearly declaring that this method will be mutating the components state. -### State and updating interval - -One common use case is a component wanting to update itself on a time interval. It's easy to use the kernel method `every`, but it's important to cancel your interval when you don't need it anymore to save memory. Hyperstack provides Lifecycle Methods \(covered in the next section\) that let you know when a component is about to be created or destroyed. Let's create a simple mixin that uses these methods to provide a React friendly `every` function that will automatically get cleaned up when your component is destroyed. - -```ruby -module ReactInterval - - def self.included(base) - base.before_mount do - @intervals = [] - end +> Important note: If you do an early exit from the mutator using a `return` or `break` no mutation +will occur. If you want to do an early exit then use the `next` keyword. - base.before_unmount do - @intervals.each(&:stop) - end - end +### The `state_accessor`, `state_reader` and `state_writer` Methods - def every(seconds, &block) - Kernel.every(seconds, &block).tap { |i| @intervals << i } - end -end +Often all a mutator method will do is assign a new value to a state. For this case Hyperstack provides +the `state_accessor`, `state_reader` and `state_writer` methods, that parallel Ruby's `attribute_accessor`, +`attribute_reader` and `attribute_writer` methods: -class TickTock < HyperComponent - include ReactInterval +```Ruby + state_accessor :some_state + ... + some_state = some_state + 1 # or just some_state += some_state +``` +In otherwords the `state_accessor` creates methods that allow read/write access to the underlying instance variable +including the call to `mutate`. - before_mount do - @seconds = 0 - end +Again the advantage is not only less typing but also clarity of code and intention. - after_mount do - every(1) { mutate @seconds = @seconds + 1 } - end +### Sharing State - render(DIV) do - P { "Hyperstack has been running for #{@seconds} seconds" } - end -end -``` +You can also use and share state at the class level and create "stateful" class libraries. This is described in the **[chapter on HyperState...](../hyper-state/README.md)** -Notice that TickTock effectively has two `before_mount` methods, one that is called to initialize the `@intervals` array and another to initialize `@seconds` +### The `force_update!` Method +We said above only state mutation can start a rerender. The `force_update!` method is the exception to this rule, as it will +force a component to rerender just because you said so. If you have to use `force_update!` you may be doing something +wrong, so use carefully. diff --git a/docs/development-workflow/README.md b/docs/development-workflow/README.md index 48f68fd9d..02c281b23 100644 --- a/docs/development-workflow/README.md +++ b/docs/development-workflow/README.md @@ -1,2 +1 @@ -# Development Workflow - +# Development Tools and Procedures diff --git a/docs/development-workflow/debugging.md b/docs/development-workflow/debugging.md index 405ec61b5..784f44ced 100644 --- a/docs/development-workflow/debugging.md +++ b/docs/development-workflow/debugging.md @@ -1,24 +1,29 @@ -# Debugging +Debugging any UI code is difficult. Hyperstack's declarative approach, and lack of redundant boilerplate helps a lot. Simply having 1/4 the code base +to deliver the same functionality is going to make things easier. -**Work in progress - ALPHA \(docs and code\)** +However all that said, **Debugging UI Code is Difficult.** The UI's main job is to deal with events coming from multiple directions and unpredictable sources, this makes tracking down failures difficult as timing can become an issue. -## Debugging tips - -Tips, good practice will help you debugging your Hyperstack application. +Here are few tips to go along with the other tools in this section (HyperSpec and HyperTrace) to make your life a bit easier. ### JavaScript Console -At any time during program execution you can breakout into the JavaScript console by simply adding a line of back-ticked JavaScript to your ruby code: +At any time during program execution you can breakout into the JavaScript console by simply adding the debugger keyword to your Ruby code. -`debugger;` +If you have source maps turned on you will then be able to see your ruby code \(and the compiled JavaScript code\) and set browser breakpoints, examine values and continue execution. -If you have source maps turned on you will then be able to see your ruby code \(and the compiled JavaScript code\) and set browser breakpoints, examine values and continue execution. See Opal Source Maps if you are not seeing source maps. +> Important Note: The Opal compiler will not handle the `debugger` keyword at the end of blocks, method definitions, or begin..end statements. +```ruby +def buggy_method + ... + debugger # this will break add any expression on the next line to fix +end +``` -You can also inspect ruby objects from the JavaScript console. +You can also inspect Ruby objects from the JavaScript console. The mapping between the Javascript and Ruby is fairly easy to follow thanks to the great Opal team. Here are some tips: [https://dev.mikamai.com/2014/11/19/3-tricks-to-debug-opal-code-from-your-browser/](https://dev.mikamai.com/2014/11/19/3-tricks-to-debug-opal-code-from-your-browser/) -### Puts is your friend +### The `puts` method is your friend Anywhere in your HyperReact code you can simply puts any\_value which will display the contents of the value in the browser console. This can help you understand React program flow as well as how data changes over time. @@ -51,3 +56,15 @@ class Thing < Hyperstack::Component end ``` +### HyperTrace + +Sometimes popping in a trace can reveal a lot about what is going on. [HyperTrace](hyper-trace.md) wraps your selected method calls in +a dump of incoming parameters, instance variable state, and return values. You can also setup conditional +breakpoints. So keep HyperTrace handy in your tool belt. + +### HyperSpec + +IMHO the best debugging tool is a spec. As soon as you start creating a new feature, or find a bug, start +writing a spec. Once you can reproduce the problem by running a spec, you are 90% of the way to fixing the problem, +and you will have another spec to add to your tests, making your app more robust. [HyperSpec](hyper-spec/README.md) extends RSpec so that +can control and interrogate the client from within your specs, using Ruby code. diff --git a/docs/development-workflow/deploy-to-heroku.md b/docs/development-workflow/deploy-to-heroku.md new file mode 100644 index 000000000..cdd636356 --- /dev/null +++ b/docs/development-workflow/deploy-to-heroku.md @@ -0,0 +1,50 @@ +There are a number of issues and additional items that need to been done when deploying to Heroku (or other production environments.) +Even if not deploying to Heroku these steps will cover most of the gotchas that you will encounter. + +If you do find any problems please log an issue, or better yet do a pull request on this page. + +1. **You need to use postgresql rather than sqlite or mysql.** You can find instructions on how to do this online, or better yet when you create your rails app, create it from the beginning with postgresql: https://www.digitalocean.com/community/tutorials/how-to-set-up-ruby-on-rails-with-postgres + +2. **Remove `app/models/application_record.rb`** This is no longer needed due to a recent rails fix, and confuses Heroku. + +3. **Use harmonly uglifier.** In config/environments/production.rb you need to change this line from: +`config.assets.js_compressor = :uglifier` +to +`config.assets.js_compressor = Uglifier.new(harmony: true)` + +4. **Insure webpacker:compile occurs before assets:precompile:** at the end of the `Rakefile` (in the root directory) add this line: +`Rake::Task["assets:precompile"].enhance(['yarn:install', 'webpacker:compile'])` + +5. **Setup your database** Make sure you run +`Heroku run rake db:migrate` + +6. **Update your production policies** By default the Hyperstack installer will leave your Policies wide open but **not** in production. +For a production app you will want to add restrictive Policies to protect your data. If you just want to get things working on Heroku you can remove the guard from the end of the `policies/application.rb` file. + +5. Add `stylesheet_pack_tag`s: Hyperstack does not automatically pull in the `.css` packs. Instead you have to add one or both these lines to your layouts, if you are requiring css assets in the pack files: +`<%= stylesheet_pack_tag 'client_only' %>` +If you are requiring css libraries in the `client_only.js` pack file +and +`<%= stylesheet_pack_tag 'client_and_server' %>` + If you are requiring css libraries in the `client_and_server.js` pack file + +6. Setup ActionCable (see [full instructions](https://blog.Heroku.com/real_time_rails_implementing_websockets_in_rails_5_with_action_cable#deploying-our-application-to-Heroku) for details) +provision Redis on Heroku `Heroku addons:add redistogo` +then get the Heroku url: `Heroku config --app action-cable-example | grep REDISTOGO_URL` +use the url in config/cable.yml (in the production section) +in config/environments/production.rb add these two lines: +`config.web_socket_server_url = "wss://your-app.Herokuapp.com/cable" ` +`config.action_cable.allowed_request_origins = ['https://your-app.Herokuapp.com', 'http://action-your-app.Herokuapp.com']` + +### On Going Development + +After updating anything in the hyperstack initializer you will need to force Heroku to clear the cache: + +First install the Heroku-repo plugin (on your console) +`$ Heroku plugins:install Heroku-repo` + +and then to clear the cache do: + +`$ Heroku repo:purge_cache -a appname` +`$ git commit --allow-empty -m "Purge cache"` +`$ git push Heroku master` diff --git a/docs/development-workflow/hyper-spec.md b/docs/development-workflow/hyper-spec.md deleted file mode 100644 index a6341f6a3..000000000 --- a/docs/development-workflow/hyper-spec.md +++ /dev/null @@ -1,422 +0,0 @@ -# HyperSpec - -With HyperSpec you can run _isomorphic_ specs for all your Hyperstack code using RSpec. Everything runs as standard RSpec test specs. - -For example if you have a component like this: - -```ruby -class SayHello < HyperComponent - param :name - render(DIV) do - "Hello there #{name}" - end -end -``` - -Your test spec would look like this: - -```ruby -describe 'SayHello', js: true do - it 'has the correct content' do - mount "SayHello", name: 'Fred' - expect(page).to have_content('Hello there Fred') - end -end -``` - -The `mount` method will setup a blank client window, and _mount_ the named component in the window, passing any parameters. - -Notice that the spec will need a client environment so we must set `js: true`. - -The `mount` method can also take a block which will be recompiled and sent to the client before mounting the component. You can place any client side code in the mount block including the definition of components. - -```ruby -describe "the mount's code block", js: true do - it 'will be recompiled on the client' do - mount 'ShowOff' do - class ShowOff < HyperComponent - render(DIV) { 'Now how cool is that???' } - end - end - expect(page).to have_content('Now how cool is that???' ) - end -end -``` - -## Why? - -Hyperstack wants to make the server-client divide as transparent to the developer as practical. Given this, it makes sense that the testing should also be done with as little concern for client versus server. - -HyperSpec allows you to directly use tools like FactoryBot \(or Hyperstack Operations\) to setup some test data, then run a spec to make sure that a component correctly displays, or modifies that data. You can use Timecop to manipulate time and keep in sync between the server and client. This makes testing easier and more realistic without writing a lot of redundant code. - -## Installation - -These instructions are assuming you are using Rails as the backend. However the `hyper-spec` gem itself does not require Rails, so you can adapt these instructions as needed. - -### Add the gems - -In your `Gemfile` add - -```ruby -group :test do - gem 'hyper-spec', path: '../hyperstack/ruby/hyper-spec' - gem 'database_cleaner' # optional but we find it works best due to the concurrency of hyperstack -end -``` - -and `bundle install` - -### Install RSpec files - -`bundle exec rails g rspec:install` - -> Skip this step if rspec is already installed. - -### Configure HyperSpec - -Update your `spec/rails_helper.rb` so it looks like this: - -```ruby -# This file is copied to spec/ when you run 'rails generate rspec:install' -require 'spec_helper' -ENV['RAILS_ENV'] ||= 'test' -require File.expand_path('../../config/environment', __FILE__) -# Prevent database truncation if the environment is production -abort("The Rails environment is running in production mode!") if Rails.env.production? -require 'rspec/rails' -# Add additional requires below this line. Rails is not loaded until this point! - -# THESE ARE LINES WE ARE ADDING -# JUST MAKE SURE THEY ARE AFTER `require 'rspec/rails'` - -# pull in the hyper-spec code. -require 'hyper-spec' - -# If you are using DatabaseCleaner here is where -# you set the mode. We recommend truncation. -DatabaseCleaner.strategy = :truncation - -# Now we setup Rspec details -RSpec.configure do |config| - - # This is only needed if you are using DatabaseCleaner - config.before(:each) do - DatabaseCleaner.clean - end - - # If you are NOT using webpacker remove this block - config.before(:suite) do # compile front-end - Webpacker.compile - end -end -... -``` - -### Create an install smoke test - -Make sure your installation is working by creating a simple smoke test like this: - -```ruby -# spec/hyperspec_smoke_test.rb -require 'rails_helper' - -describe 'Hyperspec', js: true do - it 'can mount and test a component' do - mount "HyperSpecTest" do - class HyperSpecTest < HyperComponent - render(DIV) do - "It's Alive!" - end - end - end - expect(page).to have_content("It's Alive!") - end - it 'can evaluate and test expressions on the client' do - expect_evaluate_ruby do - [1, 2, 3].reverse - end.to eq [3, 2, 1] - end -end -``` - -To run it do a -`bundle exec rspec spec/hyperspec_smoke_test.rb` - -> Note that because the test does not end in `_spec.rb` it will not be run with the rest of your specs. - -## Environment Variables - -You can set `DRIVER` to `chrome` to run the client in chrome and see what is going on. By default tests will run in chrome headless mode which is quicker, but harder to debug problems. - -```text -DRIVER=chrome bundle exec rspec -``` - -## Spec Helpers - -HyperSpec adds the following spec helpers to your test environment - -* `mount` -* `client_option` and `client_options` -* `on_client` -* `isomorphic` -* `evaluate_ruby` -* `expect_evaluate_ruby` -* `expect_promise` -* call back and event history methods -* `pause` -* `attributes_on_client` -* `size_window` -* `add_class` - -#### The `mount` Method - -`mount` takes the name of a component, prepares an empty test window, and mounts the named component in the window. -You may give a block to `mount` which will be recompiled on the client, and run _before_ mounting. This means that the component mounted may be actually defined in the block, which is useful for setting up top level wrapper components, which will invoke your component under test. You can also modify existing components for white box testing, or local fixture data, constants, etc. - -`mount` may also be given a hash of the parameters to be passed to the component. - -```ruby -mount 'Display', test: 123 do - class Display < HyperComponent - param :test - render(DIV) { test.to_s } - end -end -``` - -#### The `client_option` Method - -There are several options that control the mounting process. Use `client_option` \(or `client_options`\) before accessing any client side to set any of these options: - -* `render_on`: `:server_only`, `:client_only`, or `:both`, default is client\_only. -* `layout`: specify the layout to be used. Default is :none. -* `style_sheet`: specify the name of the style sheet to be loaded. Defaults to the application stylesheet. -* `javascript`: specify the name of the javascript asset file to be loaded. Defaults to the application js file. - -For example: - -```ruby -it "can be rendered server side only" do - client_option render_on: :server_only - mount 'SayHello', name: 'George' - expect(page).to have_content('Hello there George') - # Server only means no code is downloaded to the client - expect(evaluate_script('typeof React')).to eq('undefined') -end -``` - -If you need to pull in alternative style sheets and javascript files, the recommended way to do this is to - -1. Add them to a `specs/assets/stylesheets` and `specs/assets/javascripts` directory and -2. Add the following line to your `config/environment/test.rb` file: - - ```ruby - config.assets.paths << ::Rails.root.join('spec', 'assets', 'stylesheets').to_s - config.assets.paths << ::Rails.root.join('spec', 'assets', 'javascripts').to_s - ``` - -This way you will not pollute your application with these 'test only' files. - -_The javascript spec asset files can be `.rb` files and contain ruby code as well. See the specs for examples!_ - -#### The `on_client` Method - -`on_client` takes a block and compiles and runs it on the client. This is useful in setting up test constants and client only fixtures. - -Note that `on_client` needs to _proceed_ any calls to `mount`, `evaluate_ruby`, `expect_evaluate_ruby` or `expect_promise` as these methods will initiate the client load process. - -#### The `isomorphic` Method - -Similar to `on_client` but the block is _also_ run on the server. This is useful for setting constants shared by both client and server, and modifying behavior of isomorphic classes such as ActiveRecord models, and HyperOperations. - -```ruby -isomorphic do - class SomeModel < ActiveRecord::Base - def fake_attribute - 12 - end - end -end -``` - -#### The `evaluate_ruby` Method - -Takes either a string or a block, dynamically compiles it, downloads it to the client and runs it. - -```ruby -evaluate_ruby do - i = 12 - i * 2 -end -# returns 24 - -isomorphic do - def factorial(n) - n == 1 ? 1 : n * factorial(n-1) - end -end - -expect(evaluate_ruby("factorial(5)")).to eq(factorial(5)) -``` - -`evaluate_ruby` can also be very useful for debug. Set a breakpoint in your test, then use `evaluate_ruby` to interrogate the state of the client. - -#### The `expect_evaluate_ruby` Method - -Combines expect and evaluate methods: - -```ruby -expect_evaluate_ruby do - i = 1 - 5.times { |n| i = i*n } - i -end.to eq(120) -``` - -#### The `expect_promise` Method - -Works like `expect_evaluate_ruby` but is used with promises. `expect_promise` will hang until the promise resolves and then return to the results. - -```ruby -expect_promise do - Promise.new.tap do |p| - after(2) { p.resolve('hello') } - end -end.to eq('hello') -``` - -#### Call Back and Event History Methods - -HyperReact components can _generate_ events and perform callbacks. HyperSpec provides methods to test if an event or callback was made. - -```ruby -mount 'CallBackOnEveryThirdClick' do - class CallBackOnEveryThirdClick < HyperComponent - fires :click3 - def increment_click - @clicks ||= 0 - @clicks = (@clicks + 1) - click3!(@clicks) if @clicks % 3 == 0 - end - render do - DIV(class: :tp_clicker) { "click me" } - .on(:click) { increment_click } - end - end -end - -7.times { page.click('#tp_clicker') } -expect(callback_history_for(:click3)).to eq([[3], [6]]) -``` - -* `callback_history_for`: the entire history given as an array of arrays -* `last_callback_for`: same as `callback_history_for(xxx).last` -* `clear_callback_history_for`: clears the array \(userful for repeating test variations without remounting\) -* `event_history_for, last_event_for, clear_event_history_for`: same but for events. - -#### The `pause` Method - -For debugging. Everything stops, until you type `go()` in the client console. Running `binding.pry` also has this effect, and is often sufficient, however it will also block the server from responding unless you have a multithreaded server. - -#### The `attributes_on_client` Method - -_This feature is currently untested - use at your own risk._ - -This reads the value of active record model attributes on the client. - -In other words the method `attributes_on_client` is added to all ActiveRecord models. You then take a model you have instance of on the server, and by passing the Capybara page object, you get back the attributes for that same model instance, currently on the client. - -```ruby -expect(some_record_on_server.attributes_on_client(page)[:fred]).to eq(12) -``` - -> Note that after persisting a record the client and server will be synced so this is mainly useful for debug or in rare cases where it is important to interrogate the value on the client before its persisted. - -#### The `size_window` Method - -Sets the size of the test window. You can say: `size_window(width, height)` or pass one of the following standard sizes: to one of the following standard sizes: - -* small: 480 X 320 -* mobile: 640 X 480 -* tablet: 960 X 640 -* large: 1920 X 6000 -* default: 1024 X 768 - -example: `size_window(:mobile)` - -You can also modify the standard sizes with `:portrait` - -example: `size_window(:table, :portrait)` - -You can also specify the size by providing the width and height. - -example: `size_window(600, 600)` - -size\_window with no parameters is the same as `size_window(:default)` - -Typically you will use this in a `before(:each)` or `before(:step)` block - -#### The `add_class` Method - -Sometimes it's useful to change styles during testing \(mainly for debug so that changes on screen are visible.\) - -The `add_class` method takes a class name \(as a symbol or string\), and hash representing the style. - -```ruby -it "can add classes during testing" do - add_class :some_class, borderStyle: :solid - mount 'StyledDiv' do - class StyledDiv < HyperComponent - render(DIV, id: 'hello', class: 'some_class') do - 'Hello!' - end - end - end - expect(page.find('#hello').native.css_value('border-right-style')).to eq('solid') -end -``` - -## Integration with the Steps gem - -The [rspec-steps gem](https://github.com/LRDesign/rspec-steps) can be useful in doing client side testing. Without rspec-steps, each test spec will cause a reload of the browser window. While this insures that each test runs in a clean environment, it is typically not necessary and can really slow down testing. - -The rspec-steps gem will run each test without reloading the window, which is usually fine. - -Checkout the rspec-steps example in the `hyper_spec.rb` file for an example. - -> Note that hopefully in the near future we are going to build a custom capybara driver that will just directly talk to Hyperstack on the client side. Once this is in place these troubles should go away! - Volunteers welcome to help!\* - -## Timecop Integration - -HyperSpec is integrated with [Timecop](https://github.com/travisjeffery/timecop) to freeze, move and speed up time. The client and server times will be kept in sync when you use any these Timecop methods: - -* `freeze`: Freezes time at the specified point in time \(default is Time.now\) -* `travel`: Time runs normally forward from the point specified. -* `scale`: Like travel but times runs faster. -* `return`: Return to normal system time. - -For example: - -```ruby -Timecop.freeze # freeze time at current time -# ... test some stuff -Timecop.freeze Time.now+10.minutes # move time forward 10 minutes -# ... check to see if expected events happened etc -Timecop.return -``` - -```ruby -Timecop.scale 60, Time.now-1.year do - # Time will begin 1 year ago but advance 60 times faster than normal - sleep 10 - # still sleeps for 10 seconds YOUR time, but server and client will - # think 10 minutes have passed -end -# no need for Timecop.return if using the block style -``` - -See the Timecop [README](https://github.com/travisjeffery/timecop/blob/master/README.markdown) for more details. - -> There is one confusing thing to note: On the server if you `sleep` then you will sleep for the specified number of seconds when viewed _outside_ of the test. However inside the test environment if you look at Time.now, you will see it advancing according to the scale factor. Likewise if you have a `after` or `every` block on the client, you will wait according to _simulated_ time. - diff --git a/docs/development-workflow/hyper-spec/01-installation.md b/docs/development-workflow/hyper-spec/01-installation.md new file mode 100644 index 000000000..bbdb3f75c --- /dev/null +++ b/docs/development-workflow/hyper-spec/01-installation.md @@ -0,0 +1,49 @@ +# HyperSpec Installation + +Add `gem 'hyper-spec'` to your Gemfile in the usual way. +Typically in a Rails app you will add this in the test section of your Gemfile: + +```ruby +group :test do + gem 'hyper-spec', '~> 1.0.alpha1.0' +end +``` + +Make sure to `bundle install`. + +HyperSpec is integrated with the `pry` gem for debugging, so it is recommended to add the `pry` gem as well. + +HyperSpec will also use the `timecop` gem if present to allow you to control and synchronize time on the server and the client. + +A typical spec_helper file when using HyperSpec will look like this: + +```ruby +# spec_helper.rb +require 'hyper-spec' +require 'pry' # optional + +ENV["RAILS_ENV"] ||= 'test' +require File.expand_path('../test_app/config/environment', __FILE__) + +require 'rspec/rails' +require 'timecop' # optional + +# any other rspec configuration you need +# note HyperSpec will include chrome driver for providing the client +# run time environment +``` + +To load the webdriver and client environment your spec should have the +`:js` flag set: + +```ruby +# the js flag can be set on the entire group of specs, or a context +describe 'some hyper-specs', :js do + ... +end + +# or for an individual spec + it 'an individual hyper-spec', :js do + ... + end +``` diff --git a/docs/development-workflow/hyper-spec/02-tutorial.md b/docs/development-workflow/hyper-spec/02-tutorial.md new file mode 100644 index 000000000..d988dc86c --- /dev/null +++ b/docs/development-workflow/hyper-spec/02-tutorial.md @@ -0,0 +1,122 @@ +# Tutorial + +For this quick tutorial lets assume you have an existing Rails app that +already uses RSpec to which you have added a first Hyperstack component to +try things out. + +For your trial, you have created a very simple component that shows +the number of orders shipped by your companies website: + +```ruby +class OrdersShipped < HyperComponent + def format_number(number) + number.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse + end + + render(DIV, class: 'orders-shipped') do + format_number Order.shipped.count + end +end +``` + +> Note that styling can be taken care of in the usual way by +> providing styles for the `orders-shipped` css class. All we care +> about here is the *function* of the component. + +Meanwhile `Order` is an ActiveRecord Model that would look something like this: + +```ruby +class Order < ApplicationRecord + ... + scope :shipped, -> () { where(status: :shipped) } + ... +end +``` + +> Note that when using ActiveRecord models in your specs you will +> need to add the appropriate database setup and cleaner methods like you would +> for any specs used with ActiveRecord. We assume here that as each +> spec starts there are no records in the database + +The `OrdersShipped` component can be mounted on any page of your site, +and assuming the proper policy permissions are provided it will +show the total orders shipped, and will dynamically increase in +realtime. + +A partial spec for this component might look like this: + +```ruby +require 'spec_helper' + +describe 'OrdersShipped', :js do + it 'dynamically displays the orders shipped' do + mount 'OrdersShipped' + expect(find('div.orders-shipped')).to have_content(0) + Order.create(status: :shipped) + expect(find('div.orders-shipped')).to have_content(1) + Order.last.destroy + expect(find('div.orders-shipped')).to have_content(0) + end + + it '#format method' do + on_client { @comp = OrdersShipped.new } + ['1,234,567', '123', '1,234'].each do |n| + expect { @comp.format_number(n.gsub(',','').to_i) } + .on_client_to eq(n) + end + end +end +``` + +If you are familiar with Capybara then the first spec should +look similar to an integration spec. The difference is instead +of visiting a page, we `mount` the `OrdersShipped` component on a blank page +that hyper-spec will set up for us. This lets us unit test +components outside of any application specific view logic. + +> Note that like Capybara we indicate that a client environment should +> be set up by adding the :js tag. + +Once mounted we can use Capybara finders and matchers to check +if our content is as expected. Because we are running on the server +we can easily add and delete orders, and check the response on the UI. + +The second spec shows how we can do some white box unit testing of our +component. Instead of mounting the component we just create a new +instance which will be invisible since it was not mounted. For this we +use the `on_client` method. + +The `on_client` method takes a block, and will +compile that block using +Opal, and execute it on the client. In this case we simply create a +new `OrderShipped` instance, and assign it to an instance variable, which as you +will see will continue to be available to us later in the spec. + +> Note, if you are an RSpec purist, you would probably prefer to see +> something like `let` be used here instead of an instance variable. Shall we +> say its on the todo list. + +Now that we have our test component setup we can test its `format_number` +method. To do this we put the test expression in a block followed by +`on_client_to`. Again the block will be compiled using Opal, executed on +the client, and the result will be returned to the expectation. + +Notice that the server side variable `n` can be read (but not written) within +the client block. All local variables, memoized variables, and instance variables can +can be read in the client block as long as they represent objects that can be +sensibly marshalled and unmarshalled. + +This has covered the basics of Hyperspec - in summary: + ++ The `js` tag indicates the spec will be using a client environment. ++ `mount`: Mount a component on a blank page. This replaces the `visit` method +for unit testing components. ++ `on_client`: Execute Ruby code on the client (and return the result). ++ `on_client_to`: Execute the expectation block on the client, and then check +the expectation (on the server.) ++ Instance variables retain their values between client execution blocks. ++ All variables accessible to the spec are copied to the client if possible. + +There are many other features such as dealing with promises, passing data to +and from a mounted component, using the `Timecop` gem, and working with a `pry` +session. So read on. diff --git a/docs/development-workflow/hyper-spec/03-methods-and-features.md b/docs/development-workflow/hyper-spec/03-methods-and-features.md new file mode 100644 index 000000000..bbbe27887 --- /dev/null +++ b/docs/development-workflow/hyper-spec/03-methods-and-features.md @@ -0,0 +1,474 @@ +# HyperSpec Methods and Features + +### Expectation Helpers + +These can be used any where within your specs: + ++ [`on_client`](#the-on_client-method) - executes code on the client ++ [`isomorphic`](#the-isomorphic-method) - executes code on the client *and* the server ++ [`mount`](#mounting-components) - mounts a hyperstack component in an empty window ++ [`before_mount`](#before_mount) - specifies a block of code to be executed before the first call to `mount`, `isomorphic` or `on_client` ++ [`insert_html`](#insert_html) - insert some html into a page ++ [`client_options`](#client-initialization-options) - allows options to be specified globally ++ [`run_on_client`](#run_on_client) - same as `on_client` but no value is returned ++ [`reload_page`](#reload_page) - resets the page environment ++ [`add_class`](#add_class) - adds a CSS class ++ [`size_window`](#size_window) - specifies how big the client window should be ++ [`attributes_on_client`](#attributes_on_client) - returns any ActiveModel attributes loaded on the client + +These methods are used after mounting a component to retrieve +events sent outwards from the component: + ++ [`callback_history_for`](#retrieving-event-data-from-the-mounted-component) ++ [`last_callback_for`](#retrieving-event-data-from-the-mounted-component) ++ [`clear_callback_history_for`](#retrieving-event-data-from-the-mounted-component) ++ [`event_history_for`](#retrieving-event-data-from-the-mounted-component) ++ [`last_event_for`](#retrieving-event-data-from-the-mounted-component) ++ [`clear_event_history_for`](#retrieving-event-data-from-the-mounted-component) + +### Expectation Targets + +These can be used within expectations replacing the `to` and `not_to` methods. The expectation expression must be inclosed in a block. + ++ [`on_client_to`](#client-expectation-targets), [`to_on_client_not`](#client-expectation-targets) - the expression will be evaluated on the client, and matched on the server. + +These methods have the following aliases to make your specs more readable: ++ [`to_on_client`](#client-expectation-targets) ++ [`on_client_to_not`](#client-expectation-targets) ++ [`on_client_not_to`](#client-expectation-targets) ++ [`to_not_on_client`](#client-expectation-targets) ++ [`not_to_on_client`](#client-expectation-targets) ++ [`to_then`](#client-expectation-targets) ++ [`then_to_not`](#client-expectation-targets) ++ [`then_not_to`](#client-expectation-targets) ++ [`to_not_then`](#client-expectation-targets) ++ [`not_to_then`](#client-expectation-targets) + +in addition ++ [`with`](#client-expectation-targets) - can be chained with the above methods to pass data to initialize local variables on the client + +### Other Debugging Aids + +The following methods are used primarly at a debug break point, most require you use binding.pry as your debugger: + ++ [`to_js`](#to_js) - returns the ruby code compiled to JS. ++ [`c?`](#c?) - alias for `on_client`. ++ [`ppr`](#ppr) - print the results of the ruby expression on the client console. ++ [`debugger`](#debugger) - Sets a debug breakpoint on code running on the client. ++ [`open_in_chrome`](#open_in_chrome) - Opens a chrome browser that will load the current state. ++ [`pause`](#pause) - Halts execution on the server without blocking I/O. + +### Available Webdrivers + +HyperSpec comes integrated with Chrome and Chrome headless webdrivers. The default configuration will run using Chrome headless. To see what is going on set the `DRIVER` environment variable to `chrome` +```bash +DRIVER=chrome bundle exec rspec +``` + +### Timecop Integration + +You can use the [`timecop` gem](https://github.com/travisjeffery/timecop) to control the flow of time within your specs. Hyperspec will coordinate things with the client so the time on the client is kept in sync with the time on the server. So for example if you use Timecop to advance time 1 day on the server, time on the browser will also advance by one day. + +See the [Client Initialization Options](#client-initialization-options) section for how to control the client time zone, and clock resolution. + +### The `no_reset` flag + +By default the client environment will be reinitialized at the beginning of every spec. If this is not needed you can speed things up by adding the `no_reset` flag to a block of specs. + +### Known Issues + +See the last section below for known issues. + +# Details + +### The `on_client` method + +The on_client method takes a block. The ruby code inside the block will be executed on the client, and the result will be returned. + +```ruby + it 'will print a message on the client' do + on_client do + puts 'hey I am running here on the client!' + end + end +``` + +If the block returns a promise Hyperspec will wait for the promise to be resolved (or rejected) before returning. For example: + +```ruby + it 'waits for a promise' do + start_time = Time.now + result = on_client do + promise = Promise.new + after(10.seconds) { promise.resolve('done!') } + promise + end + expect(result).to eq('done!') + expect(Time.now-start_time).to be >= 10.seconds + end +``` +> HyperSpec will do its best to reconstruct the result back on the server in some sensible way. Occasionally it just doesn't work, in which case you can end the block with a `nil` or some other simple expression, or use the `run_on_client` method, which does not return the result. + +### Accessing variables on the client + +It is often useful to pass variables from the spec to the client. Hyperspec will copy all your local variables, memoized variables, and instance variables known at the time the `on_client` block is compiled to the client.
+```ruby + let!(memoized) { 'a memoized variable' } + it 'will pass variables to the client' do + local = 'a local variable' + @instance = 'an instance variable' + result = on_client { [memoized, local, @instance] } + expect(result).to eq [memoized, local, @instance] + end +``` +> Note that memoized variables are not initialized until first +accessed, so you probably want to use the let! method unless you +are sure you are accessing the memoized value before sending it to the client. + +The value of instance variables initialized on the client are preserved +across blocks executed on the client. For example: +```ruby + it 'remembers instance variables' do + on_client { @total = 0 } + 10.times do |i| + # note how we are passing i in + on_client { @total += i } + end + result = on_client { @total } + expect(result).to eq(10 * 11 / 2) + end +``` + +> Be especially careful of this when using the [`no_reset` flag](#the-no_reset-flag) as instance variables will retain their values between each spec in this mode. + +#### White and Black Listing Variables + +By default all local variables, memoized variables, and instance variables in scope in the spec will be copied to the client. This can be controlled through the `include_vars` and `exclude_vars` [client options](#client-initialization-options). + +`include_vars` can be set to ++ an array of symbols: only those vars will be copied, ++ a single symbol: only that var will be copied, ++ any other truthy value: all vars will be copied (the default) ++ or nil, false, or an empty array: no vars will be copied. + +`exclude_vars` can be set to ++ an array of symbols - those vars will **not** be copied, ++ a single symbol - only that var will be excluded, ++ any other truthy value - no vars will be copied, ++ or nil, false, or an empty array - all vars will be copied (the default). + +Examples: + +```Ruby + # don't copy vars at all. + client_option exclude_vars: true + # only copy var1 and the instance var @var2 + client_option include_vars: [:var1, :@var2] + # only exclude foo_var + client_option exclude_vars: :foo_var +``` + +Note that the exclude_vars list will take precedence over the include_vars list. + +The exclude/include lists can be overridden on an individual call to on_client by providing a hash of names and values to on_client: + +```ruby + result = on_client(var: 12) { var * var } + expect(result).to eq(144) +``` + +You can do the same thing on expectations using the `with` method - See [Client Expectation Targets](#client-expectation-targets). + + +### The `isomorphic` method + +The `isomorphic` method works the same as `on_client` but in addition it also executes the same block on the server. It is especially useful when doing some testing of +ActiveRecord models, where you might want to modify the behavior of the model on server and the client. + +```ruby + it 'can run code the same everywhere!' do + isomorphic do + def factorial(x) + x.zero? ? 1 : x * factorial(x - 1) + end + end + + on_the_client = on_client { factorial(7) } + on_the_server = factorial(7) + expect(on_the_client).to eq(on_the_server) + end +``` + +### Client Initialization Options + +The first time a spec runs code on the client, it has to initialize a browser context. You can use the `client_options` (aka `client_option`) method to specify the following options when the page is loaded. + ++ `time_zone` - browsers always run in the local time zone, if you want to force the browser to act as if its in a different zone, you can use the time_zone option, and provide any valid zone that the rails `in_time_zone` method will accept.
+Example: `client_option time_zone: 'Hawaii'` ++ `clock_resolution`: Indicates the resolution that the simulated clock will run at on the client, when using the TimeCop gem. The default value is 20 (milliseconds). ++ `include_vars`: white list of all vars to be copied to the client. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. ++ `exclude_vars`: black list of all vars not to be copied to the client. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. ++ `render_on`: `:client_only` (default), `:server_only`, or `:both` +Hyperstack components can be prerendered on the server. The `render_on` option controls this feature. For example `server_only` is useful to insure components are properly prerendered. *See the `mount` method [below](#mounting-components) for more details on rendering components* ++ `no_wait`: After the page is loaded the system will by default wait until all javascript requests to the server complete before proceeding. Specifying `no_wait: true` will skip this. ++ `javascript`: The javascript asset to load when mounting the component. By default it will be `application` (.js is assumed). Note that the standard Hyperstack configuration will compile all the client side Ruby assets as well as javascript packages into the `application.js` file, so the default will work fine. ++ `style_sheet`: The style sheet asset to load when mounting the component. By default it will be `application` (.css is assumed). ++ `controller` - **(expert zone!)** specify a controller that will be used to mount the +component. By default hyper-spec will build a controller and route to handle the request from the client to mount the component. + +Any other options not listed above will be passed along to the Rail's controller `render` method. So for example you could specify some other specific layout using `client_option layout: 'special_layout'` + +Note that this method can be used in the `before(:each)` block of a spec context to provide options for all the specs in the block. + +### Mounting Components + +The `mount` method is used to render a component on a page: + +```ruby + it 'can display a component for me' do + mount 'SayHello', name: 'Lannar' do + class SayHello < HyperComponent + param :name + render(DIV) do + "Hello #{name}!" + end + end + end + + expect(page).to have_content('Hello Lannar') + end +``` + +The `mount` method has a few options. In it's simplest form you specify just the name of the component that is already defined in your hyperstack code and it will be mounted. + +You can add parameters that will be passed to the component as in the above example. As the above example also shows you can also define code within the block. This is just shorthand for defining the code before hand using `on_client`. The code does not have to be the component being mounted, but might be just some logic to help with the test. + +In addition `mount` can take any of the options provided to `client_options` (see above.) To provide these options, you must provide a (possibly) empty params hash. For example: +```ruby +mount 'MyComponent', {... params ... }, {... opts ... } +``` + +### Retrieving Event Data From the Mounted Component + +Components *receive* parameters, and may send callbacks and events back out. To test if a component has sent the appropriate data you can use the following methods: + ++ `callback_history_for` ++ `last_callback_for` ++ `clear_callback_history_for` ++ `event_history_for` ++ `last_event_for` ++ `clear_event_history_for` + +```ruby + it 'can check on a clients events and callbacks' do + mount 'BigTalker' do + class BigTalker < HyperComponent + fires :i_was_clicked + param :call_me_back, type: Proc + + before_mount { @click_counter = 0 } + + render(DIV) do + BUTTON { 'click me' }.on(:click) do + @click_counter += 1 + i_was_clicked! + call_me_back.call(@click_counter) + end + end + end + end + 3.times do + find('button').click + end + # the history is an array, one element for each item in the history + expect(event_history_for(:i_was_clicked).length).to eq(3) + # each item in the array is itself an array of the arguments + expect(last_call_back_for(:call_me_back)).to eq([3]) + # clearing the history resets the array to empty + clear_event_history_for(:i_was_clicked) + expect(event_history_for(:i_was_clicked).length).to eq(0) + end +``` + +> Note that you must declare the params as type `Proc`, or use +the `fires` method to declare an event for the history mechanism to work. + +### Other Helpers + +#### `before_mount` + +Specifies a block of code to be executed before the first call to `mount`, `isomorphic` or `on_client`. This is primarly useful to add to an rspec `before(:each)` block containing common client code needed by all the specs in the context. + +> Unlike `mount`, `isomorphic` and `on_client`, `before_mount` does not load the client page, but will wait for the first of the other methods to be called. + +#### `add_class` + +Adds a CSS class. The first parameter is the name of the class, and the second is a hash of styles, represented in the React [style format.](https://reactjs.org/docs/dom-elements.html#style) + +Example: `add_class :some_class, borderStyle: :solid` adds a class with style `border-style: 'solid'` + +#### `run_on_client` + +same as `on_client` but no value is returned. Useful when the return value may be too complex to marshall and unmarshall using JSON. + +#### `reload_page` + +Shorthand for `mount` with no parameters. Useful if you need to reset the client within a spec. + +#### `size_window` + +Indicates the size of the browser window. The values can be given either symbolically or as two numbers (width and height). Predefined sizes are: + ++ `:small`: 480 x 320 ++ `:mobile` 640 x 480 ++ `:tablet` 960 x 64, ++ `:large` 1920 x 6000 ++ `:default` 1024 x 768 + +All of the above can be modified by providing the `:portrait` option as the first or second parameter. + +So for example the following are all equivalent: + ++ `size_window(:small, :portrait)` ++ `size_window(:portrait, :small)` ++ `size_window(320, 480)` + +#### `attributes_on_client` + +returns any `ActiveModel` attributes loaded on the client. HyperModel will normally begin a load cycle as soon as you access the attribute on the client. However it is sometimes useful to see what attributes have already been loaded. + +#### `insert_html` + +takes a string and inserts it into test page when it is mounted. Useful for testing code that is not dependent on Hyper Components. +For example an Opal library that adds some jQuery extensions. + +### Client Expectation Targets + +These can be used within expectations replacing the `to` and `not_to` methods. The expectation expression must be inclosed in a block. + +For example: + +```ruby +it 'has built-in expectation targets' do + expect { RUBY_ENGINE }.on_client_to eq('opal') +end +``` + +The above expectation is short for saying: + +```ruby + result = on_client { RUBY_ENGINE } + expect(result).to eq('opal') +``` + +These methods have the following aliases to make your specs more readable: ++ `to_on_client` ++ `on_client_to_not` ++ `on_client_not_to` ++ `to_not_on_client` ++ `not_to_on_client` ++ `to_then` ++ `then_to_not` ++ `then_not_to` ++ `to_not_then` ++ `not_to_then` + +The `then` variants are useful to note that the spec involves a promise, but it does no explicit checking that the result comes from a promise. + +In addition the `with` method can be chained with the above methods to pass data to initialize local variables on the client: + +```ruby + it 'can pass values to the client using the with method' do + expect { foo * foo }.with(foo: 12).to_on_client eq(144) + end +``` + +By default HyperSpec will copy all local variables, memoized variables, and instance variables defined in a spec to the client. The specific variables can also be white listed and black listed. The `with` method overrides any white or black listed values. So for example if you prefer to use the more explicit `with` method to pass values to the client, you can add `client_option exclude_vars: true` in a `before(:all)` block in your spec helper. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. + +### Useful Debug Methods + +These methods are primarily designed to help debug code and specs. + +#### `c?` + +Shorthand for `on_client`, useful for entering expressions in the pry console, to investigate the state of the client. + +```ruby +pry:> c? { puts 'hello on the console' } # prints hello on the client +-> nil +``` + +#### `to_js` + +Takes a block like `on_client` but rather than running the code on the client, simply returns the resulting code. This is useful for debugging obscure problems when the Opal compiler or some feature of +Hyperspec is suspected as the issue. + +#### `ppr` + +Takes a block like `on_client` and prints the result on the client console using JS console.log. Equivalent to doing + +```ruby + on_client do + begin + ... + end.tap { |r| `console.log(r)` } + end +``` + +This is useful when the result cannot be usefully returned to the server, +or when the result of interest is better looked at as the raw +javascript object. + +#### `debugger` + +This psuedo method can be inserted into any code executed on the client. It will cause the code to stop, and enter a *javascript* read-eval loop, within the debug console. + +Unfortunately ATM we do not have the technology to enter a *Ruby* read-eval loop at an arbitrary point on the client. + +> Note: due to a bug in the Opal compiler your code should not have `debugger` as the last expression in a method or a block. In this situation add any expression (such as nil) after the debugger statement. +```ruby +def foo + ... some code ... + debugger # this will fail with a compiler syntax error +end +``` + +#### `open_in_chrome` +By default specs are run with headless chrome, so there is no visible browser window. The `open_in_chrome` method will open a browser window, and load it with the current state. + +You can also run specs in a visible chrome window by setting the `DRIVER` environment variable to `chrome`. i.e. (`DRIVER=chrome bundle exec rspec ...`) + +#### `pause` +The method is typically not needed assuming you are using a multithreaded server like Puma. If for whatever reason the pry debug session is not multithreaded, *and* you want to try some kind of experiment on the javascript console, *and* those experiments make requests to the server, you may not get a response, because all threads are in use. + +You can resolve this by using the `pause` method in the debug session which will put the server debug session into a non-blocking loop. You can then experiment in the JS console, and when done release the pause by executing `go()` in the *javascript* debug console. + +### Known Issues + +####Using `visit` and the Application Layout +Currently this is not well integrated (see [issue 398](https://github.com/hyperstack-org/hyperstack/issues/398)). If you want to visit a page on the website +using `visit`, the following will not work: Timecop integration, and the `insert_html` and `before_mount` methods. You will also have to execute this line in your spec: +```ruby +page.instance_variable_set("@hyper_spec_mounted", true) +``` +Upvote issue 398 if this presents a big problem for you. + +#### Some Complex Expressions Do Not Work +> This has been fixed in Parser version 2.7 which works with Opal 1.0 +> So the issue is only with older versions of Opal. + +[Issue 127](https://github.com/hyperstack-org/hyperstack/issues/127) + +You may get an error like this when running a spec: + +```text +(string):1:20: error: unexpected token tOP_ASGN +(string):1: hash.[]("foo") += 1 +(string):1: +``` + +The problem is the unparser incorrectly generates `hash.[]("foo") += 1` instead of `hash['foo'] += 1`. + +The good news its pretty easy to find such expressions and replace them with something like + +`hash["foo"] = hash["foo"] + 1` diff --git a/docs/development-workflow/hyper-spec/04-using-with-rack.md b/docs/development-workflow/hyper-spec/04-using-with-rack.md new file mode 100644 index 000000000..7c5147f40 --- /dev/null +++ b/docs/development-workflow/hyper-spec/04-using-with-rack.md @@ -0,0 +1,56 @@ +# Using Hyperspec with Rack + +Hyperspec will run with Rails out of the box, but you can also use Hyperspec with any Rack application, with just a little more setup. For example here is a sample configuration setup with Sinatra: + +```ruby +# Gemfile +... + +gem "sinatra" +gem "rspec" +gem "pry" +gem "opal" +gem "opal-sprockets" +gem "rack" +gem "puma" +group :test do + # gem 'hyper-spec', '~> 1.0.alpha1.0' + # or to use edge: + gem 'hyper-spec', + git: 'git://github.com/hyperstack-org/hyperstack.git', + branch: 'edge', + glob: 'ruby/*/*.gemspec' +end +``` + +```ruby +# spec/spec_helper.rb + +require "bundler" +Bundler.require +ENV["RACK_ENV"] ||= "test" + +# require your application files as needed +require File.join(File.dirname(__FILE__), "..", "app.rb") + +# bring in needed support files +require "rspec" +require "rack/test" +require "hyper-spec/rack" + +# assumes your sinatra app is named app +Capybara.app = HyperSpecTestController.wrap(app: app) + +set :environment, :test +set :run, false +set :raise_errors, true +set :logging, false +``` + +### Details + +The interface between Hyperspec and your application environment is defined by the `HyperspecTestController` class. This file typically includes a set of helper methods from `HyperSpec::ControllerHelpers`, which can then be overridden to give whatever behavior your specific framework needs. Have a look at the `hyper-spec/rack.rb` and `hyper-spec/controller_helpers.rb` files in the Hyperspec gem directory. + +### Example + +A complete (but very simple) example is in this repos `ruby/examples/misc/sinatra_app` directory diff --git a/docs/development-workflow/hyper-spec/README.md b/docs/development-workflow/hyper-spec/README.md new file mode 100644 index 000000000..44dd165e3 --- /dev/null +++ b/docs/development-workflow/hyper-spec/README.md @@ -0,0 +1,41 @@ +# HyperSpec + +## Adding client side testing to RSpec + +The `hyper-spec` gem supports the Hyperstack goals of programmer productivity and seamless web development by allowing testing to be done with minimal concern for the client-server interface. + +The `hyper-spec` gem adds functionality to the `rspec`, `capybara`, `timecop` and `pry` gems allowing you to do the following: + ++ write component and integration tests using the rspec syntax and helpers ++ write specs that run on both the client and server ++ evaluate client side ruby expressions from within specs and while using `pry` ++ share data between the client and server within your specs ++ control and synchronize time on the client and the server + +HyperSpec can be used standalone, but if used as part of a Hyperstack application it allows straight forward testing of Hyperstack Components and your ActiveRecord Models. + +So for example here is part of a simple unit test of a TodoIndex component: + +```ruby +it "will update the TodoIndex", js: true do + # mounts the TodoIndex component (client side) + mount 'TodoIndex' + # Todo is an ActiveRecord Model + # create a new Todo on the server (we could use FactoryBot of course) + todo_1 = Todo.create(title: 'this todo created on the server') + # verify that UI got updated + expect(find('.ToDoItem-Text').text).to eq todo_1.title + # verify that the count of Todos on the client side DB matches the server + expect { Todo.count }.on_client_to eq Todo.count + # now create another Todo on the client + new_todo_title = 'this todo created on the client' + # note that local variables are copied from the server to the client + on_client { Todo.create(title: new_todo_title) } + # the Todo should now be reflected on the server + expect(Todo.last.title).to eq new_todo_title +end +``` + +When using HyperSpec all the specs execute on the server side, but they may also interrogate the state of the UI as well as the state +of any of the client side objects. The specs can execute any valid Ruby code client side to create new test objects as well as do +white box testing. This keeps the logic of your specs in one place. diff --git a/docs/development-workflow/hyper-trace.md b/docs/development-workflow/hyper-trace.md new file mode 100644 index 000000000..ebb83a1aa --- /dev/null +++ b/docs/development-workflow/hyper-trace.md @@ -0,0 +1,6 @@ + +HyperTrace provides a simple way to log information to the Javascript console. You get a real time display of the parameters being passed to +the traced methods, the state of their instance variables, and the return values. You can also set conditional breakpoints so you can stop execution +and dig deeper from the Javascript console. + +## This Page Under Construction diff --git a/docs/development-workflow/tools.md b/docs/development-workflow/tools.md deleted file mode 100644 index 2674cff95..000000000 --- a/docs/development-workflow/tools.md +++ /dev/null @@ -1,30 +0,0 @@ -# Tools - -**Work in progress - ALPHA \(docs and code\)** - -## Hyper-console - -Hyper-Console will open a new popup window, that is running an IRB style read-eval loop. The console window will compile what ever ruby code you type, and if it compiles, will send it to your main window for execution. The result \(or error message\) plus any console output will be displayed in the console window. - -## Hyper-spec - -With Hyper-Spec you can run isomorphic specs for all your Hyperstack code using RSpec. Everything runs as standard RSpec test specs. - -Hyperstack wants to make the server-client divide as transparent to the developer as practical. Given this, it makes sense that the testing should also be done with as little concern for client versus server. - -Hyper-spec allows you to directly use tools like FactoryGirl \(or Hyperstack Operations\) to setup some test data, then run a spec to make sure that a component correctly displays, or modifies that data. You can use Timecop to manipulate time and keep in sync between the server and client. This makes testing easier and more realistic without writing a lot of redundant code. - -## Hyper-trace - -Method tracing and conditional break points for Opal and Hyperstack debug. - -Typically you are going to use this in Capybara or Opal-RSpec examples that you are debugging. - -Hyper-trace adds a hypertrace method to all classes that you will use to switch on tracing and break points. - -## Opal Hot Reloader - -Opal Hot Reloader is for pure programmer joy \(not having to reload the page to compile your source\) and the Opal Console is incredibly useful to test how Ruby code compiles to JavaScript. - -Opal Hot Reloader is going to just dynamically \(via a websocket connection\) chunks of code in the page almost instaneously. - diff --git a/docs/isomorphic-dsl/hyper-model.md b/docs/hyper-model/README.md similarity index 93% rename from docs/isomorphic-dsl/hyper-model.md rename to docs/hyper-model/README.md index 63f0a4228..cad9ae85e 100644 --- a/docs/isomorphic-dsl/hyper-model.md +++ b/docs/hyper-model/README.md @@ -1,6 +1,9 @@ -# Isomorphic Models + +HyperModel extends Rails ActiveRecord models so that you have direct access to them on the client, using the same ActiveRecord classes and methods as you +would on the server. Nothing new to learn, or configure, just plug in and go. + +## This Page Under Construction -**Work in progress - ALPHA \(docs and code\)** In Hyperstack, your ActiveRecord Models are available in your Isomorphic code. @@ -12,7 +15,7 @@ In other words, one browser creates, updates, or destroys a Model, and the chang * You access your Model data in your Components, Operations, and Stores just like you would on the server or in an ERB or HAML view file. * If an optional push transport is connected Hyperstack broadcasts any changes made to your ActiveRecord models as they are persisted on the server or updated by one of the authorized clients. -* Some Models can be designated as _server-only_ which means they are not available to the Isomorphic code. +* Some Models (or even parts of Models) can be designated as _server-only_ which means they are not available to the client code. For example, consider a simple model called `Dictionary` which might be part of Wiktionary type app. @@ -35,8 +38,7 @@ class WordOfTheDay < Hyperstack::Component def pick_entry! # pick a random word and assign the selected record to entry - @entry = Dictionary.defined.all[rand(Dictionary.defined.count)] - force_update! # redraw our component when the word changes + mutate @entry = Dictionary.defined[rand(Dictionary.defined.count)] # Notice that we use standard ActiveRecord constructs to select our # random entry value end @@ -58,7 +60,8 @@ class WordOfTheDay < Hyperstack::Component end ``` -For complete examples with _push_ updates, see any of the apps in the `examples` directory, or build your own in 5 minutes following one of the quickstart guides: +**This is the entire code. There are no application APIs needed. The synchronization between server and client is completely taken care of by HyperModel. If you have +an existing code base little to updates to your existing Models is needed, and you will use the same ActiveRecord API you have been using.** ## Isomorphic Models @@ -69,34 +72,7 @@ In order for Hyperstack to see your Models \(and make them Isomorphic\) you need | **Location of Models** | **Scope** | | :--- | :--- | | `app\models` | Server-side code only | -| `app\Hyperstack\models` | Isomorphic code \(client and server\) | - -### Rails 5.1.x - -Upto Rails 4.2, all models inherited from `ActiveRecord::Base`. But starting from Rails 5, all models will inherit from `ApplicationRecord`. - -To accommodate this change, the following file has been automatically added to models in Rails 5 applications. - -```ruby -# app/models/application_record.rb -class ApplicationRecord < ActiveRecord::Base - self.abstract_class = true -end -``` - -For Hyperstack to see this change, this file needs to be moved \(or copied if you have some server-side models\) to the `apps/Hyperstack` folder. - -### Explicit Scope Access - -In order to prevent unauthorized access to information like scope counts, lists of record ids, etc, Hyperstack now \(see issue [https://github.com/ruby-Hyperstack/hyper-mesh/issues/43](https://github.com/ruby-Hyperstack/hyper-mesh/issues/43)\) requires you explicitly allow scopes to be viewed on the client, otherwise you will get an AccessViolation. - -To globally allow access to all scopes add this to the ApplicationRecord class - -```ruby -class ApplicationRecord < ActiveRecord::Base - regulate_scope :all -end -``` +| `app\hyperstack\models` | Isomorphic code \(client and server\) | ## ActiveRecord API @@ -106,15 +82,15 @@ Hyperstack uses a subset of the standard ActiveRecord API to give your Isomorphi Hyperstack integrates with React \(through Components\) to deliver your Model data to the client without you having to create extra APIs or specialized controllers. The key idea of React is that when state \(or params\) change, the portions of the display effected by this data will be updated. -Hyperstack automatically creates React state objects that will be updated as server side data is loaded or changes. When these states change the associated parts of the display will be updated. +On the client each database record being used by the client is represented as an observable store **([see the chapter on HyperState for details](hyper-state/README.md))** which will mutate as server side data is loaded or changes. When these states change the associated parts of the display will be updated. -A brief overview of how this works will help you understand the how Hypeloop gets the job done. +A brief overview of how this works will help you understand the how HyperStack gets the job done. #### Rendering Cycle On the UI you will be reading models in order to display data. -If during the rendering of the display the Model data is not yet loaded, placeholder values \(the default values from the `columns_hash`\) will be returned by Hyperstack. +If during the rendering of the display the Model data is not yet loaded, placeholder values \(the default values from the database schema\) will be returned by Hyperstack. Hyperstack then keeps track of where these placeholders \(or `DummyValue`s\) are displayed, and when they do get loaded, those parts of the display will re-render. @@ -140,7 +116,7 @@ There are a number of methods that allow you to interact with this load cycle wh #### New and Create -`new`: Takes a hash of attributes and initializes a new unsaved record. The values of any attributes not specified in the hash will be taken from the Models default values specified in the `columns_hash`. +`new`: Takes a hash of attributes and initializes a new unsaved record. The values of any attributes not specified in the hash will be taken from the Models default values specified in the data base schema. If `new` is passed a native javascript object it will be treated as a hash and converted accordingly. @@ -170,11 +146,23 @@ scope :completed, `unscoped` and `all`: These builtin scopes work just like standard ActiveRecord. +BTW: to save typing you can skip the `all`: Models will respond like enumerators. + ```ruby Word.all.each { |word| LI { word.text }} ``` -BTW: to save typing you can skip the `all`: Models will respond like enumerators. +`where`: The where method can be used to filter records: + +```ruby +Word.where("LENGTH(text) = ?", n) +``` + +> The `where` method is implemented internally as a scope on the client that +will execute the where method on the server. If the parameters to the where +method the scope will be updated on the client, but using SQL in the where as +in the above example will get executed on the server. + `find`: takes an id and delivers the corresponding record. @@ -192,6 +180,21 @@ Word.find_by_text('hello') # short for Word.find_by(text: 'hello') Word.offset(500).limit(20) # get words 500-519 ``` +#### Applying Class Methods to Collections + +Like Rails if you define a class method on a model, you can apply it to a collection of those records, allowing you +to chain methods with scopes (and relationships) + +```ruby +class Word < ApplicationRecord + def self.page(pg) + offset(pg-1 * 20).limit(20) + end +end +... + Word.some_scope.page(3) +``` + #### Relationships and Aggregations `belongs_to, has_many, has_one`: These all work as on the server. **However it is important that you fully specify both sides of the relationship.** @@ -234,6 +237,9 @@ class User < ActiveRecord::Base end ``` + + + Sometimes it is desirable to only run the method on the server. This can be done using the `server_method` macro: ```ruby @@ -694,4 +700,3 @@ You can of course simulate server side changes to your Models through this conso * **On page load you get a message about super class mismatch for `DummyValue`** You are still have the old `reactive-record` gem in your Gemfile, remove it from your gemfile and your components manifest. * **On page load you get a message about no method `session` for `nil`** You are still referencing the old reactive-ruby or reactrb gems either directly or indirectly though a gem like reactrb-router. Replace any gems like `reactrb-router` with `hyper-router`. You can also just remove `reactrb`, as `hyper-model` will be included by the `hyper-model` gem. * **You keep seeing the message `WebSocket connection to 'ws://localhost:3000/cable' failed: WebSocket is closed before the connection is established.`** every few seconds in the console. There are probably lots of reasons for this, but it means ActionCable can't get itself going. One reason is that you are trying to run with Passenger instead of Puma, and trying to use `async` mode in cable.yml file. `async` mode requires Puma. - diff --git a/docs/client-dsl/hyper-router.md b/docs/hyper-router/README.md similarity index 95% rename from docs/client-dsl/hyper-router.md rename to docs/hyper-router/README.md index 96dd377f6..2e44eb851 100644 --- a/docs/client-dsl/hyper-router.md +++ b/docs/hyper-router/README.md @@ -1,6 +1,12 @@ -# Client-side Routing -HyperRouter is a DSL wrapper for [ReactRouter v4.x](https://github.com/ReactTraining/react-router) to provide client-side routing for Single Page Applications (SPA). + +**HyperRouter is a DSL wrapper for [ReactRouter v4.x](https://github.com/ReactTraining/react-router) to provide +client-side routing for Single Page Applications (SPA). As the user changes "pages" instead of reloading from the server your App will mount +different components.** + +## This Page Under Construction + + ## Usage diff --git a/docs/hyper-state/README.md b/docs/hyper-state/README.md new file mode 100644 index 000000000..863afa1eb --- /dev/null +++ b/docs/hyper-state/README.md @@ -0,0 +1,508 @@ + + +### Revisiting the Tic Tac Toe Game + +The easiest way to understand HyperState is by example. If you you did not see the Tic-Tac-Toe example, then **[please review it now](/client-dsl/interlude-tic-tac-toe.md)**, as we are going to use this to demonstrate how to use the `Hyperstack::State::Observable` module. + +In our original Tic-Tac-Toe implementation the state of the game was stored in the `DisplayGame` component. State was updated by +"bubbling up" events from lower level components up to `DisplayGame` where the event handler updated the state. + +This is a nice simple approach but suffers from two issues: ++ Each level of lower level components must be responsible for bubbling up the events to the higher component. ++ The `DisplayGame` component is responsible for both managing state and displaying the game. + +As our applications become larger we will want a way to keep each component's interface isolated and not dependent on the overall +architecture, and to insure good separation of concerns. + +The `Hyperstack::State::Observable` module allows us to put the game's state into a separate class which can be accessed from any +component: No more need to bubble up events, and no more cluttering up our `DisplayGame` component with state management +and details of the game's data structure. + +Here is the game state and associated methods moved out of the `DisplayGame` component into its own class: + +```ruby +class Game + include Hyperstack::State::Observable + + def initialize + @history = [[]] + @step = 0 + end + + observer :player do + @step.even? ? :X : :O + end + + observer :current do + @history[@step] + end + + state_reader :history + + WINNING_COMBOS = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]] + + def current_winner? + WINNING_COMBOS.each do |a, b, c| + return current[a] if current[a] && current[a] == current[b] && current[a] == current[c] + end + false + end + + mutator :handle_click! do |id| + board = history[@step] + return if current_winner? || board[id] + + board = board.dup + board[id] = player + @history = history[0..@step] + [board] + @step += 1 + end + + mutator(:jump_to!) { |step| @step = step } +end +``` +Let's go over the each of the differences from the code that was in the `DisplayGame` component. + +```ruby +class Game + include Hyperstack::State::Observable +``` + +`Game` is now in its own class and includes `Hyperstack::State::Observable`. This adds a number of methods to `Game` that allows our class to become +a *reactive store*. When `Game` interacts with other stores and components they will be updated as the state of `Game` changes. + +```ruby + def initialize + @history = [[]] + @step = 0 + end +``` + +In the original implementation we initialized the two state variables `@history` and `@step` in the `before_mount` callback. The same initialization +is now in the `initialize` method which will be called when a new instance of the game is created. This will still be done in the `DisplayGame` +`before_mount` callback (see below.) + +```ruby + observer :player do + @step.even? ? :X : :O + end + + observer :current do + @history[@step] + end +``` + +In the original implementation we had instance methods `player` and `current`. Now that `Game` is a separate class we define these +methods using `observer`. + +The `observer` method creates a method that is the inverse of `mutator`. While `mutate` (and `mutator`) indicate that state has +been changed `observe` and `observer` indicate that state has been accessed outside the class. + +```ruby + attr_reader :history +``` + +Just as we have `mutate`, `mutator`, and `state_writer`, we have `observe`, `observer`, and `state_reader`. + +```ruby + WINNING_COMBOS = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]] + + def current_winner? + WINNING_COMBOS.each do |a, b, c| + return current[a] if current[a] && current[a] == current[b] && current[a] == current[c] + end + false + end +``` + +We don't need any changes to `current_winner?`. It accesses the internal state through the `current` method +so there is no need to explicitly make `current_winner?` an observer (but we could, without affecting anything.) + +```ruby + mutator :handle_click! do |id| + board = history[@step] + return if current_winner? || board[id] + + board = board.dup + board[id] = player + @history = history[0..@step] + [board] + @step += 1 + end + + mutator(:jump_to!) { |step| @step = step } +end +``` + +Finally we need no changes to the `handle_click!` and `jump_to!` mutators either. + +#### The Updated DisplayGame Component + +```ruby +class DisplayGame < HyperComponent + before_mount { @game = Game.new } + def moves + return unless @game.history.length > 1 + + @game.history.length.times do |move| + LI(key: move) { move.zero? ? "Go to game start" : "Go to move ##{move}" } + .on(:click) { @game.jump_to!(move) } + end + end + + def status + if (winner = @game.current_winner?) + "Winner: #{winner}" + else + "Next player: #{@game.player}" + end + end + + render(DIV, class: :game) do + DIV(class: :game_board) do + DisplayCurrentBoard(game: @game) + end + DIV(class: :game_info) do + DIV { status } + OL { moves } + end + end +end +``` + +The `DisplayGame` `before_mount` callback is still responsible for initializing the game, but it no longer needs to be aware of +the internals of the game's state. It simply calls `Game.new` and stores the result in the `@game` instance variable. For the rest +of the component's code we call the appropriate method on `@game`. + +We will need to pass the entire game to `DisplayBoard` (we will see why shortly) so we will rename it to `DisplayCurrentBoard`. + +As we will see `DisplayCurrentBoard` will be responsible for directly notifying the game that a user has clicked, so we do not +need to handle any events coming back from `DisplayCurrentBoard`. + +#### The DisplayCurrentBoard Component + +```ruby +class DisplayCurrentBoard < HyperComponent + param :game + + def draw_square(id) + BUTTON(class: :square, id: id) { game.current[id] } + .on(:click) { game.handle_click!(id) } + end + + render(DIV) do + (0..6).step(3) do |row| + DIV(class: :board_row) do + (row..row + 2).each { |id| draw_square(id) } + end + end + end +end +``` + +The `DisplayCurrentBoard` component receives the entire game, and it will access the current board, using the +`current` method, and will directly notify the game when a user clicks using the `handle_click!` method. + +By having `DisplayCurrentBoard` directly deal with user actions, we simplify both components as they do not have to +communicate back upwards via events. Instead we communicate through the central game store. + +### The Flux Loop + +Rather than sending params down to lower level components, and having the components bubble up events, we have created a *Flux Loop*. +The `Game` store holds the state, the top level component reads the state and sends it down to lower level components, those +components update the `Game` state causing the top level component to re-rerender. + +This structure greatly simplifies the structure and understanding of our components, and keeps each component functionally isolated. + +Furthermore algorithms such as `current_winner?` now are neatly abstracted out into their own class. + +### Classes and Instances + +If we are sure we will only want one game board, we could define `Game` with class methods like this: + +```ruby +class Game + include Hyperstack::State::Observable + + class << self + def initialize + @history = [[]] + @step = 0 + end + + observer :player do + @step.even? ? :X : :O + end + + observer :current do + @history[@step] + end + + state_reader :history + + WINNING_COMBOS = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]] + + def current_winner? + WINNING_COMBOS.each do |a, b, c| + return current[a] if current[a] && current[a] == current[b] && current[a] == current[c] + end + false + end + + mutator :handle_click! do |id| + board = history[@step] + return if current_winner? || board[id] + + board = board.dup + board[id] = player + @history = history[0..@step] + [board] + @step += 1 + end + + mutator(:jump_to!) { |step| @step = step } + end +end + +class DisplayBoard < HyperComponent + param :board + + def draw_square(id) + BUTTON(class: :square, id: id) { board[id] } + .on(:click) { Game.handle_click!(id) } + end + + render(DIV) do + (0..6).step(3) do |row| + DIV(class: :board_row) do + (row..row + 2).each { |id| draw_square(id) } + end + end + end +end + +class DisplayGame < HyperComponent + def moves + return unless Game.history.length > 1 + + Game.history.length.times do |move| + LI(key: move) { move.zero? ? "Go to game start" : "Go to move ##{move}" } + .on(:click) { Game.jump_to!(move) } + end + end + + def status + if (winner = Game.current_winner?) + "Winner: #{winner}" + else + "Next player: #{Game.player}" + end + end + + render(DIV, class: :game) do + DIV(class: :game_board) do + DisplayBoard(board: Game.current) + end + DIV(class: :game_info) do + DIV { status } + OL { moves } + end + end +end +``` + +Now instead of creating an instance and passing it around we +call the class level methods on `Game` throughout. + +The `Hyperstack::State::Observable` module will call any class level `initialize` methods in the class or subclasses +before the first component mounts. + +Note that with this approach we can go back to passing just the current board to `DisplayBoard` as `DisplayBoard` can +directly access `Game.handle_click!` since there is only one game. + +### Thinking About Stores + +To summarize: a store is simply a Ruby object or class that using the `observe` and `mutate` methods marks when its internal data +has been observed by some other class, or when its internal data has changed. + +When components render they observe stores throughout the system, and when those stores mutate the components will rerender. + +You as the programmer need only to remember that public methods that read *internal* state must at some point +during their execution declare this using `observe`, `observer`, +`state_reader` or `state_accessor` methods. Likewise a method that changes *internal* state must declare this using `mutate`, `mutator`, `state_writer` or `state_accessor` methods. + +If your store's methods access other stores, you do not need worry about their state, only your own. On the other hand keep in mind +that the built in Ruby Array and Hash classes are **not** stores, so when you modify or read an Array or a Hash its up to you to use +the appropriate `mutate` or `observe` method. + +### Stores and Parameters + +Typically in a large system you will have one or more central stores, and what you end up passing as parameters are either instances of those stores, or some other kind of index into the store. If there is only one store (as in the case of our Game), you +need not pass any parameters at all. + +We can rewrite the previous iteration of `DisplayBoard` to demonstrate this: + +```ruby +class DisplaySquare + param :id + render + BUTTON(class: :square, id: id) { Game.current[id] } + .on(:click) { Game.handle_click(id) } + end +end + +class DisplayBoard < HyperComponent + render(DIV) do + (0..6).step(3) do |row| + DIV(class: :board_row) do + (row..row + 2).each { |id| DisplaySquare(id: id) } + end + end + end +end +``` + +Here `DisplayBoard` no longer takes any parameter (and could be renamed again to `DisplayCurrentBoard`) and now a new component - +`DisplaySquare` - takes the id of the square to display, but the game or the current board are never passed as parameters; +there is no need to as they are implicit. + +Whether to pass (or not pass) a store class, an instance of a store, or some other index into the store is a design decision that depends on +lots of factors, mainly how you see your application evolving over time. + +### Summary of Methods + +All the observable methods can be used either at the class or instance level. + +#### Observing State: `observe, observer, state_reader` + +The `observe` method takes any number of arguments and/or a block. The last argument evaluated or the value of the block is returned. + +The arguments and block are evaluated then the object's state will be *observed*. + +If the block exits with a return or break, the state will **not** be observed. + +```ruby +# evaluate and return a value +observe @history[@step] + +# evaluate a block and return its value +observe do + @history[@step] +end +``` + +The `observer` method defines a new method with an implicit observe: + +```ruby +observer :foo do |x, y, z| + ... +end +``` +is equivilent to +```ruby +def foo(x, y, z) + observe do + ... + end +end +``` + +Again if the block exits with a `return` or `break` the state will **not** be observed. + +The `state_reader` method declares one or more state accessors with an implicit state observation: + +```ruby +state_reader :bar, :baz +``` +is equivilent to +```ruby +def bar + observe @bar +end +def baz + observe @baz +end +``` + +#### Mutating State: `mutate, mutator, state_writer, toggle` + +The `mutate` method takes any number of arguments and/or a block. The last argument evaluated or the value of the block is returned. + +The arguments and block are evaluated then the object's state will be *mutated*. + +If the block exits with a return or break, the state will **not** be mutated. + +```ruby +# evaluate and return a value +mutate @history[@step] + +# evaluate a block and return its value +mutate do + @history[@step] +end +``` + +The `mutator` method defines a new method with an implicit mutate: + +```ruby +mutator :foo do |x, y, z| + ... +end +``` +is equivilent to +```ruby +def foo(x, y, z) + mutate do + ... + end +end +``` + +Again if the block exits with a `return` or `break` the state will **not** be mutated. + +The `state_writer` method declares one or more state accessors with an implicit state mutation: + +```ruby +state_reader :bar, :baz +``` +is equivilent to +```ruby +def bar=(x) + mutate @bar = x +end +def baz=(x) + observe @baz = x +end +``` + +The `toggle` method reverses the polarity of a instance variable: + +```ruby +toggle(:foo) +``` +is equivilent to +```ruby +mutate @foo = !@foo +``` + +#### The `state_accessor` Method + +Combines `state_reader` and `state_writer` methods. + +```ruby +state_accessor :foo, :bar +``` +is equivilent to +```ruby +state_reader :foo, :bar +state_writer :foo, :bar +``` + +### Components and Stores + +The standard `HyperComponent` base class includes `Hyperstack::State::Observable` so any `HyperComponent` has access to +all of the above methods. A component also always **observes itself** so you never need to use `observe` within +a component **unless** the state will be accessed outside the component. However once you start doing that you +would be better off to move the state into a separate store. + +> In addition components also act as the **Observers** in the system. What this means is +that the current component that is running its render method is recording all stores that call `observe`, when +a store mutates, then all the components that recorded observations will be rerendered. diff --git a/docs/installation/README.md b/docs/installation/README.md index 4026317a8..7ba05740e 100644 --- a/docs/installation/README.md +++ b/docs/installation/README.md @@ -1,2 +1,53 @@ # Installation +The easiest way to install Hyperstack in either a new or existing Rails app is to run installer. + +## Pre-Requisites + +#### - Rails >= 5.x + +[Rails Install Instructions](http://railsinstaller.org/en) + +#### - Yarn + +For a full system install including webpacker for managing javascript assets you will +need yarn. To skip adding webpacker use `hyperstack:install:skip-webpack` + +[Yarn Install Instructions](https://yarnpkg.com/en/docs/install#mac-stable) + +## - Creating a New Rails App + +If you don't have an existing Rails app to add Hyperstack to, you can create a new Rails app +with the following command line: + +``` +bundle exec rails new NameOfYourApp -T +``` + +To avoid much pain, do not name your app `Application` as this will conflict with all sorts of +things in Rails and Hyperstack. + +Once you have created the app cd into the newly created directory. + +> The -T option will skip adding minitest directories, as Hyperstack prefers RSpec. + +## - Installing HyperStack + +* add `gem 'rails-hyperstack', "~> 1.0.alpha1.0"` to your gem file +* run `bundle install` +* run `bundle exec rails hyperstack:install` + +> Note: if you want to use the unreleased edge branch your gem specification will be: +> +> ```ruby +> gem 'rails-hyperstack', +> git: 'git://github.com/hyperstack-org/hyperstack.git', +> branch: 'edge', +> glob: 'ruby/*/*.gemspec' +> ``` +> + +## - Start the Rails app + +* `bundle exec foreman start` to start Rails and the Hotloader +* Navigate to `http://localhost:5000/` diff --git a/docs/installation/man-installation.md b/docs/installation/man-installation.md index f2772802d..07ce23e82 100644 --- a/docs/installation/man-installation.md +++ b/docs/installation/man-installation.md @@ -1,65 +1,4 @@ -# Installation - -You can install Hyperstack either - -* using a template - best for new applications \(not working for Rails 6\) yet; -* using a Rails generator - best for existing Rails apps; -* or manually walking through the detailed installation instructions below. - -Even if you use the template or generator, later reading through the detailed installation instructions can be helpful to understand how the system fits together and the generators work. - -## Pre-Requisites - -#### - Rails 5.x [Install Instructions](http://railsinstaller.org/en) - -And for a full system as setup by the template or install generator you will need - -#### - Yarn [Install Instructions](https://yarnpkg.com/en/docs/install#mac-stable) - -## Installing using the template - -This template will create a **new** Rails app with Webpacker from Hyperstack edge branch. This is the easiest way to get started. - -### Run the template - -Simply run the command below to create a new Rails app with Hyperstack all configured: - -```text -rails new MyApp -T -m https://rawgit.com/hyperstack-org/hyperstack/edge/install/rails-webpacker.rb -``` - -> Note: The -T flag will not install minitest directories, leaving room for Rspec and Hyperspec. See the HyperSpec readme under "Tools" for more info. - -### Start the Rails app - -* `foreman start` to start Rails and the Hotloader -* Navigate to `http://localhost:5000/` - -## Installing in an Existing Rails App - -If you have an existing Rails app, you can use the built in generator to install Hyperstack. Best to create a new branch of course before trying this out. - -* add `gem 'rails-hyperstack', "~> 1.0.alpha1.0"` to your gem file -* run `bundle install` -* run `bundle exec rails g hyperstack:install` - -> Note: if you want to use the unreleased edge branch your gem specification will be: -> -> ```ruby -> gem 'rails-hyperstack', -> git: 'git://github.com/hyperstack-org/hyperstack.git', -> branch: 'edge', -> glob: 'ruby/*/*.gemspec' -> ``` -> -> ### Start the Rails app - -* `bundle exec foreman start` to start Rails and the Hotloader -* Navigate to `http://localhost:5000/` - -> Note that the generator will add a wild card route to the beginning of your routes file. This will let you immediately test Hyperstack, but will also mean that all of your existing routes are now unreachable. So after getting Hyperstack up, you will want to adjust things to your needs. See the first in the **Manual Installation** section for more info. - -## Manual Installation +# Manual Installation To manually install a complete Hyperstack system there are quite a few steps. However these can be broken down into six separate activities each of which will leave your system in a working and testable state: @@ -107,6 +46,7 @@ and visit `localhost:3000/test` and you will see **TestApp** displayed. This command accomplishes four tasks which you can also manually perform if you prefer: 1. Insure that the `hyperstack-loader` is required in your `application.js` file; +2. Insure that the webpacker manifest file is loading JS files; 2. Insure that you have a `HyperComponent` base class defined; 3. Add a skeleton `TestApp` component and 4. Add a route to the `TestApp` component in your rails routes file. @@ -124,7 +64,15 @@ The `hyperstack-loader` is a dynamically generated asset manifest that will load //= require_tree . ``` -Once this is added to your `application.js` file you will see the hyperstack asset manifest on the Rails console and the browser's debug console which looks like this: +#### Check the webpacker manifest file + +In Rails 6 the webpacker default installation no longer links to the javascripts directory, but because Opal uses sprockets we need to add it back in. Check to see if you have a `app/assets/config/manifest.js` file, and if you do, insure it has the following line: + +```javascript +//= link_directory ../javascripts .js +``` + +Once hyperstack-loader and link_directory directives are added you can reload any page you will see the hyperstack asset manifest on the Rails console and the browser's debug console which looks like this: ```text require 'opal' @@ -153,7 +101,7 @@ require 'config/initializers/inflections.rb' All your Hyperstack code goes into the `app/hyperstack` directory, which is at the same level as `app/models`, `app/views`, `app/controllers`, etc. -Inside this directory are subdirectories for each of the different parts of you hyperstack application code. Components go in the `app/hyperstack/components` directory. +Inside this directory are subdirectories for each of the different parts of you Hyperstack application code. Components go in the `app/hyperstack/components` directory. Like Rails models, and Rails controllers, Hyperstack components by convention inherit from an application defined base class. So while a typical Rails model inherits from the `ApplicationRecord` class, your Hyperstack components will inherit from the `HyperComponent` class. @@ -242,7 +190,6 @@ will render the component named `TestApp` at that position in your view. You can also use `render_component` in a controller in place of the standard Rails render method. Like the `render_component` view helper you can pass the component parameters in from the controller. -There are quite a few steps, but each has a specific, and understandable purpose. ## Installing the Hotloader @@ -252,7 +199,7 @@ There are three steps to installing the Hotloader: 1. Importing Hotloader into your `hyperstack-loader` manifest; 2. Adding the `foreman` gem to your `Gemfile` and -3. Adding a `Procfile` to the root of our application directory. +3. Adding a `Procfile` to the root of our application directory.`` By default the Hotloader is **not** included in the hyperstack manifest so the first step is to add the `config/initializers/hyperstack.rb` initializer file, and _import_ the Hotloader: @@ -702,4 +649,3 @@ Another approach is to add a simple component using the component generator: and then mount this component using ERB from within an existing view: `<% render_component 'HyperTest' %>` - diff --git a/docs/development-workflow/hyper-i18n.md b/docs/internationalization/README.md similarity index 86% rename from docs/development-workflow/hyper-i18n.md rename to docs/internationalization/README.md index d84812508..da9d58c93 100644 --- a/docs/development-workflow/hyper-i18n.md +++ b/docs/internationalization/README.md @@ -1,11 +1,8 @@ -# Internationalization - -## HyperI18n - -**Work in progress - ALPHA \(docs and code\)** - + HyperI18n seamlessly brings Rails I18n into your Hyperstack application. +## This Page Under Construction + ## Installation and Setup **TODO these steps are wrong** @@ -14,10 +11,6 @@ HyperI18n seamlessly brings Rails I18n into your Hyperstack application. 2. Install the Gem: `bundle install` 3. Add `require 'hyper-i18n'` to your components manifest -## Note! - -This gem is in it's very early stages, and only a handful of the API has been implemented. Contributions are very welcome! - ### Usage Hyper-I18n brings in the standard ActiveSupport API. @@ -71,4 +64,3 @@ end ### Server Rendering HyperI18n is fully compatible with server rendering! All translations are also sent to the client, so as to bypass fetching/rendering again on the client. - diff --git a/docs/isomorphic-dsl/README.md b/docs/isomorphic-dsl/README.md deleted file mode 100644 index bb9c55c06..000000000 --- a/docs/isomorphic-dsl/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Isomorphic DSL - diff --git a/docs/isomorphic-dsl/hyper-operation.md b/docs/operations/README.md similarity index 99% rename from docs/isomorphic-dsl/hyper-operation.md rename to docs/operations/README.md index 1904d6af0..c215a7b82 100644 --- a/docs/isomorphic-dsl/hyper-operation.md +++ b/docs/operations/README.md @@ -1,8 +1,8 @@ -# Isomorphic Operations + +HyperOperations are HyperStack's implementation of Service Objects based on Trailblazer Operations. Operations can be used on the client, the server, or can act like a remote procedure call mechanism communicating between the client and the server. -**Work in progress - ALPHA \(docs and code\)** +## This Page Under Construction -Operations are Hyperstack's implementation of Service Object which is... > "A class that performs an action" [A simple explanation of Service Objects for Ruby on Rails](https://medium.freecodecamp.org/service-objects-explained-simply-for-ruby-on-rails-5-a8cc42a5441f) @@ -1060,4 +1060,3 @@ The design of Hyperstack's Operations have been inspired by three concepts: [Tra | Action Data | Hyperstack::Operation parameters | | Dispatcher | `Hyperstack::Operation#dispatch` method | | Registering a Store | `Store.receives` | - diff --git a/docs/isomorphic-dsl/hyper-policy.md b/docs/policies/README.md similarity index 97% rename from docs/isomorphic-dsl/hyper-policy.md rename to docs/policies/README.md index 649e63896..842031a88 100644 --- a/docs/isomorphic-dsl/hyper-policy.md +++ b/docs/policies/README.md @@ -1,8 +1,9 @@ -# Policies + +**Your data is protected by Hyperstack's policy mechanism. Policies regulate Create, Read/Broadcast, Update and Delete access based on the browsers logged in +user. You define in one set of files what data can be seen by who, and who can update the database** -**WORK IN PROGRESS DOCS** +## This Page Under Construction -## Authorization Access to your Isomorphic Models is controlled by _Policies_ that describe how the current _acting\_user_ and _channels_ may access your Models. @@ -291,8 +292,8 @@ Finally falsy values are ignored. You can also send `connect` directly to ActiveRecord models: ```ruby -AdminUser.connect! # same as Hyperloop.connect(AdminUser) -current_user.connect! # same as Hyperloop.connect(current_user) +AdminUser.connect # same as Hyperloop.connect(AdminUser) +current_user.connect # same as Hyperloop.connect(current_user) ``` #### Connection Sequence Summary @@ -591,4 +592,3 @@ end ``` BTW what if you want to restrict what data is broadcast? In Hyperloop you just update the regulation. In pundit you may have to edit both the index controller method and Policy class. - diff --git a/docs/rails-installation/README.md b/docs/rails-installation/README.md new file mode 100644 index 000000000..1866f8678 --- /dev/null +++ b/docs/rails-installation/README.md @@ -0,0 +1,9 @@ +## Rails Installation + +The easiest way to get the full benefit of Hyperstack is to integrate it with a Rails application. + +Adding Hyperstack to your existing Rails App is as simple as adding the gem and running the installer. + +Continue to the next section to make sure you have the necessary prerequisites on your machine. + +**[For more info on why we use Rails](why-rails.md)** diff --git a/docs/rails-installation/file-structure.md b/docs/rails-installation/file-structure.md new file mode 100644 index 000000000..29a2e6217 --- /dev/null +++ b/docs/rails-installation/file-structure.md @@ -0,0 +1,249 @@ +## Application File Structure + +Hyperstack adds the following files and directories to your Rails +application: + +``` +/app/hyperstack +/app/operations +/app/policies +/config/initializers/hyperstack.rb +/app/javascript/packs/client_and_server.js +/app/javascript/packs/client_only.js +``` + +In addition there are configuration settings in existing Rails files that are explained in the next section. Below we cover the purpose of each these files, and their contents. + +### The `/app/hyperstack/` Directory + +Here lives all your Hyperstack code that will run on the client. Some of the subdirectories are *isomorphic* meaning the code is shared between the client and the server, other directories are client only. + +Within the `hyperstack` directory there can be the following sub-directories: + ++ `components` *(client-only)* is where your components live. +> Following Rails conventions a component with a class of `Bar::None::FooManchu` should be in a file named `components/bar/none/foo_manchu.rb` + ++ `models` *(isomorphic)* is where ActiveRecord models are shared with the client. More on this below. + ++ `operations` *(isomorphic)* is where Hyperstack Operations will live. + ++ `shared` *(isomorphic)* is where you can put shared code that are not models or operations. + ++ Any other subdirectory (such as `libs` and `client-ops`) will be considered client-only. + +### Sharing Models and Operations + +Files in the `hyperstack` `/models` and `/operations` directories are loaded on the client *and* the server. So when you place a model's class definition in the `hyperstack/models` directory the class is available on the client. + +Assuming: +```Ruby +# app/hyperstack/models/todo.rb +class Todo < ApplicationRecord + ... +end +``` +Then +``` + Todo.count # will return the same value on the client and the server +``` + +> See the Policy section below for how access to the actual data is controlled. Remember a Model *describes* some data, but the actual data is stored in the database, and protected by Policies. + +Likewise Operations placed in the `/operations` directory can be run on the client or the server, or in the case of a `ServerOp` the operation can be invoked on the client, but will run on the server. + +Hyperstack sets things up so that Rails will first look in the `hyperstack` `/models` and `/operations` directories, and then in the server only `app/models` and `app/operations` directories. So if you don't want some model shared you can just leave it in the normal `app` directory. + +### Splitting Class Definitions + +There are cases where you would like split a class definition into its shared and server-only aspects. For example there may be code in a model that cannot be sensibly run on the client. Hyperstack augments the Rails dependency lookup mechanism so that when a file is found in a `hyperstack` directory we will *also* load any matching file in the normal `app` directory. + +This works because Ruby classes are *open*, so that you can define a class (or module) in multiple places. + +### Server Side Operations + +Operations are Hyperstack's way of providing *Service Objects*: classes that perform some operation not strictly belonging to a single model, and often involving other services such as remote APIs. *The idea of Operations comes from the [Trailblazer Framework.](https://trailblazer.to/2.0/gems/operation/2.0/index.html)* + +As such Operations can be useful strictly on the server side, and so can be added to the `app/operations` directory. + +Server side operations can also be remotely run from the client. Such operations are defined as subclasses of `Hyperstack::ServerOp`. + +The right way to define a `ServerOp` is to place its basic definition including its parameter signature in the `hyperstack/operations` directory, and then placing the rest of the operation's definition in the `app/operations` directory. + +### Policies + +Hyperstack uses Policies to define access rights to your models. Policies are placed in the `app/policies` directory. For example the policies for the `Todo` model would defined by the `TodoPolicy` class located at `app/policies/todo_policy.rb` Details on policies can be found [Policy section of this document.](https://docs.hyperstack.org/isomorphic-dsl/hyper-policy). + +### Example Directory Structure +``` +└── app/ + ├── models/ + │ └── user.rb # private section of User model + ├── operations/ + │ └── email_the_owner.rb # server code + ├── hyperstack/ + │ ├── components/ + │ │ ├── app.rb + │ │ ├── edit_todo.rb + │ │ ├── footer.rb + │ │ ├── header.rb + │ │ ├── show_todo.rb + │ │ └── todo_index.rb + │ ├── models/ + │ │ ├── application_record.rb # usually no need to split this + │ │ ├── todo.rb # note all of Todo definition is public + │ │ └── user.rb # user has a public and private section + │ └── operations/ + │ └── email_the_owner.rb # serverop interface only + └── policies/ + ├── todo_policy.rb + └── user_policy.rb + +``` + +These directories are where most of your work will be done during Hyperstack development. + +> #### What about Controllers and Views? +Hyperstack works alongside Rails controllers and views. In a clean-sheet Hyperstack app you never need to create a controller or a view. On the other hand if you have existing code or aspects of your project that you feel would work better using a traditional MVC approach everything will work fine. You can also merge the two worlds: Hyperstack includes two helpers that allow you to mount components either from a controller or from within a view. + +### The Hyperstack Initializer + +The Hyperstack configuration can be controlled via the `config/initializers/hyperstack.rb` initializer file. Using the installer will set up a reasonable set of of options, which you can tweak as needed. + +Here is a summary of the various configuration settings: + +```ruby +# config/initializers/hyperstack.rb + +# server_side_auto_require will patch the ActiveSupport Dependencies module +# so that you can define classes and modules with files in both the +# app/hyperstack/xxx and app/xxx directories. + +require "hyperstack/server_side_auto_require.rb" + +# By default the generators will generate new components as subclasses of +# HyperComponent. You can change this using the component_base_class setting. + +Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent' + +# prerendering is default :off, you should wait until your +# application is relatively well debugged before turning on. + +Hyperstack.prerendering = :off # or :on + +# The transport setting controls how push (websocket) communications are +# implemented. The default is :none, but will be set to :action_cable if you +# install hyper-model. + +# Other possibilities are :action_cable, :pusher (see www.pusher.com) +# or :simple_poller which is sometimes handy during system debug. + +Hyperstack.transport = :action_cable # :pusher, :simple_poller or :none + +# hotloader settings: +# sets the port hotloader will listen on. Note this must match the value used +# to start the hotloader typically in the foreman Procfile. +Hyperstack.hotloader_port = 25222 +# seconds between pings over the hotloader websocket. Normally not needed. +Hyperstack.hotloader_ping = nil +# hotloader will automatically reload callbacks when effected classes are +# reloaded. Not recommended to change this. +Hyperstack.hotloader_ignore_callback_mapping = false + +# Transport settings +# seconds before timeout when sending messages between the rails console and +# the server. +Hyperstack.send_to_server_timeout = 10 + +# Transport specific options +Hyperstack.opts, { + # pusher specific options + app_id: 'your pusher app id', + key: 'your pusher key', + secret: 'your pusher secret', + cluster: 'mt1', # pusher cluster defaults to mt1 + encrypted: true, # encrypt pusher comms, defaults to true + refresh_channels_every: 2.minutes, # how often to check which channels are alive + + # simple poller specific options + expire_polled_connection_in: 5.minutes, # when to kill simple poller connections + seconds_between_poll: 5.seconds, # how fast to poll when using simple poller + expire_new_connection_in: 10.seconds, # how long to keep initial sessions alive +} + +# Namespace used to keep hyperstack communication separate from other websockets +Hyperstack.channel_prefix = 'synchromesh' + +# If there a JS console available should websocket comms be logged? +Hyperstack.client_logging = true + +# Automatically create a (possibly temporary) websocket connection as each +# browser session starts. Usually this is needed for further authentication and +# should be left as true +Hyperstack.connect_session = true + +# Where to store the connection tables. Default is :active_record but you +# can also specify redis. If specifying redis the redis url defaults to +# redis://127.0.0.1:6379 +Hyperstack.connection = [adapter: :active_record] # or + # [adapter: :redis, redis_url: 'redis://127.0.0.1:6379] + +# The import directive loads optional portions of the various hyperstack gems. +# Here are the common imports typically included: + +Hyperstack.import 'hyperstack/hotloader', client_only: true if Rails.env.development? + +# and these are typically not imported: + +# React source is normally brought in through webpacker +# Hyperstack.import 'react/react-source-browser' + +# add this line if you need jQuery AND ARE NOT USING WEBPACK +# Hyperstack.import 'hyperstack/component/jquery', client_only: true + +# The following are less common settings which you should never have to change: +Hyperstack.prerendering_files = ['hyperstack-prerender-loader.js'] +Hyperstack.public_model_directories = ['app/hyperstack/models'] + + +# change definition of on_error to control how errors such as validation +# exceptions are reported on the server +module Hyperstack + def self.on_error(operation, err, params, formatted_error_message) + ::Rails.logger.debug( + "#{formatted_error_message}\n\n" + + Pastel.new.red( + 'To further investigate you may want to add a debugging '\ + 'breakpoint to the on_error method in config/initializers/hyperstack.rb' + ) + ) + end +end if Rails.env.development? +``` + +### Hyperstack Packs + +Rails `webpacker` organizes javascript into *packs*. Hyperstack will look for and load one of two packs depending on if you are prerendering or not. + +The default content of these packs are as follows: + +```javascript +//app/javascript/packs/client_and_server.js +// these packages will be loaded both during prerendering and on the client +React = require('react'); // react-js library +createReactClass = require('create-react-class'); // backwards compatibility with ECMA5 +History = require('history'); // react-router history library +ReactRouter = require('react-router'); // react-router js library +ReactRouterDOM = require('react-router-dom'); // react-router DOM interface +ReactRailsUJS = require('react_ujs'); // interface to react-rails +// to add additional NPM packages run `yarn add package-name@version` +// then add the require here. +``` + +```javascript +//app/javascript/packs/client_only.js +// add any requires for packages that will run client side only +ReactDOM = require('react-dom'); // react-js client side code +jQuery = require('jquery'); // remove if you don't need jQuery +// to add additional NPM packages call run yarn add package-name@version +// then add the require here. +``` diff --git a/docs/rails-installation/generators.md b/docs/rails-installation/generators.md new file mode 100644 index 000000000..04f469d2a --- /dev/null +++ b/docs/rails-installation/generators.md @@ -0,0 +1,98 @@ +## Hyperstack Generators + +As well as the installer Hyperstack includes two generators to create +basic component skeletons. + +### Summary: + +``` +bundle exec rails g hyper:component ComponentName # add a new component +bundle exec rails g hyper:router RouterName # add a new router component +``` + +both support the following flags: + ++ `--no-help` don't add extra comments and method examples ++ `--add-route=...` add a route to this component to the Rails routes file ++ `--base-class=...` change the base class name from the default + +### The Component Generator + +To add a new component skeleton use the `hyper:component` generator: + +``` +bundle exec rails g hyper:component ComponentName +``` + +#### File directories and Name Spacing Components + +The above will create a new class definition for `MyComponent` in a file named `my_component.rb` and place it in +the `app/hyperstack/components/` directory. The component may be name spaced and +will be placed in the appropriate subdirectory. I.e. `Foo::BarSki` will generate +`app/hyperstack/components/foo/bar_ski.rb` + +#### The `--no-help` flag + +By default the skeleton will be verbose and contain examples of the most often used +class methods which you can keep or delete as needed. You can generate a minimal +component with the `--no-help` flag. + +### Router Generator + +Typically your top level component will be a *Router* which will take care of dispatching to specific components as the URL changes. This provides the essence of a *Single Page App* where as the user moves between parts of +the application the URL is updated, the *back* and *forward* buttons work, but the page is **not** reloaded from the server. + +A component becomes a router by including the `Hyperstack::Router` module +which provides a number of methods that will be used in the router +component. + +To generate a new router skeleton use the `hyper:router` generator: + +``` +bundle exec rails g hyper:router App +``` + +> Note that in any Single Page App there will be two routers in play. +On the server the router is responsible dispatching each incoming HTTP request to a +controller. The controller will deliver back (usually using a view) the contents of the request. +> +> In addition on a Single Page App you will have a router running on the client, which will dispatch to different components depending on the current value of the URL. The server is only contacted if the current URL leaves the set of URLs that client router knows how to deal with. + +#### Adding a Route to the Rails `routes.rb` File + +When you generate a new component you can use the `--add-route` option to add the route for you. For example: + +``` +bundle exec rails g hyper:router MainApp \ + --add-route="/(*others)" +``` +will add + +```ruby + get '/(*others)', to: 'hyperstack#main_app' +``` +to the Rails `routes.rb` file, which will direct all URLS to the `MainApp` component. + +For details see **[Routing and Mounting Components.](routing-and-mounting-components.md)** + +#### Specifying the Base Class + +By default components will inherit from the `HyperComponent` base class. + +You can globally override this by changing the value `Hyperstack.component_base_class` in the `hyperstack.rb` initializer. + +For example some teams prefer `ApplicationComponent` as their base class name. + +You can also override the base class name when generating a component using the `--base-class` option. + +This is useful when you have a common library subclass that other classes will inherit from. For example: + +``` +bundle exec rails g hyper:component UserBio --base-class=TextArea +``` +will generate +``` +class UserBio < TextArea +... +end +``` diff --git a/docs/rails-installation/other-details.md b/docs/rails-installation/other-details.md new file mode 100644 index 000000000..95b82c1e2 --- /dev/null +++ b/docs/rails-installation/other-details.md @@ -0,0 +1,114 @@ +## Other Rails Configuration Details + +Hyperstack internally sets a number of Rails configurations as outlined below. + +>These are all setup +automatically by the hyperstack generators and installers. They are documented here for advanced configuration or in the sad chance that something gets broken during your setup. Please report any issues with setup, or if you feel you have to manually tweak things. + +#### Require the `hyperstack-loader` + +The `app/assets/javascripts/application.js` file needs to require the hyperstack-loader. + +```javascript +//= require hyperstack-loader // add as the last require directive +``` + +The loader handles bringing in client side code, getting it compiled (using sprockets) and adding it to the webpacks (if using webpacker.) + +> Note that now that Rails is using webpacker by default you may have to create +this file, and the single line above. If so be sure to checkout your layout +file, as the javascript_include_tag will also be missing there. + +#### `app/assets/config/manifest.js` + +If you are using webpacker this file must exist and contain the following line: + +```Ruby +//= link_directory ../javascripts .js +``` + +This line insures that the any javascript in the assets directory are included in the webpacks. In older versions of Rails, this line will already be there, and if not +using webpacker its actually not necessary (but doesn't hurt anything.) + +#### The application layout + +If using a recent version of rails with webpacker you may find that the application.html.erb file no longer loads the application.js file. Make sure that your layout file has this line: + +```html + <%= javascript_include_tag 'application' %> +``` + +#### Required NPM modules + +If using Webpacker Hyperstack needs the following NPM modules: + +``` +yarn 'react', '16' +yarn 'react-dom', '16' +yarn 'react-router', '^5.0.0' +yarn 'react-router-dom', '^5.0.0' +yarn 'react_ujs', '^2.5.0' +yarn 'jquery', '^3.4.1' # this is only needed if using jquery +yarn 'create-react-class' +``` + +#### Routing + +If using hyper-model you need to mount the Hyperstack engine in the routes file like this: + +```ruby +# config/routes.rb +Rails.application.routes.draw do + # this route should be first in the routes file so it always matches' + mount Hyperstack::Engine => '/hyperstack' # you can use any path you choose + ... +``` + +To directly route from a URL to a component you can use the builtin Hyperstack +controller with a route like this: + +```ruby + get "hyperstack-page/(*others)", "hyperstack#comp_name" +``` + +Where `comp_name` is the underscored name of the component you want to mount. I.e. `MyComp` becomes `my_comp`. The `/(*others)` indicates that all routes beginning with +`hyperstack-page/` will be matched, if that is your desired behavior. + +> Note that the engine mount point can be any string you wish but the controller routed to above is always `hyperstack`. + +#### Other Rails Configuration Settings + +Hyperstack will by default set a number of Rails configuration settings. To disable this +set +```ruby + config.hyperstack.auto_config = false +``` +In your Rails application.rb configuration file. + +Otherwise the following settings are automatically applied in test and staging: + +```ruby +# This will prevent any data transmitted by HyperOperation from appearing in logs +config.filter_parameters << :hyperstack_secured_json + + # Add the hyperstack directories + config.eager_load_paths += %W(#{config.root}/app/hyperstack/models) + config.eager_load_paths += %W(#{config.root}/app/hyperstack/models/concerns) + config.eager_load_paths += %W(#{config.root}/app/hyperstack/operations) + config.eager_load_paths += %W(#{config.root}/app/hyperstack/shared) + + # But remove the outer hyperstack directory so rails doesn't try to load its + # contents directly + delete_first config.eager_load_paths, "#{config.root}/app/hyperstack" +``` +but in production we autoload instead of eager load. +```ruby + # add the hyperstack directories to the auto load paths + config.autoload_paths += %W(#{config.root}/app/hyperstack/models) + config.autoload_paths += %W(#{config.root}/app/hyperstack/models/concerns) + config.autoload_paths += %W(#{config.root}/app/hyperstack/operations) + config.autoload_paths += %W(#{config.root}/app/hyperstack/shared) + + # except for the outer hyperstack directory + delete_first config.autoload_paths, "#{config.root}/app/hyperstack" +``` diff --git a/docs/rails-installation/prerequisites.md b/docs/rails-installation/prerequisites.md new file mode 100644 index 000000000..bfb04d6d8 --- /dev/null +++ b/docs/rails-installation/prerequisites.md @@ -0,0 +1,36 @@ +## Prerequisites + +#### Rails + +Hyperstack is currently tested on Rails ~> 5.0 and ~> 6.0. +>If you are on Rails 4.0 it might be time to upgrade, but that said you probably can manually install Hyperstack on Rails 4.0 and get it working. + +[Rails Install Instructions](http://railsinstaller.org/en) + +#### Yarn + +For a full system install including webpacker for managing javascript assets you will +need yarn. To skip adding webpacker use `hyperstack:install:skip-webpack` when installing Hyperstack. + +[Yarn Install Instructions](https://yarnpkg.com/en/docs/install#mac-stable) + +#### Database + +To fully utilize Hyperstack's capabilities you will be need an SQL database that has an ActiveRecord adapter. If you have a choice we have found Postgresql works best (and it also deploys to Heroku without issue.) If you are new to Rails, then the default Sqlite database (which rails will install) will work fine. +> Why don't we support NoSql databases? The biggest reasons are security and effeciency. Hyperstack access-policies are based on known table names and attributes and after-commit hooks. Keep in mind that modern DBs support the json and jsonb attribute types allowing you to add arbitrary json based data to your database. + +### Creating a New Rails App + +If you don't have an existing Rails app you can create a new Rails app +with the following command line: + +``` +rails new NameOfYourApp -T +``` + +To avoid much pain do not name your app `Application` as this will conflict with all sorts of +things in Rails and Hyperstack. + +Once you have created the app cd into the newly created directory. + +> The -T option will skip adding minitest directories as Hyperstack prefers RSpec. However if you have an existing app with minitest that is okay too. diff --git a/docs/rails-installation/routing-and-mounting-components.md b/docs/rails-installation/routing-and-mounting-components.md new file mode 100644 index 000000000..fc8969420 --- /dev/null +++ b/docs/rails-installation/routing-and-mounting-components.md @@ -0,0 +1,86 @@ +## Routing and Mounting Components + +Within a Rails Application there are three ways to render or *mount* a +component on a page: + ++ Route directly to the component from the rails `routes.rb` file. ++ Render a component directly from a controller. ++ Render a component from within a layout or view file. + +### Routing Directly to Components + +Components can be directly mounted from the Rails `routes.rb` file, using the builtin Hyperstack controller. + +For example a Rails `routes.rb` file containing + +```ruby + get 'some_page/(*others)', to: 'hyperstack#some_component' +``` + +will route all urls beginning with `some_page` to `SomeComponent`. + +When you generate a new component you can use the `--add-route` option to add the route for you (see the previous section.) + +Note that typically the Rails route will be going to a Router Component. That is why we typically add the wild card to the Rails route so that all urls beginning with `some_page/` will all be handled by `SomeComponent` without having to reload the page. + +Also note that for the purposes of the example we used rather dubious names, a more logical setup would be: + +```ruby + get `/(*others)`, to 'hyperstack#app' +``` + +Which you could generate with +``` +bundle exec rails g hyper:router App --add-route="/(*others)" +``` + +You could also divide your application into several single page apps, for example + +```ruby +... + get 'admin/(*others)', to: 'hyperstack#admin' + get '/(*others)', to: 'hyperstack#app' +... +``` + +would route all URLS beginning with admin to the `Admin` component, and everything else +to the main `App` component. *Note that order of the routes is important as Rails will +dispatch to the first route it matches.* + +> If the component is named spaced separate each module with a double underscore (`__`) and +leave the module names CamelCase: +```ruby + get 'admin/(*others)', to: 'hyperstack#Admin__App' +``` + +### Rendering a Component from a Controller + +To render a component from a controller use the `render_component` helper: + +```ruby + render_component 'Admin', {user_id: params[:id]}, layout: 'admin' + # would pass the user_id to the Admin component and use the admin layout + + # in general: + render_component 'The::Component::Name' + { ... component params ... }, + { other render params such as layout } +``` + +Only the component name is required, but note that if you want to have other +render params, you will have to supply at least an empty hash for the component +params. + +### Rendering (or Mounting) a Component from a View + +To mount a component directly in a view use the mount_component view helper: + +```html + <%= mount_component 'Clock' %> +``` + +Like render_component may take params which will be passed to the mounted component. + +Mounting a component in an existing view, is a very useful way to integrate Hyperstack +into existing applications. You mount a component to serve a specific function such as +a dynamic footer or a tweeter feed onto an existing view without having to do a major redesign. diff --git a/docs/rails-installation/using-the-installer.md b/docs/rails-installation/using-the-installer.md new file mode 100644 index 000000000..7116908f3 --- /dev/null +++ b/docs/rails-installation/using-the-installer.md @@ -0,0 +1,51 @@ +## Installing HyperStack + +In the directory of your existing or newly created Rails app: + +* add `gem 'rails-hyperstack', "~> 1.0.alpha1.0"` to your `Gemfile` +* run `bundle install` +* run `bundle exec rails hyperstack:install` + +> Note: if you want to use the unreleased edge branch your gem specification will be: +> +> ```ruby +> gem 'rails-hyperstack', +> git: 'git://github.com/hyperstack-org/hyperstack.git', +> branch: 'edge', +> glob: 'ruby/*/*.gemspec' +> ``` +> **HOWEVER currently we are doing weekly alpha updates, you should not need to do this.** + +### Start the Rails app + +* `bundle exec foreman start` to start Rails and the Hotloader +* Navigate to `http://localhost:5000/` + +You will see an empty page with the word "App" displayed. + +Open your editor and find the file `/app/hyperstack/components/app.rb` + +Change the `'App'` to `'Hello World'` and save the file. + +You should see the page on the browser change to "Hello World" + +You are in business! + +> If this does not work, please contact us on *[slack](https://hyperstack.org/slack)*, or **[create an issue on github.](https://github.com/hyperstack-org/hyperstack/issues/new)** + +### Installer Options + +You can control what gets installed with the following options: + +``` +bundle exec rails hyperstack:install:webpack # just add webpack +bundle exec rails hyperstack:install:skip-webpack # all but webpack +bundle exec rails hyperstack:install:hyper-model # just add hyper-model +bundle exec rails hyperstack:install:skip-hyper-model # all but hyper-model +bundle exec rails hyperstack:install:hotloader # just add the hotloader +bundle exec rails hyperstack:install:skip-hotloader # skip the hotloader +``` + +> Note that the `:webpack` and `:skip-webpack` options control whether the installer will +add the webpacker Gem. If webpacker is already installed in the Gemfile then the +installer will always integrate with webpacker. diff --git a/docs/rails-installation/why-rails.md b/docs/rails-installation/why-rails.md new file mode 100644 index 000000000..6d3f11135 --- /dev/null +++ b/docs/rails-installation/why-rails.md @@ -0,0 +1,11 @@ +### Why Rails? +Rails provides a robust, tried and true tool chain that takes care of much of the day to day details of building your app. Hyperstack builds on the Rails philosophy of convention over configuration, meaning that there is almost no boiler plate code in your Rails-Hyperstack application. Almost every line that you write for your Hyperstack application will deal with the application requirements. We have seen real reductions of up to 400% in the lines of code needed to deliver high quality functionality. + +People sometimes balk at Rails because when they see the huge number of files and directories generated by the Rails installer, it looks crazy, complex, and ineffecient. Keep in mind that this has very little if any impact on your application's performance, and when developing code 90% of your time will be spent in the following directories: `app/models` and `app/hyperstack`. The rest of the files are there to hold configuration files, and seldom used content, so they have a place out of the way of your main development activities. + +Developers often believe that Rails modules like ActionController and ActiveRecord while powerful are slow. +In the case of Hyperstack this is largely irrelevant since one of our goals is to offload as much work to the client as possible. For example rather than have a multitude of controllers delivering different page views and updates, your client side Hyperstack code is now responsible for that. The role of the server becomes the central database, and the place where secure operations are executed (such as sending mail, authenticating users etc.) + +### How about other Rack Frameworks? + +But still you may have specific needs for a lighter weight system, or have an existing Sinatra app (for example) that you would like to use with Hyperstack. For now we will say it's in the plan, and it's just a matter of time. If you are interested leave a comment on this issue: https://github.com/hyperstack-org/hyperstack/issues/340 diff --git a/docs/specs/.browserslistrc b/docs/specs/.browserslistrc new file mode 100644 index 000000000..e94f8140c --- /dev/null +++ b/docs/specs/.browserslistrc @@ -0,0 +1 @@ +defaults diff --git a/docs/specs/.gitignore b/docs/specs/.gitignore new file mode 100644 index 000000000..aea2bd9c2 --- /dev/null +++ b/docs/specs/.gitignore @@ -0,0 +1,38 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore uploaded files in development +/storage/* +!/storage/.keep + +/node_modules +/yarn-error.log + +/public/assets +.byebug_history + +# Ignore master key for decrypting credentials and more. +/config/master.key + +/public/packs +/public/packs-test +/node_modules +/yarn-error.log +yarn-debug.log* +.yarn-integrity diff --git a/docs/specs/.ruby-version b/docs/specs/.ruby-version new file mode 100644 index 000000000..fbafd6b60 --- /dev/null +++ b/docs/specs/.ruby-version @@ -0,0 +1 @@ +2.7.2 \ No newline at end of file diff --git a/docs/specs/Gemfile b/docs/specs/Gemfile new file mode 100644 index 000000000..9a3ef799a --- /dev/null +++ b/docs/specs/Gemfile @@ -0,0 +1,75 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '2.7.2' + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.2.4', '>= 5.2.4.5' +# Use sqlite3 as the database for Active Record +gem 'sqlite3' +# Use Puma as the app server +gem 'puma', '~> 3.11' +# Use SCSS for stylesheets +gem 'sass-rails', '~> 5.0' +# Use Uglifier as compressor for JavaScript assets +gem 'uglifier', '>= 1.3.0' +# See https://github.com/rails/execjs#readme for more supported runtimes +# gem 'mini_racer', platforms: :ruby + +# Use CoffeeScript for .coffee assets and views +gem 'coffee-rails', '~> 4.2' +# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks +gem 'turbolinks', '~> 5' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 2.5' +# Use Redis adapter to run Action Cable in production +# gem 'redis', '~> 4.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use ActiveStorage variant +# gem 'mini_magick', '~> 4.8' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +# Reduces boot times through caching; required in config/boot.rb +gem 'bootsnap', '>= 1.1.0', require: false + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem 'rspec-rails' +end + +group :development do + # Access an interactive console on exception pages or by calling 'console' anywhere in the code. + gem 'web-console', '>= 3.3.0' + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' +end + + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem 'rails-hyperstack', path: '../../ruby/rails-hyperstack' +gem 'hyperstack-config', path: '../../ruby/hyperstack-config' +gem 'hyper-state', path: '../../ruby/hyper-state' +gem 'hyper-component', path: '../../ruby/hyper-component' +gem 'hyper-operation', path: '../../ruby/hyper-operation' +gem 'hyper-model', path: '../../ruby/hyper-model' +gem 'hyper-router', path: '../../ruby/hyper-router' +gem 'hyper-spec', path: '../../ruby/hyper-spec' + +gem 'pry' +gem "opal", "1.0.5" + +gem "opal-jquery" + +group :development do + gem 'foreman' +end + +gem 'webpacker' diff --git a/docs/specs/Gemfile.lock b/docs/specs/Gemfile.lock new file mode 100644 index 000000000..106146aea --- /dev/null +++ b/docs/specs/Gemfile.lock @@ -0,0 +1,443 @@ +PATH + remote: ../../ruby/hyper-component + specs: + hyper-component (1.0.alpha1.7) + hyper-state (= 1.0.alpha1.7) + hyperstack-config (= 1.0.alpha1.7) + opal-activesupport (~> 0.3.1) + react-rails (>= 2.4.0, < 2.5.0) + +PATH + remote: ../../ruby/hyper-model + specs: + hyper-model (1.0.alpha1.7) + activemodel + activerecord (>= 4.0.0) + hyper-operation (= 1.0.alpha1.7) + +PATH + remote: ../../ruby/hyper-operation + specs: + hyper-operation (1.0.alpha1.7) + activerecord (>= 4.0.0) + hyper-component (= 1.0.alpha1.7) + mutations + opal-activesupport (~> 0.3.1) + tty-table + +PATH + remote: ../../ruby/hyper-router + specs: + hyper-router (1.0.alpha1.7) + hyper-component (= 1.0.alpha1.7) + hyper-state (= 1.0.alpha1.7) + opal-browser (~> 0.2.0) + +PATH + remote: ../../ruby/hyper-spec + specs: + hyper-spec (1.0.alpha1.7) + actionview + capybara + chromedriver-helper (= 1.2.0) + filecache + method_source + opal (>= 0.11.0, < 2.0) + parser + rspec + selenium-webdriver + timecop (~> 0.8.1) + uglifier + unparser (>= 0.4.2) + webdrivers + +PATH + remote: ../../ruby/hyper-state + specs: + hyper-state (1.0.alpha1.7) + hyperstack-config (= 1.0.alpha1.7) + +PATH + remote: ../../ruby/hyperstack-config + specs: + hyperstack-config (1.0.alpha1.7) + listen (~> 3.0) + opal (>= 0.11.0, < 2.0) + opal-browser (~> 0.2.0) + uglifier + websocket + +PATH + remote: ../../ruby/rails-hyperstack + specs: + rails-hyperstack (1.0.alpha1.7) + hyper-model (= 1.0.alpha1.7) + hyper-router (= 1.0.alpha1.7) + hyperstack-config (= 1.0.alpha1.7) + opal-browser (~> 0.2.0) + opal-rails + rails (>= 5.0.0, < 7.0) + react-rails (>= 2.4.0, < 2.5.0) + +GEM + remote: https://rubygems.org/ + specs: + abstract_type (0.0.7) + actioncable (5.2.4.5) + actionpack (= 5.2.4.5) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.4.5) + actionpack (= 5.2.4.5) + actionview (= 5.2.4.5) + activejob (= 5.2.4.5) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.4.5) + actionview (= 5.2.4.5) + activesupport (= 5.2.4.5) + rack (~> 2.0, >= 2.0.8) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.4.5) + activesupport (= 5.2.4.5) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.4.5) + activesupport (= 5.2.4.5) + globalid (>= 0.3.6) + activemodel (5.2.4.5) + activesupport (= 5.2.4.5) + activerecord (5.2.4.5) + activemodel (= 5.2.4.5) + activesupport (= 5.2.4.5) + arel (>= 9.0) + activestorage (5.2.4.5) + actionpack (= 5.2.4.5) + activerecord (= 5.2.4.5) + marcel (~> 0.3.1) + activesupport (5.2.4.5) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + adamantium (0.2.0) + ice_nine (~> 0.11.0) + memoizable (~> 0.4.0) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + anima (0.3.2) + abstract_type (~> 0.0.7) + adamantium (~> 0.2) + equalizer (~> 0.0.11) + archive-zip (0.12.0) + io-like (~> 0.3.0) + arel (9.0.0) + ast (2.4.2) + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.0) + bindex (0.8.1) + bootsnap (1.7.2) + msgpack (~> 1.0) + builder (3.2.4) + byebug (11.1.3) + capybara (3.35.3) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + childprocess (3.0.0) + chromedriver-helper (1.2.0) + archive-zip (~> 0.10) + nokogiri (~> 1.8) + coderay (1.1.3) + coffee-rails (4.2.2) + coffee-script (>= 2.2.0) + railties (>= 4.0.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + concord (0.1.6) + adamantium (~> 0.2.0) + equalizer (~> 0.0.9) + concurrent-ruby (1.1.8) + connection_pool (2.2.3) + crass (1.0.6) + diff-lcs (1.4.4) + equalizer (0.0.11) + erubi (1.10.0) + execjs (2.7.0) + ffi (1.15.0) + filecache (1.0.2) + foreman (0.87.2) + globalid (0.4.2) + activesupport (>= 4.2.0) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + ice_nine (0.11.2) + io-like (0.3.1) + jbuilder (2.11.2) + activesupport (>= 5.0.0) + jquery-rails (4.4.0) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.9.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + memoizable (0.4.2) + thread_safe (~> 0.3, >= 0.3.1) + method_source (1.0.0) + mimemagic (0.3.5) + mini_mime (1.0.2) + mini_portile2 (2.5.0) + minitest (5.14.4) + mprelude (0.1.0) + abstract_type (~> 0.0.7) + adamantium (~> 0.2.0) + concord (~> 0.1.5) + equalizer (~> 0.0.9) + ice_nine (~> 0.11.1) + procto (~> 0.0.2) + msgpack (1.4.2) + mutations (0.9.1) + activesupport + nio4r (2.5.7) + nokogiri (1.11.2) + mini_portile2 (~> 2.5.0) + racc (~> 1.4) + opal (1.0.5) + ast (>= 2.3.0) + parser (~> 2.6) + opal-activesupport (0.3.3) + opal (>= 0.5.0, < 2) + opal-browser (0.2.0) + opal + paggio + opal-jquery (0.4.4) + opal (>= 0.10.0, < 1.1) + opal-rails (1.1.2) + jquery-rails + opal (~> 1.0.0) + opal-activesupport (>= 0.0.5) + opal-jquery (~> 0.4.4) + opal-sprockets (~> 0.4.6) + rails (>= 5.1, < 6.1) + sprockets-rails (>= 2.3.3, < 4.0) + opal-sprockets (0.4.9.1.0.3.7) + opal (~> 1.0.0) + sprockets (~> 3.7) + tilt (>= 1.4) + paggio (0.2.6) + parser (2.7.2.0) + ast (~> 2.4.1) + pastel (0.8.0) + tty-color (~> 0.5) + procto (0.0.3) + pry (0.14.0) + coderay (~> 1.1) + method_source (~> 1.0) + public_suffix (4.0.6) + puma (3.12.6) + racc (1.5.2) + rack (2.2.3) + rack-proxy (0.6.5) + rack + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.4.5) + actioncable (= 5.2.4.5) + actionmailer (= 5.2.4.5) + actionpack (= 5.2.4.5) + actionview (= 5.2.4.5) + activejob (= 5.2.4.5) + activemodel (= 5.2.4.5) + activerecord (= 5.2.4.5) + activestorage (= 5.2.4.5) + activesupport (= 5.2.4.5) + bundler (>= 1.3.0) + railties (= 5.2.4.5) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (5.2.4.5) + actionpack (= 5.2.4.5) + activesupport (= 5.2.4.5) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rake (13.0.3) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) + react-rails (2.4.7) + babel-transpiler (>= 0.7.0) + connection_pool + execjs + railties (>= 3.2) + tilt + regexp_parser (2.1.1) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-rails (5.0.1) + actionpack (>= 5.2) + activesupport (>= 5.2) + railties (>= 5.2) + rspec-core (~> 3.10) + rspec-expectations (~> 3.10) + rspec-mocks (~> 3.10) + rspec-support (~> 3.10) + rspec-support (3.10.2) + ruby_dep (1.5.0) + rubyzip (2.3.0) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.1.0) + railties (>= 5.2.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + selenium-webdriver (3.142.7) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) + semantic_range (3.0.0) + spring (2.1.1) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.2) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.4.2) + strings (0.2.1) + strings-ansi (~> 0.2) + unicode-display_width (>= 1.5, < 3.0) + unicode_utils (~> 1.4) + strings-ansi (0.2.0) + thor (1.1.0) + thread_safe (0.3.6) + tilt (2.0.10) + timecop (0.8.1) + tty-color (0.6.0) + tty-screen (0.8.1) + tty-table (0.12.0) + pastel (~> 0.8) + strings (~> 0.2.0) + tty-screen (~> 0.8) + turbolinks (5.2.1) + turbolinks-source (~> 5.2) + turbolinks-source (5.2.0) + tzinfo (1.2.9) + thread_safe (~> 0.1) + uglifier (4.2.0) + execjs (>= 0.3.0, < 3) + unicode-display_width (2.0.0) + unicode_utils (1.4.0) + unparser (0.5.5) + abstract_type (~> 0.0.7) + adamantium (~> 0.2.0) + anima (~> 0.3.1) + concord (~> 0.1.5) + diff-lcs (~> 1.3) + equalizer (~> 0.0.9) + mprelude (~> 0.1.0) + parser (>= 2.6.5) + procto (~> 0.0.2) + web-console (3.7.0) + actionview (>= 5.0) + activemodel (>= 5.0) + bindex (>= 0.4.0) + railties (>= 5.0) + webdrivers (4.6.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (>= 3.0, < 4.0) + webpacker (5.2.1) + activesupport (>= 5.2) + rack-proxy (>= 0.6.1) + railties (>= 5.2) + semantic_range (>= 2.3.0) + websocket (1.2.9) + websocket-driver (0.7.3) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + +PLATFORMS + ruby + +DEPENDENCIES + bootsnap (>= 1.1.0) + byebug + coffee-rails (~> 4.2) + foreman + hyper-component! + hyper-model! + hyper-operation! + hyper-router! + hyper-spec! + hyper-state! + hyperstack-config! + jbuilder (~> 2.5) + listen (>= 3.0.5, < 3.2) + opal (= 1.0.5) + opal-jquery + pry + puma (~> 3.11) + rails (~> 5.2.4, >= 5.2.4.5) + rails-hyperstack! + rspec-rails + sass-rails (~> 5.0) + spring + spring-watcher-listen (~> 2.0.0) + sqlite3 + turbolinks (~> 5) + tzinfo-data + uglifier (>= 1.3.0) + web-console (>= 3.3.0) + webpacker + +RUBY VERSION + ruby 2.7.2p137 + +BUNDLED WITH + 2.1.4 diff --git a/docs/specs/Gemfile.orig b/docs/specs/Gemfile.orig new file mode 100644 index 000000000..55c5e8fdb --- /dev/null +++ b/docs/specs/Gemfile.orig @@ -0,0 +1,72 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '2.7.2' + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.2.4', '>= 5.2.4.5' +# Use sqlite3 as the database for Active Record +gem 'sqlite3' +# Use Puma as the app server +gem 'puma', '~> 3.11' +# Use SCSS for stylesheets +gem 'sass-rails', '~> 5.0' +# Use Uglifier as compressor for JavaScript assets +gem 'uglifier', '>= 1.3.0' +# See https://github.com/rails/execjs#readme for more supported runtimes +# gem 'mini_racer', platforms: :ruby + +# Use CoffeeScript for .coffee assets and views +gem 'coffee-rails', '~> 4.2' +# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks +gem 'turbolinks', '~> 5' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 2.5' +# Use Redis adapter to run Action Cable in production +# gem 'redis', '~> 4.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use ActiveStorage variant +# gem 'mini_magick', '~> 4.8' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +# Reduces boot times through caching; required in config/boot.rb +gem 'bootsnap', '>= 1.1.0', require: false + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] +end + +group :development do + # Access an interactive console on exception pages or by calling 'console' anywhere in the code. + gem 'web-console', '>= 3.3.0' + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' +end + + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem 'rails-hyperstack', path: '../..' +gem 'hyperstack-config', path: '../../../hyperstack-config' +gem 'hyper-state', path: '../../../hyper-state' +gem 'hyper-component', path: '../../../hyper-component' +gem 'hyper-operation', path: '../../../hyper-operation' +gem 'hyper-model', path: '../../../hyper-model' +gem 'hyper-router', path: '../../../hyper-router' +gem 'hyper-spec', path: '../../../hyper-spec' + +gem 'pry' +gem "opal", "1.0.5" + +group :development do + gem 'foreman' +end + +gem 'webpacker' \ No newline at end of file diff --git a/docs/specs/Procfile b/docs/specs/Procfile new file mode 100644 index 000000000..15e9f0c92 --- /dev/null +++ b/docs/specs/Procfile @@ -0,0 +1,2 @@ +web: bundle exec rails s -b 0.0.0.0 +hot-loader: bundle exec hyperstack-hotloader -p 25222 -d app/hyperstack diff --git a/docs/specs/README.md b/docs/specs/README.md new file mode 100644 index 000000000..7db80e4ca --- /dev/null +++ b/docs/specs/README.md @@ -0,0 +1,24 @@ +# README + +This README would normally document whatever steps are necessary to get the +application up and running. + +Things you may want to cover: + +* Ruby version + +* System dependencies + +* Configuration + +* Database creation + +* Database initialization + +* How to run the test suite + +* Services (job queues, cache servers, search engines, etc.) + +* Deployment instructions + +* ... diff --git a/docs/specs/Rakefile b/docs/specs/Rakefile new file mode 100644 index 000000000..e85f91391 --- /dev/null +++ b/docs/specs/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/docs/specs/app/assets/config/manifest.js b/docs/specs/app/assets/config/manifest.js new file mode 100644 index 000000000..b16e53d6d --- /dev/null +++ b/docs/specs/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/docs/specs/app/assets/images/.keep b/docs/specs/app/assets/images/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/app/assets/javascripts/application.js b/docs/specs/app/assets/javascripts/application.js new file mode 100644 index 000000000..e4f578461 --- /dev/null +++ b/docs/specs/app/assets/javascripts/application.js @@ -0,0 +1,17 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's +// vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. JavaScript code in this file should be added after the last require_* statement. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require rails-ujs +//= require activestorage +//= require turbolinks +//= require hyperstack-loader +//= require_tree . diff --git a/docs/specs/app/assets/javascripts/cable.js b/docs/specs/app/assets/javascripts/cable.js new file mode 100644 index 000000000..739aa5f02 --- /dev/null +++ b/docs/specs/app/assets/javascripts/cable.js @@ -0,0 +1,13 @@ +// 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. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/docs/specs/app/assets/javascripts/channels/.keep b/docs/specs/app/assets/javascripts/channels/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/app/assets/stylesheets/application.css b/docs/specs/app/assets/stylesheets/application.css new file mode 100644 index 000000000..d05ea0f51 --- /dev/null +++ b/docs/specs/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, 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/docs/specs/app/channels/application_cable/channel.rb b/docs/specs/app/channels/application_cable/channel.rb new file mode 100644 index 000000000..d67269728 --- /dev/null +++ b/docs/specs/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/docs/specs/app/channels/application_cable/connection.rb b/docs/specs/app/channels/application_cable/connection.rb new file mode 100644 index 000000000..0ff5442f4 --- /dev/null +++ b/docs/specs/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/docs/specs/app/controllers/application_controller.rb b/docs/specs/app/controllers/application_controller.rb new file mode 100644 index 000000000..09705d12a --- /dev/null +++ b/docs/specs/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/docs/specs/app/controllers/concerns/.keep b/docs/specs/app/controllers/concerns/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/app/helpers/application_helper.rb b/docs/specs/app/helpers/application_helper.rb new file mode 100644 index 000000000..de6be7945 --- /dev/null +++ b/docs/specs/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/docs/specs/app/hyperstack/components/hyper_component.rb b/docs/specs/app/hyperstack/components/hyper_component.rb new file mode 100644 index 000000000..f4abbaab2 --- /dev/null +++ b/docs/specs/app/hyperstack/components/hyper_component.rb @@ -0,0 +1,13 @@ +# app/hyperstack/hyper_component.rb + +require 'hyperstack/component/jquery' + +class HyperComponent + # All component classes must include Hyperstack::Component + include Hyperstack::Component + # The Observable module adds state handling + include Hyperstack::State::Observable + # The following turns on the new style param accessor + # i.e. param :foo is accessed by the foo method + param_accessor_style :accessors +end diff --git a/docs/specs/app/hyperstack/models/application_record.rb b/docs/specs/app/hyperstack/models/application_record.rb new file mode 100644 index 000000000..0f9516adc --- /dev/null +++ b/docs/specs/app/hyperstack/models/application_record.rb @@ -0,0 +1,6 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + # allow remote access to all scopes - i.e. you can count or get a list of ids + # for any scope or relationship + ApplicationRecord.regulate_scope :all unless Hyperstack.env.production? +end diff --git a/docs/specs/app/hyperstack/models/sample.rb b/docs/specs/app/hyperstack/models/sample.rb new file mode 100644 index 000000000..fcfe551ee --- /dev/null +++ b/docs/specs/app/hyperstack/models/sample.rb @@ -0,0 +1,2 @@ +class Sample < ApplicationRecord +end diff --git a/docs/specs/app/javascript/images/hyperloop-logo-medium-pink.png b/docs/specs/app/javascript/images/hyperloop-logo-medium-pink.png new file mode 100644 index 000000000..ba68f59b1 Binary files /dev/null and b/docs/specs/app/javascript/images/hyperloop-logo-medium-pink.png differ diff --git a/docs/specs/app/javascript/packs/client_and_server.js b/docs/specs/app/javascript/packs/client_and_server.js new file mode 100644 index 000000000..787f96ab9 --- /dev/null +++ b/docs/specs/app/javascript/packs/client_and_server.js @@ -0,0 +1,22 @@ +//app/javascript/packs/client_and_server.js +// these packages will be loaded both during prerendering and on the client +React = require('react'); // react-js library +createReactClass = require('create-react-class'); // backwards compatibility with ECMA5 +History = require('history'); // react-router history library +ReactRouter = require('react-router'); // react-router js library +ReactRouterDOM = require('react-router-dom'); // react-router DOM interface +ReactRailsUJS = require('react_ujs'); // interface to react-rails +// to add additional NPM packages run `yarn add package-name@version` +// then add the require here. +Mui = require('@material-ui/core') +Button = require('@material-ui/core/Button') +Sui = require('semantic-ui-react') + +webpackImagesMap = {}; +var imagesContext = require.context('../images/', true, /\.(gif|jpg|png|svg)$/i); + +function importAll (r) { + r.keys().forEach(key => webpackImagesMap[key] = r(key)); +} + +importAll(imagesContext); diff --git a/docs/specs/app/javascript/packs/client_only.js b/docs/specs/app/javascript/packs/client_only.js new file mode 100644 index 000000000..5eb4a6e48 --- /dev/null +++ b/docs/specs/app/javascript/packs/client_only.js @@ -0,0 +1,6 @@ +//app/javascript/packs/client_only.js +// add any requires for packages that will run client side only +ReactDOM = require('react-dom'); // react-js client side code +jQuery = require('jquery'); // remove if you don't need jQuery +// to add additional NPM packages call run yarn add package-name@version +// then add the require here. diff --git a/docs/specs/app/jobs/application_job.rb b/docs/specs/app/jobs/application_job.rb new file mode 100644 index 000000000..a009ace51 --- /dev/null +++ b/docs/specs/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/docs/specs/app/mailers/application_mailer.rb b/docs/specs/app/mailers/application_mailer.rb new file mode 100644 index 000000000..286b2239d --- /dev/null +++ b/docs/specs/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end diff --git a/docs/specs/app/models/concerns/.keep b/docs/specs/app/models/concerns/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/app/models/sample.rb b/docs/specs/app/models/sample.rb new file mode 100644 index 000000000..556d67f79 --- /dev/null +++ b/docs/specs/app/models/sample.rb @@ -0,0 +1,5 @@ +class Sample < ApplicationRecord + def self.super_secret_server_side_method + true + end +end diff --git a/docs/specs/app/policies/hyperstack/application_policy.rb b/docs/specs/app/policies/hyperstack/application_policy.rb new file mode 100644 index 000000000..40dac9d7c --- /dev/null +++ b/docs/specs/app/policies/hyperstack/application_policy.rb @@ -0,0 +1,16 @@ + # /Users/mitch/rubydev/hyperstack/ruby/rails-hyperstack/spec/test_app/app/policies/hyperstack/application_policy.rb + + # Policies regulate access to your public models + # The following policy will open up full access (but only in development) + # The policy system is very flexible and powerful. See the documentation + # for complete details. + module Hyperstack + class ApplicationPolicy + # Allow any session to connect: + always_allow_connection + # Send all attributes from all public models + regulate_all_broadcasts { |policy| policy.send_all } + # Allow all changes to models + allow_change(to: :all, on: [:create, :update, :destroy]) { true } + end unless Rails.env.production? + end diff --git a/docs/specs/app/views/layouts/application.html.erb b/docs/specs/app/views/layouts/application.html.erb new file mode 100644 index 000000000..1003a84ec --- /dev/null +++ b/docs/specs/app/views/layouts/application.html.erb @@ -0,0 +1,15 @@ + + + + TestApp + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + + + <%= yield %> + + diff --git a/docs/specs/app/views/layouts/mailer.html.erb b/docs/specs/app/views/layouts/mailer.html.erb new file mode 100644 index 000000000..cbd34d2e9 --- /dev/null +++ b/docs/specs/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/docs/specs/app/views/layouts/mailer.text.erb b/docs/specs/app/views/layouts/mailer.text.erb new file mode 100644 index 000000000..37f0bddbd --- /dev/null +++ b/docs/specs/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/docs/specs/babel.config.js b/docs/specs/babel.config.js new file mode 100644 index 000000000..4df194934 --- /dev/null +++ b/docs/specs/babel.config.js @@ -0,0 +1,70 @@ +module.exports = function(api) { + var validEnv = ['development', 'test', 'production'] + var currentEnv = api.env() + var isDevelopmentEnv = api.env('development') + var isProductionEnv = api.env('production') + var isTestEnv = api.env('test') + + if (!validEnv.includes(currentEnv)) { + throw new Error( + 'Please specify a valid `NODE_ENV` or ' + + '`BABEL_ENV` environment variables. Valid values are "development", ' + + '"test", and "production". Instead, received: ' + + JSON.stringify(currentEnv) + + '.' + ) + } + + return { + presets: [ + isTestEnv && [ + '@babel/preset-env', + { + targets: { + node: 'current' + } + } + ], + (isProductionEnv || isDevelopmentEnv) && [ + '@babel/preset-env', + { + forceAllTransforms: true, + useBuiltIns: 'entry', + corejs: 3, + modules: false, + exclude: ['transform-typeof-symbol'] + } + ] + ].filter(Boolean), + plugins: [ + 'babel-plugin-macros', + '@babel/plugin-syntax-dynamic-import', + isTestEnv && 'babel-plugin-dynamic-import-node', + '@babel/plugin-transform-destructuring', + [ + '@babel/plugin-proposal-class-properties', + { + loose: true + } + ], + [ + '@babel/plugin-proposal-object-rest-spread', + { + useBuiltIns: true + } + ], + [ + '@babel/plugin-transform-runtime', + { + helpers: false + } + ], + [ + '@babel/plugin-transform-regenerator', + { + async: false + } + ] + ].filter(Boolean) + } +} diff --git a/docs/specs/bin/bundle b/docs/specs/bin/bundle new file mode 100755 index 000000000..f19acf5b5 --- /dev/null +++ b/docs/specs/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/docs/specs/bin/rails b/docs/specs/bin/rails new file mode 100755 index 000000000..5badb2fde --- /dev/null +++ b/docs/specs/bin/rails @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/docs/specs/bin/rake b/docs/specs/bin/rake new file mode 100755 index 000000000..d87d5f578 --- /dev/null +++ b/docs/specs/bin/rake @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/docs/specs/bin/setup b/docs/specs/bin/setup new file mode 100755 index 000000000..94fd4d797 --- /dev/null +++ b/docs/specs/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/docs/specs/bin/spring b/docs/specs/bin/spring new file mode 100755 index 000000000..d89ee495f --- /dev/null +++ b/docs/specs/bin/spring @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +# This file loads Spring without using Bundler, in order to be fast. +# It gets overwritten when you run the `spring binstub` command. + +unless defined?(Spring) + require 'rubygems' + require 'bundler' + + lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) + spring = lockfile.specs.detect { |spec| spec.name == 'spring' } + if spring + Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path + gem 'spring', spring.version + require 'spring/binstub' + end +end diff --git a/docs/specs/bin/update b/docs/specs/bin/update new file mode 100755 index 000000000..58bfaed51 --- /dev/null +++ b/docs/specs/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/docs/specs/bin/webpack b/docs/specs/bin/webpack new file mode 100755 index 000000000..1031168d0 --- /dev/null +++ b/docs/specs/bin/webpack @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= "development" + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "bundler/setup" + +require "webpacker" +require "webpacker/webpack_runner" + +APP_ROOT = File.expand_path("..", __dir__) +Dir.chdir(APP_ROOT) do + Webpacker::WebpackRunner.run(ARGV) +end diff --git a/docs/specs/bin/webpack-dev-server b/docs/specs/bin/webpack-dev-server new file mode 100755 index 000000000..dd9662737 --- /dev/null +++ b/docs/specs/bin/webpack-dev-server @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= "development" + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "bundler/setup" + +require "webpacker" +require "webpacker/dev_server_runner" + +APP_ROOT = File.expand_path("..", __dir__) +Dir.chdir(APP_ROOT) do + Webpacker::DevServerRunner.run(ARGV) +end diff --git a/docs/specs/bin/yarn b/docs/specs/bin/yarn new file mode 100755 index 000000000..460dd565b --- /dev/null +++ b/docs/specs/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/docs/specs/config.ru b/docs/specs/config.ru new file mode 100644 index 000000000..f7ba0b527 --- /dev/null +++ b/docs/specs/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/docs/specs/config/application.rb b/docs/specs/config/application.rb new file mode 100644 index 000000000..274e98121 --- /dev/null +++ b/docs/specs/config/application.rb @@ -0,0 +1,33 @@ +require_relative 'boot' + +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "active_storage/engine" +require "action_controller/railtie" +require "action_mailer/railtie" +require "action_view/railtie" +require "action_cable/engine" +require "sprockets/railtie" +# require "rails/test_unit/railtie" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module TestApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.2 + + # 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. + + # Don't generate system test files. + config.generators.system_tests = nil + end +end diff --git a/docs/specs/config/boot.rb b/docs/specs/config/boot.rb new file mode 100644 index 000000000..b9e460cef --- /dev/null +++ b/docs/specs/config/boot.rb @@ -0,0 +1,4 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/docs/specs/config/cable.yml b/docs/specs/config/cable.yml new file mode 100644 index 000000000..26aa82dd9 --- /dev/null +++ b/docs/specs/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: test_app_production diff --git a/docs/specs/config/credentials.yml.enc b/docs/specs/config/credentials.yml.enc new file mode 100644 index 000000000..f2658b4da --- /dev/null +++ b/docs/specs/config/credentials.yml.enc @@ -0,0 +1 @@ +2I4YKMI75zh1i34wXLWZtosTS8RWD7rPvnTklEJUf+LLtLgBPx+x66IgKC1qXwm9YCZKr0RoLKKadRFNV83wbwn9HsYva1+4Y+Uqoe9osebmc6fpj49RFGCiyXn+vPqdwZAcxwV+YgpDXWPuCYGI53DaVe9RR1GA26GrrAUbzHwBWTJnCiBezseWpI/mg1JbhVtNVY08zF7emHZSY8hYAbpPV8jHt4aiiJFvwBpOO9AT5nXp4/q2+L92A6oruJmOg5jOwxT1jyn/Z0IMUUEfranEU6/cLgAiC8B0uVR9xsJf1raTAClnFiXQ4c25RHhyboVTebvEe0MVaEM4sekSIs/sCOZX7FjtVHpPAuzmyAcdsnkiQtGdi0iByyvmVrsg5ipD5Xd2qL3LbD85gMRmUesD7k5ctdjwgBfx--I4i4GlTqE68W2nZv--GCiEYpdDZPAhZ0Tdn5qYQg== \ No newline at end of file diff --git a/docs/specs/config/database.yml b/docs/specs/config/database.yml new file mode 100644 index 000000000..0d02f2498 --- /dev/null +++ b/docs/specs/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/docs/specs/config/environment.rb b/docs/specs/config/environment.rb new file mode 100644 index 000000000..426333bb4 --- /dev/null +++ b/docs/specs/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/docs/specs/config/environments/development.rb b/docs/specs/config/environments/development.rb new file mode 100644 index 000000000..1311e3e4e --- /dev/null +++ b/docs/specs/config/environments/development.rb @@ -0,0 +1,61 @@ +Rails.application.configure do + # 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/docs/specs/config/environments/production.rb b/docs/specs/config/environments/production.rb new file mode 100644 index 000000000..0a7c662b5 --- /dev/null +++ b/docs/specs/config/environments/production.rb @@ -0,0 +1,94 @@ +Rails.application.configure do + # 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 = "test_app_#{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/docs/specs/config/environments/test.rb b/docs/specs/config/environments/test.rb new file mode 100644 index 000000000..60a779896 --- /dev/null +++ b/docs/specs/config/environments/test.rb @@ -0,0 +1,49 @@ +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 + + # added by hyperstack installer + config.assets.paths << Rails.root.join('public', 'packs-test', 'js').to_s +end diff --git a/docs/specs/config/initializers/application_controller_renderer.rb b/docs/specs/config/initializers/application_controller_renderer.rb new file mode 100644 index 000000000..89d2efab2 --- /dev/null +++ b/docs/specs/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/docs/specs/config/initializers/assets.rb b/docs/specs/config/initializers/assets.rb new file mode 100644 index 000000000..7188478c4 --- /dev/null +++ b/docs/specs/config/initializers/assets.rb @@ -0,0 +1,15 @@ +# 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 ) + Rails.application.config.assets.paths << Rails.root.join('public', 'packs', 'js').to_s diff --git a/docs/specs/config/initializers/backtrace_silencers.rb b/docs/specs/config/initializers/backtrace_silencers.rb new file mode 100644 index 000000000..59385cdf3 --- /dev/null +++ b/docs/specs/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/docs/specs/config/initializers/content_security_policy.rb b/docs/specs/config/initializers/content_security_policy.rb new file mode 100644 index 000000000..d3bcaa5ec --- /dev/null +++ b/docs/specs/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# 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 + +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end + +# If you are using UJS then enable automatic nonce generation +# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } + +# 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/docs/specs/config/initializers/cookies_serializer.rb b/docs/specs/config/initializers/cookies_serializer.rb new file mode 100644 index 000000000..5a6a32d37 --- /dev/null +++ b/docs/specs/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/docs/specs/config/initializers/filter_parameter_logging.rb b/docs/specs/config/initializers/filter_parameter_logging.rb new file mode 100644 index 000000000..4a994e1e7 --- /dev/null +++ b/docs/specs/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/docs/specs/config/initializers/hyperstack.rb b/docs/specs/config/initializers/hyperstack.rb new file mode 100644 index 000000000..e3ff05d6d --- /dev/null +++ b/docs/specs/config/initializers/hyperstack.rb @@ -0,0 +1,46 @@ + +# transport controls how push (websocket) communications are +# implemented. The default is :none. +# Other possibilities are :action_cable, :pusher (see www.pusher.com) +# or :simple_poller which is sometimes handy during system debug. + +Hyperstack.transport = :action_cable # :pusher, :simple_poller or :none + + +# Hyperstack.import 'react/react-source-browser' # uncomment this line if you want hyperstack to use its copy of react +Hyperstack.import 'hyperstack/hotloader', client_only: true if Rails.env.development? + +# server_side_auto_require will patch the ActiveSupport Dependencies module +# so that you can define classes and modules with files in both the +# app/hyperstack/xxx and app/xxx directories. For example you can split +# a Todo model into server and client related definitions and place this +# in `app/hyperstack/models/todo.rb`, and place any server only definitions in +# `app/models/todo.rb`. + +require "hyperstack/server_side_auto_require.rb" + +# set the component base class + +Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent' + +# prerendering is default :off, you should wait until your +# application is relatively well debugged before turning on. + +Hyperstack.prerendering = :off # or :on + +# add this line if you need jQuery AND ARE NOT USING WEBPACK +# Hyperstack.import 'hyperstack/component/jquery', client_only: true + +# change definition of on_error to control how errors such as validation +# exceptions are reported on the server +module Hyperstack + def self.on_error(operation, err, params, formatted_error_message) + ::Rails.logger.debug( + "#{formatted_error_message}\n\n" + + Pastel.new.red( + 'To further investigate you may want to add a debugging '\ + 'breakpoint to the on_error method in config/initializers/hyperstack.rb' + ) + ) + end +end if Rails.env.development? diff --git a/docs/specs/config/initializers/inflections.rb b/docs/specs/config/initializers/inflections.rb new file mode 100644 index 000000000..ac033bf9d --- /dev/null +++ b/docs/specs/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/docs/specs/config/initializers/mime_types.rb b/docs/specs/config/initializers/mime_types.rb new file mode 100644 index 000000000..dc1899682 --- /dev/null +++ b/docs/specs/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/docs/specs/config/initializers/wrap_parameters.rb b/docs/specs/config/initializers/wrap_parameters.rb new file mode 100644 index 000000000..bbfc3961b --- /dev/null +++ b/docs/specs/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/docs/specs/config/locales/en.yml b/docs/specs/config/locales/en.yml new file mode 100644 index 000000000..decc5a857 --- /dev/null +++ b/docs/specs/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/docs/specs/config/master.key b/docs/specs/config/master.key new file mode 100644 index 000000000..a2b9f71e1 --- /dev/null +++ b/docs/specs/config/master.key @@ -0,0 +1 @@ +814e5877df4bd7952ab20a37feca8b00 \ No newline at end of file diff --git a/docs/specs/config/puma.rb b/docs/specs/config/puma.rb new file mode 100644 index 000000000..b2102072b --- /dev/null +++ b/docs/specs/config/puma.rb @@ -0,0 +1,37 @@ +# 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 `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver 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/docs/specs/config/routes.rb b/docs/specs/config/routes.rb new file mode 100644 index 000000000..115e3b498 --- /dev/null +++ b/docs/specs/config/routes.rb @@ -0,0 +1,6 @@ +Rails.application.routes.draw do + mount Hyperstack::Engine => '/hyperstack' # this route should be first in the routes file so it always matches + get '/(*others)', to: 'hyperstack#App' + # get '/(*others)', to: 'hyperstack#app' + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html +end diff --git a/docs/specs/config/spring.rb b/docs/specs/config/spring.rb new file mode 100644 index 000000000..9fa7863f9 --- /dev/null +++ b/docs/specs/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/docs/specs/config/storage.yml b/docs/specs/config/storage.yml new file mode 100644 index 000000000..d32f76e8f --- /dev/null +++ b/docs/specs/config/storage.yml @@ -0,0 +1,34 @@ +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 +# 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/docs/specs/config/webpack/development.js b/docs/specs/config/webpack/development.js new file mode 100644 index 000000000..c5edff94a --- /dev/null +++ b/docs/specs/config/webpack/development.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/docs/specs/config/webpack/environment.js b/docs/specs/config/webpack/environment.js new file mode 100644 index 000000000..d16d9af74 --- /dev/null +++ b/docs/specs/config/webpack/environment.js @@ -0,0 +1,3 @@ +const { environment } = require('@rails/webpacker') + +module.exports = environment diff --git a/docs/specs/config/webpack/production.js b/docs/specs/config/webpack/production.js new file mode 100644 index 000000000..be0f53aac --- /dev/null +++ b/docs/specs/config/webpack/production.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'production' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/docs/specs/config/webpack/test.js b/docs/specs/config/webpack/test.js new file mode 100644 index 000000000..c5edff94a --- /dev/null +++ b/docs/specs/config/webpack/test.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/docs/specs/config/webpacker.yml b/docs/specs/config/webpacker.yml new file mode 100644 index 000000000..a6b146566 --- /dev/null +++ b/docs/specs/config/webpacker.yml @@ -0,0 +1,92 @@ +# Note: You must restart bin/webpack-dev-server for changes to take effect + +default: &default + source_path: app/javascript + source_entry_path: packs + public_root_path: public + public_output_path: packs + cache_path: tmp/cache/webpacker + webpack_compile_output: true + + # Additional paths webpack should lookup modules + # ['app/assets', 'engine/foo/app/assets'] + additional_paths: [] + + # Reload manifest.json on all requests so we reload latest compiled packs + cache_manifest: false + + # Extract and emit a css file + extract_css: false + + static_assets_extensions: + - .jpg + - .jpeg + - .png + - .gif + - .tiff + - .ico + - .svg + - .eot + - .otf + - .ttf + - .woff + - .woff2 + + extensions: + - .mjs + - .js + - .sass + - .scss + - .css + - .module.sass + - .module.scss + - .module.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 + pretty: 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 + + # Extract and emit a css file + extract_css: true + + # Cache manifest.json for performance + cache_manifest: true diff --git a/docs/specs/db/development.sqlite3 b/docs/specs/db/development.sqlite3 new file mode 100644 index 000000000..d0ada1f15 Binary files /dev/null and b/docs/specs/db/development.sqlite3 differ diff --git a/docs/specs/db/migrate/20210318185111_create_samples.rb b/docs/specs/db/migrate/20210318185111_create_samples.rb new file mode 100644 index 000000000..414b4f664 --- /dev/null +++ b/docs/specs/db/migrate/20210318185111_create_samples.rb @@ -0,0 +1,10 @@ +class CreateSamples < ActiveRecord::Migration[5.2] + def change + create_table :samples do |t| + t.string :name + t.text :description + + t.timestamps + end + end +end diff --git a/docs/specs/db/schema.rb b/docs/specs/db/schema.rb new file mode 100644 index 000000000..9b567d04c --- /dev/null +++ b/docs/specs/db/schema.rb @@ -0,0 +1,22 @@ +# 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. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 2021_03_18_185111) do + + create_table "samples", force: :cascade do |t| + t.string "name" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + +end diff --git a/docs/specs/db/seeds.rb b/docs/specs/db/seeds.rb new file mode 100644 index 000000000..1beea2acc --- /dev/null +++ b/docs/specs/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) +# Character.create(name: 'Luke', movie: movies.first) diff --git a/docs/specs/db/test.sqlite3 b/docs/specs/db/test.sqlite3 new file mode 100644 index 000000000..b8bc41c17 Binary files /dev/null and b/docs/specs/db/test.sqlite3 differ diff --git a/docs/specs/lib/assets/.keep b/docs/specs/lib/assets/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/lib/tasks/.keep b/docs/specs/lib/tasks/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/log/.keep b/docs/specs/log/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/package.json b/docs/specs/package.json new file mode 100644 index 000000000..75623160d --- /dev/null +++ b/docs/specs/package.json @@ -0,0 +1,24 @@ +{ + "name": "test-app", + "private": true, + "dependencies": { + "@material-ui/core": "^4.11.3", + "@rails/actioncable": "^6.0.0", + "@rails/activestorage": "^6.0.0", + "@rails/ujs": "^6.0.0", + "@rails/webpacker": "5.2.1", + "create-react-class": "^15.7.0", + "jquery": "^3.6.0", + "react": "16", + "react-dom": "16", + "react-router": "^5.0.0", + "react-router-dom": "^5.0.0", + "react_ujs": "^2.5.0", + "semantic-ui-react": "^2.0.3", + "turbolinks": "^5.2.0" + }, + "version": "0.1.0", + "devDependencies": { + "webpack-dev-server": "^3.11.2" + } +} diff --git a/docs/specs/postcss.config.js b/docs/specs/postcss.config.js new file mode 100644 index 000000000..aa5998a80 --- /dev/null +++ b/docs/specs/postcss.config.js @@ -0,0 +1,12 @@ +module.exports = { + plugins: [ + require('postcss-import'), + require('postcss-flexbugs-fixes'), + require('postcss-preset-env')({ + autoprefixer: { + flexbox: 'no-2009' + }, + stage: 3 + }) + ] +} diff --git a/docs/specs/public/404.html b/docs/specs/public/404.html new file mode 100644 index 000000000..2be3af26f --- /dev/null +++ b/docs/specs/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/docs/specs/public/422.html b/docs/specs/public/422.html new file mode 100644 index 000000000..c08eac0d1 --- /dev/null +++ b/docs/specs/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
+
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/docs/specs/public/500.html b/docs/specs/public/500.html new file mode 100644 index 000000000..78a030af2 --- /dev/null +++ b/docs/specs/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
+
+

We're sorry, but something went wrong.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/docs/specs/public/apple-touch-icon.png b/docs/specs/public/apple-touch-icon.png new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/public/favicon.ico b/docs/specs/public/favicon.ico new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/public/robots.txt b/docs/specs/public/robots.txt new file mode 100644 index 000000000..37b576a4a --- /dev/null +++ b/docs/specs/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/docs/specs/spec/client-dsl/README_spec.rb b/docs/specs/spec/client-dsl/README_spec.rb new file mode 100644 index 000000000..3389d801f --- /dev/null +++ b/docs/specs/spec/client-dsl/README_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' +describe "README", :js do + it "Clock" do + mount "Clock" do + class Clock < HyperComponent + # Components can be parameterized. + # in this case you can override the default + # with a different format + param format: "%d-%m-%Y %I:%M:%S %p" + # before_mount and after_mount are examples of a life cycle methods. + before_mount do + # Before the component is first rendered (mounted) + # we initialize @current_time + @current_time = Time.now + end + after_mount do + # after the component is mounted + # we setup a periodic timer that will update the + # current_time instance variable every second. + # The mutate method signals a change in state + every(1.second) { mutate @current_time = Time.now } + end + # every component has a render block which describes what will be + # drawn on the UI + render do + # Components can render other components or primitive HTML or SVG + # tags. Components also use their state to determine what to render, + # in this case the @current_time instance variable + DIV { @current_time.strftime(format) } + end + end + end + expect(Time.parse(find("div div").text)).to be_within(5.seconds).of Time.now + Timecop.travel(Time.now+30.seconds) do + sleep 1 + expect(Time.parse(find("div div").text)).to be_within(5.seconds).of Time.now + end + end +end diff --git a/docs/specs/spec/client-dsl/error-recovery_spec.rb b/docs/specs/spec/client-dsl/error-recovery_spec.rb new file mode 100644 index 000000000..ca3bafb32 --- /dev/null +++ b/docs/specs/spec/client-dsl/error-recovery_spec.rb @@ -0,0 +1,135 @@ +require "spec_helper" +describe "error-recovery.md", :js do + describe "example 1 and 2" do + before(:each) do + before_mount do + class ContentWhichFailsSometimes < HyperComponent + class << self + mutator :fail_now! do + @fail_now = true + end + def time_to_fail? + return unless @fail_now + + @fail_now = false + raise "well not so good" + end + end + render do + DIV(id: :tp_1) { "I'm okay #{Time.now}" } + ContentWhichFailsSometimes.time_to_fail? + end + end + class ReportError < Hyperstack::Operation + param :err + end + end + end + + it "recovers from failure 1" do + mount "App" do + class App < HyperComponent + render(DIV) do + H1 { "Welcome to Our App" } + if @failure_fall_back + DIV { 'Whoops we had a little problem!' } + BUTTON { 'retry' }.on(:click) { mutate @failure_fall_back = false } + else + ContentWhichFailsSometimes() + end + end + + rescues do |err| + @failure_fall_back = true + ReportError.run(err: err) + end + end + end + expect(page).to have_content "I'm okay" + on_client { ContentWhichFailsSometimes.fail_now! } + expect(page).to have_content "Whoops we had a little problem!" + find("button").click + expect(page).to have_content "I'm okay" + end + + it "recovers from failure 2" do + mount "App" do + class App < HyperComponent + render(DIV) do + H1 { "Welcome to Our App" } + ContentWhichFailsSometimes() + end + + rescues do + end + end + end + expect(page).to have_content "I'm okay" + last_rendered_message = find('div#tp_1').text + Timecop.travel(1.minute) do + on_client { ContentWhichFailsSometimes.fail_now! } + expect(page).to have_content "I'm okay" + expect(find('div#tp_1').text).not_to eq last_rendered_message + end + end + + it "rescue block arguments" do + mount "App" do + class App < HyperComponent + render(DIV) do + H1 { "Welcome to Our App" } + ContentWhichFailsSometimes() + end + + rescues do |*args| + puts "I received: [#{args}]" + args.each do |arg| + puts arg.class + end + puts args.first.backtrace + App.err = args + end + class << self + attr_accessor :err + end + end + end + on_client { ContentWhichFailsSometimes.fail_now! } + expect { App.err[0] }.on_client_to eq "well not so good" + expect { App.err[0].class }.on_client_to eq "RuntimeError" + expect { App.err[0].respond_to? :backtrace }.on_client_to be_truthy + expect { App.err[0].respond_to? :message }.on_client_to be_truthy + expect { App.err[1].keys }.on_client_to eq ["componentStack"] + end + + it "rescuing from callbacks" do + mount "App" do + class InnerApp < HyperComponent + before_mount do + raise "whoops" unless App.failed_once + end + render {} + end + class App < HyperComponent + class << self + attr_accessor :failed_once + end + render(DIV) do + H1 { "Welcome to Our App" } + InnerApp() + end + + rescues do |*args| + App.failed_once = true + puts "rescues. #{App.object_id} #{!!App.failed_once}" + end + end + end + on_client { ContentWhichFailsSometimes.fail_now! } + expect(page).to have_content "Welcome to Our App" + end + + + + end +end diff --git a/docs/specs/spec/client-dsl/javascript-components_spec.rb b/docs/specs/spec/client-dsl/javascript-components_spec.rb new file mode 100644 index 000000000..240efd0d6 --- /dev/null +++ b/docs/specs/spec/client-dsl/javascript-components_spec.rb @@ -0,0 +1,128 @@ +require "spec_helper" + +describe "interfacing to javascript components", :js do + it "can import a single JS component" do + mount "MyBigApp" do + %x{ + window.SayHello = class extends React.Component { + constructor(props) { + super(props); + this.displayName = "SayHello" + } + render() { return React.createElement("div", null, "Hello ", this.props.name); } + } + } + + class SayHello < HyperComponent + imports 'SayHello' + end + + class MyBigApp < HyperComponent + render(DIV) do + # SayHello will now act like any other Hyperstack component + SayHello name: 'Matz' + end + end + end + expect(page).to have_content "Hello Matz" + end + it "will auto import a whole library" do + mount "App" do + class App < HyperComponent + render do + Mui::Button(variant: :contained, color: :primary) { "Click me" }.on(:click) do + alert 'you clicked the primary button!' + end + Button(variant: :contained, color: :secondary) { "Click me" }.on(:click) do + alert 'you clicked the secondary button!' + end + end + end + end + find('button.MuiButton-containedPrimary').click + expect(accept_alert).to eq "you clicked the primary button!" + find('button.MuiButton-containedSecondary').click + expect(accept_alert).to eq "you clicked the secondary button!" + end + + it "importing images" do + mount "App" do + class HyperComponent + def self.img_src(file_path) + @img_map ||= Native(`webpackImagesMap`) + @img_map["./#{file_path}"] + end + def img_src(file_path) + HyperComponent.img_src(file_path) + end + end + class App < HyperComponent + render do + IMG(src: img_src("hyperloop-logo-medium-pink.png")) + end + end + end + expect(File).to exist "#{Rails.root}/public#{URI(find('img')[:src]).path}" + end + + it "fun with jQuery" do + mount "App" do + class App < HyperComponent + render(FRAGMENT) do + DIV(id: :inner_div_1) { 'hello' } + DIV(id: :inner_div_2) { 'goodby' } + end + end + end + on_client { jQ['#inner_div_1'].html = 'zoom' } + expect(find('#inner_div_1').text).to eq 'zoom' + on_client { Element['#inner_div_1'].html = 'foobar' } + expect(find('#inner_div_1').text).to eq 'foobar' + end + + it "using dom_node" do + mount "App" do + class App < HyperComponent + render(FRAGMENT) do + INPUT(id: :text_1) + FocusedInput() + end + end + class FocusedInput < HyperComponent + after_mount do + jQ[dom_node].focus + end + render do + INPUT(id: :text_2) + end + end + end + page.send_keys 'hello' + expect(find('input#text_2').value).to eq 'hello' + end + + it 'passing elements to javascript components' do + mount "App" do + class App < HyperComponent + render do + tab1 = Sui::TabPane() do + P { "Tab 1 Content" } + end + + tab2 = Sui::TabPane() do + P { "Tab 2 Content" } + end + + panes = [ + { menuItem: "Tab 1", render: tab1 }, + { menuItem: "Tab 2", render: tab2 } + ] + + Sui::Tab(panes: panes) + end + end + end + expect(page).to have_content "Tab 1 Content" + expect(page).to have_content "Tab 1 Content" + end +end diff --git a/docs/specs/spec/client-dsl/params_spec.rb b/docs/specs/spec/client-dsl/params_spec.rb new file mode 100644 index 000000000..261254f39 --- /dev/null +++ b/docs/specs/spec/client-dsl/params_spec.rb @@ -0,0 +1,27 @@ +require "spec_helper" + +describe "params", :js do + it "passing an element" do + mount "App" do + class Reveal < HyperComponent + param :content + render do + BUTTON { "#{@show ? 'hide' : 'show'} me" } + .on(:click) { mutate @show = !@show } + content.render if @show + end + end + + class App < HyperComponent + render do + Reveal(content: DIV { "I came from the App" }) + end + end + end + expect(find("button").text).to eq "show me" + expect(page).not_to have_content("I came from the App", wait: 0) + find("button").click + expect(find("button").text).to eq "hide me" + expect(page).to have_content("I came from the App") + end +end diff --git a/docs/specs/spec/client-dsl/predefined-events_spec.rb b/docs/specs/spec/client-dsl/predefined-events_spec.rb new file mode 100644 index 000000000..15fd91087 --- /dev/null +++ b/docs/specs/spec/client-dsl/predefined-events_spec.rb @@ -0,0 +1,78 @@ +# spec/client-dsl/predefined-events_spec.rb +require "spec_helper" + +describe "predefined-events.md", :js do + it "The YouSaid Component" do + mount "YouSaid" do + class YouSaid < HyperComponent + state_accessor :value + render(DIV) do + INPUT(value: value) + .on(:key_down) do |e| + next unless e.key_code == 13 + + alert "You said: #{value}" + self.value = "" + end + .on(:change) do |e| + self.value = e.target.value + end + end + end + end + 2.times do + expect( + accept_alert { find("input").send_keys "hello", :enter } + ).to eq "You said: hello" + end + end + + it "The YouSaid Component - with Enter Event" do + mount "YouSaid" do + class YouSaid < HyperComponent + state_accessor :value + render(DIV) do + INPUT(value: value) + .on(:enter) do + alert "You said: #{value}" + self.value = "" + end + .on(:change) do |e| + self.value = e.target.value + end + end + end + end + 2.times do + expect( + accept_alert { find("input").send_keys "hello", :enter } + ).to eq "You said: hello" + end + end + + it "Drag and Drop Demo" do + mount "DragAndDrop" do + class DragAndDrop < HyperComponent + render do + DIV(id: :div1, style: { width: 350, height: 70, padding: 10, border: '1px solid #aaaaaa' }) + .on(:drop) do |evt| + evt.prevent_default + data = `#{evt.native_event}.native.dataTransfer.getData("text")` + `#{evt.target}.native.appendChild(document.getElementById(data))` + end + .on(:drag_over, &:prevent_default) + + IMG(id: :drag1, src: "https://www.w3schools.com/html/img_logo.gif", draggable: "true", width: 336, height: 69) + .on(:drag_start) do |evt| + `#{evt.native_event}.native.dataTransfer.setData("text", #{evt.target}.native.id)` + end + end + end + end + source = find("img") + target = find("div#div1") + expect(source.find(:xpath, "..")[:id]).not_to eq target + source.drag_to target + expect(source.find(:xpath, "..")).to eq target + end +end diff --git a/docs/specs/spec/hyper-state/tic_tac_toe_spec.rb b/docs/specs/spec/hyper-state/tic_tac_toe_spec.rb new file mode 100644 index 000000000..8961b8bd8 --- /dev/null +++ b/docs/specs/spec/hyper-state/tic_tac_toe_spec.rb @@ -0,0 +1,739 @@ +require "spec_helper" + +CSS = <<~CSS + body { + font: 14px "Century Gothic", Futura, sans-serif; + margin: 20px; + } + + ol, ul { + padding-left: 30px; + } + + .board_row:after { + clear: both; + content: ""; + display: table; + } + + .status { + margin-bottom: 10px; + } + + .square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; + } + + .square:focus { + outline: none; + } + + .kbd-navigation .square:focus { + background: #ddd; + } + + .game { + display: flex; + flex-direction: row; + } + + .game_info { + margin-left: 20px; + } +CSS + +describe "Tic Tac Toe Game", :js do + def buttons + find_all("button", wait: 0.0) + end + + def squares + buttons.collect(&:text) + end + + def lis + find_all("li", wait: 0.0) + end + + def history + lis.collect(&:text) + end + + def run_the_spec(initial_history) + expect(page).to have_content "Next player: X", wait: 0 + expect(history).to eq initial_history + expect(squares.count).to eq 9 + expect(squares.detect(&:present?)).to be_nil + buttons[0].click + expect(page).to have_content "Next player: O", wait: 0 + expect(history).to eq ["Go to game start", "Go to move #1"] + expect(squares).to eq ["X", "", "", "", "", "", "", "", ""] + buttons[4].click + expect(page).to have_content "Next player: X", wait: 0 + expect(history).to eq ["Go to game start", "Go to move #1", "Go to move #2"] + expect(squares).to eq ["X", "", "", "", "O", "", "", "", ""] + buttons[2].click + expect(page).to have_content "Next player: O", wait: 0 + expect(history).to eq ["Go to game start", "Go to move #1", "Go to move #2", "Go to move #3"] + expect(squares).to eq ["X", "", "X", "", "O", "", "", "", ""] + buttons[7].click + expect(page).to have_content "Next player: X", wait: 0 + expect(history).to eq ["Go to game start", "Go to move #1", "Go to move #2", "Go to move #3", "Go to move #4"] + expect(squares).to eq ["X", "", "X", "", "O", "", "", "O", ""] + buttons[1].click + expect(page).to have_content "Winner: X", wait: 0 + lis[3].click + expect(squares).to eq ["X", "", "X", "", "O", "", "", "", ""] + expect(history).to eq ["Go to game start", "Go to move #1", "Go to move #2", "Go to move #3", "Go to move #4", "Go to move #5"] + expect(page).to have_content "Next player: O", wait: 0 + buttons[1].click + expect(squares).to eq ["X", "O", "X", "", "O", "", "", "", ""] + expect(history).to eq ["Go to game start", "Go to move #1", "Go to move #2", "Go to move #3", "Go to move #4"] + expect(page).to have_content "Next player: X", wait: 0 + buttons[3].click + buttons[7].click + expect(page).to have_content "Winner: O", wait: 0 + lis[3].click + buttons[0].click # note in the original JSX implementation this caused a failure + lis[4].click # because even though the click is invalid, it still erased the history + expect(squares).to eq ["X", "O", "X", "", "O", "", "", "", ""] + end + + it "first translation", skip: "fails see note above at end of run_the_spec" do + insert_html "" + mount "Game" do + # function Square(props) { + # return ( + # + # ); + # } + # + # class Square < HyperComponent + # param :value + # fires :click + # render do + # BUTTON(class: :square) { value } + # .on(:click) { click! } + # end + # end + + # class Board extends React.Component { + # renderSquare(i) { + # return ( + # this.props.onClick(i)} + # /> + # ); + # }# + # render() { + # return ( + #
+ #
+ # {this.renderSquare(0)} + # {this.renderSquare(1)} + # {this.renderSquare(2)} + #
+ #
+ # {this.renderSquare(3)} + # {this.renderSquare(4)} + # {this.renderSquare(5)} + #
+ #
+ # {this.renderSquare(6)} + # {this.renderSquare(7)} + # {this.renderSquare(8)} + #
+ #
+ # ); + # } + # } + + class Board < HyperComponent + param :squares + fires :click + + def draw_square(id) + BUTTON(class: :square) { squares[id] } + .on(:click) { click!(id) } + end + + render(DIV) do + 3.times do |row| + DIV(class: :board_row) do + 3.times { |col| draw_square(row * 3 + col) } + end + end + end + end + + # class Game extends React.Component { + # constructor(props) { + # super(props); + # this.state = { + # history: [ + # { + # squares: Array(9).fill(null) + # } + # ], + # stepNumber: 0, + # xIsNext: true + # }; + # } + + class Game < HyperComponent + before_mount do + @history = [{ squares: Array.new(9) }] + @step = 0 + end + + def player + @step.even? ? :X : :O + end + + # handleClick(i) { + # const history = this.state.history.slice(0, this.state.stepNumber + 1); + # const current = history[history.length - 1]; + # const squares = current.squares.slice(); + # if (calculateWinner(squares) || squares[i]) { + # return; + # } + # squares[i] = this.state.xIsNext ? "X" : "O"; + # this.setState({ + # history: history.concat([ + # { + # squares: squares + # } + # ]), + # stepNumber: history.length, + # xIsNext: !this.state.xIsNext + # }); + # } + + mutator :handle_click! do |i| + @history = @history[0..@step] + current = @history.last + squares = current[:squares].dup + return if winner?(squares) || squares[i] + + squares[i] = player + @history += [{ squares: squares }] + @step += 1 + end + + # jumpTo(step) { + # this.setState({ + # stepNumber: step, + # xIsNext: (step % 2) === 0 + # }); + # } + + mutator(:jump_to!) { |step| @step = step } + + # const moves = history.map((step, move) => { + # const desc = move ? + # 'Go to move #' + move : + # 'Go to game start'; + # return ( + #
  • + # + #
  • + # ); + # }); + + def moves + @history.length.times do |move| + LI(key: move) do + move.zero? ? "Go to game start" : "Go to move ##{move}" + end.on(:click) { jump_to!(move) } + end + end + + # const history = this.state.history; + # const current = history[this.state.stepNumber]; + # const winner = calculateWinner(current.squares); + # + # + # let status; + # if (winner) { + # status = "Winner: " + winner; + # } else { + # status = "Next player: " + (this.state.xIsNext ? "X" : "O"); + # } + + def current + @history[@step] + end + + def status + if (winner = winner? current[:squares]) + "Winner: #{winner}" + else + "Next player: #{player}" + end + end + + # return ( + #
    + #
    + # this.handleClick(i)} + # /> + #
    + #
    + #
    {status}
    + #
      {moves}
    + #
    + #
    + # ); + + render(DIV, class: :game) do + DIV(class: :game_board) do + Board(squares: current[:squares]) + .on(:click, &method(:handle_click!)) + end + DIV(class: :game_info) do + DIV { status } + OL { moves } + end + end + + # function calculateWinner(squares) { + # const lines = [ + # [0, 1, 2], + # [3, 4, 5], + # [6, 7, 8], + # [0, 3, 6], + # [1, 4, 7], + # [2, 5, 8], + # [0, 4, 8], + # [2, 4, 6] + # ]; + # for (let i = 0; i < lines.length; i++) { + # const [a, b, c] = lines[i]; + # if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + # return squares[a]; + # } + # } + # return null; + # } + + LINES = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6] + ] + + def winner?(squares) + LINES.each do |a, b, c| + return squares[a] if squares[a] && squares[a] == squares[b] && squares[a] == squares[c] + end + false + end + end + end + run_the_spec(["Go to game start"]) + end + + it "works (even better)" do + insert_html "" + mount "Game" do + class Board < HyperComponent + param :squares + fires :clicked_at + + def draw_square(id) + BUTTON(class: :square, id: id) { squares[id] } + .on(:click) { clicked_at!(id) } + end + + render(DIV) do + (0..6).step(3) do |row| + DIV(class: :board_row) do + (row..row + 2).each { |id| draw_square(id) } + end + end + end + end + + class Game < HyperComponent + before_mount do + @history = [{ squares: [] }] + @step = 0 + end + + def player + @step.even? ? :X : :O + end + + mutator :handle_click! do |id| + squares = @history[@step][:squares] + return if winner?(squares) || squares[id] + + squares = squares.dup + squares[id] = player + @history = @history[0..@step] + [{ squares: squares }] + @step += 1 + end + + mutator(:jump_to!) { |step| @step = step } + + def moves + return unless @history.length > 1 + + @history.length.times do |move| + LI(key: move) { move.zero? ? "Go to game start" : "Go to move ##{move}" } + .on(:click) { jump_to!(move) } + end + end + + def current + @history[@step] + end + + def status + if (winner = winner? current[:squares]) + "Winner: #{winner}" + else + "Next player: #{player}" + end + end + + render(DIV, class: :game) do + DIV(class: :game_board) do + Board(squares: current[:squares]) + .on(:clicked_at, &method(:handle_click!)) + end + DIV(class: :game_info) do + DIV { status } + OL { moves } + end + end + + WINNING_COMBOS = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6] + ] + + def winner?(board) + WINNING_COMBOS.each do |a, b, c| + return board[a] if board[a] && board[a] == board[b] && board[a] == board[c] + end + false + end + end + end + run_the_spec([]) + end + + it "simplified the board - no hash" do + insert_html "" + mount "DisplayGame" do + class DisplayBoard < HyperComponent + param :board + fires :clicked_at + + def draw_square(id) + BUTTON(class: :square, id: id) { board[id] } + .on(:click) { clicked_at!(id) } + end + + render(DIV) do + (0..6).step(3) do |row| + DIV(class: :board_row) do + (row..row + 2).each { |id| draw_square(id) } + end + end + end + end + + class DisplayGame < HyperComponent + before_mount do + @history = [[]] + @step = 0 + end + + def current + @history[@step] + end + + WINNING_COMBOS = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6] + ] + + def current_winner? + WINNING_COMBOS.each do |a, b, c| + return current[a] if current[a] && current[a] == current[b] && current[a] == current[c] + end + false + end + + def player + @step.even? ? :X : :O + end + + state_reader :history + + mutator :handle_click! do |id| + board = history[@step] + return if current_winner? || board[id] + + board = board.dup + board[id] = player + @history = history[0..@step] + [board] + @step += 1 + end + + mutator(:jump_to!) { |step| @step = step } + + def moves + return unless history.length > 1 + + history.length.times do |move| + LI(key: move) { move.zero? ? "Go to game start" : "Go to move ##{move}" } + .on(:click) { jump_to!(move) } + end + end + + def status + if (winner = current_winner?) + "Winner: #{winner}" + else + "Next player: #{player}" + end + end + + render(DIV, class: :game) do + DIV(class: :game_board) do + DisplayBoard(board: current) + .on(:clicked_at, &method(:handle_click!)) + end + DIV(class: :game_info) do + DIV { status } + OL { moves } + end + end + end + end + run_the_spec([]) + end + + it "using a store" do + insert_html "" + mount "DisplayGame" do + class Game + include Hyperstack::State::Observable + + def initialize + @history = [[]] + @step = 0 + end + + observer :player do + @step.even? ? :X : :O + end + + observer :current do + @history[@step] + end + + state_reader :history + + WINNING_COMBOS = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]] + + def current_winner? + WINNING_COMBOS.each do |a, b, c| + return current[a] if current[a] && current[a] == current[b] && current[a] == current[c] + end + false + end + + mutator :handle_click! do |id| + board = history[@step] + return if current_winner? || board[id] + + board = board.dup + board[id] = player + @history = history[0..@step] + [board] + @step += 1 + end + + mutator(:jump_to!) { |step| @step = step } + end + + class DisplayCurrentBoard < HyperComponent + param :game + + def draw_square(id) + BUTTON(class: :square, id: id) { game.current[id] } + .on(:click) { game.handle_click!(id) } + end + + render(DIV) do + (0..6).step(3) do |row| + DIV(class: :board_row) do + (row..row + 2).each { |id| draw_square(id) } + end + end + end + end + + class DisplayGame < HyperComponent + before_mount { @game = Game.new } + def moves + return unless @game.history.length > 1 + + @game.history.length.times do |move| + LI(key: move) { move.zero? ? "Go to game start" : "Go to move ##{move}" } + .on(:click) { @game.jump_to!(move) } + end + end + + def status + if (winner = @game.current_winner?) + "Winner: #{winner}" + else + "Next player: #{@game.player}" + end + end + + render(DIV, class: :game) do + DIV(class: :game_board) do + DisplayCurrentBoard(game: @game) + end + DIV(class: :game_info) do + DIV { status } + OL { moves } + end + end + end + + end + run_the_spec([]) + end + + it "using a class level store" do + insert_html "" + mount "DisplayGame" do + class Game + include Hyperstack::State::Observable + class << self + def initialize + @history = [[]] + @step = 0 + end + + observer :player do + @step.even? ? :X : :O + end + + observer :current do + @history[@step] + end + + state_reader :history + + WINNING_COMBOS = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]] + + def current_winner? + WINNING_COMBOS.each do |a, b, c| + return current[a] if current[a] && current[a] == current[b] && current[a] == current[c] + end + false + end + + mutator :handle_click! do |id| + board = history[@step] + return if current_winner? || board[id] + + board = board.dup + board[id] = player + @history = history[0..@step] + [board] + @step += 1 + end + + mutator(:jump_to!) { |step| @step = step } + end + end + + class DisplayBoard < HyperComponent + param :board + + def draw_square(id) + BUTTON(class: :square, id: id) { board[id] } + .on(:click) { Game.handle_click!(id) } + end + + render(DIV) do + (0..6).step(3) do |row| + DIV(class: :board_row) do + (row..row + 2).each { |id| draw_square(id) } + end + end + end + end + + class DisplayGame < HyperComponent + def moves + return unless Game.history.length > 1 + + Game.history.length.times do |move| + LI(key: move) { move.zero? ? "Go to game start" : "Go to move ##{move}" } + .on(:click) { Game.jump_to!(move) } + end + end + + def status + if (winner = Game.current_winner?) + "Winner: #{winner}" + else + "Next player: #{Game.player}" + end + end + + render(DIV, class: :game) do + DIV(class: :game_board) do + DisplayBoard(board: Game.current) + end + DIV(class: :game_info) do + DIV { status } + OL { moves } + end + end + end + end + run_the_spec([]) + end +end diff --git a/docs/specs/spec/spec_helper.rb b/docs/specs/spec/spec_helper.rb new file mode 100644 index 000000000..6352f473e --- /dev/null +++ b/docs/specs/spec/spec_helper.rb @@ -0,0 +1,8 @@ +# spec_helper.rb +require 'hyper-spec' +require 'pry' # optional + +ENV["RAILS_ENV"] ||= 'test' +require File.expand_path('../../../specs/config/environment', __FILE__) + +require 'rspec/rails' diff --git a/docs/specs/storage/.keep b/docs/specs/storage/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/tmp/.keep b/docs/specs/tmp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/vendor/.keep b/docs/specs/vendor/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/yarn.lock b/docs/specs/yarn.lock new file mode 100644 index 000000000..f8bc4d3e5 --- /dev/null +++ b/docs/specs/yarn.lock @@ -0,0 +1,8174 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" + integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== + dependencies: + "@babel/highlight" "^7.12.13" + +"@babel/compat-data@^7.13.0", "@babel/compat-data@^7.13.12", "@babel/compat-data@^7.13.8": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.12.tgz#a8a5ccac19c200f9dd49624cac6e19d7be1236a1" + integrity sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ== + +"@babel/core@^7.11.1": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.10.tgz#07de050bbd8193fcd8a3c27918c0890613a94559" + integrity sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.9" + "@babel/helper-compilation-targets" "^7.13.10" + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helpers" "^7.13.10" + "@babel/parser" "^7.13.10" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + lodash "^4.17.19" + semver "^6.3.0" + source-map "^0.5.0" + +"@babel/generator@^7.13.0", "@babel/generator@^7.13.9": + version "7.13.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39" + integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw== + dependencies: + "@babel/types" "^7.13.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab" + integrity sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz#6bc20361c88b0a74d05137a65cac8d3cbf6f61fc" + integrity sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.10", "@babel/helper-compilation-targets@^7.13.8": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.10.tgz#1310a1678cb8427c07a753750da4f8ce442bdd0c" + integrity sha512-/Xju7Qg1GQO4mHZ/Kcs6Au7gfafgZnwm+a7sy/ow/tV1sHeraRUHbjdat8/UvDor4Tez+siGKDk6zIKtCPKVJA== + dependencies: + "@babel/compat-data" "^7.13.8" + "@babel/helper-validator-option" "^7.12.17" + browserslist "^4.14.5" + semver "^6.3.0" + +"@babel/helper-create-class-features-plugin@^7.13.0": + version "7.13.11" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz#30d30a005bca2c953f5653fc25091a492177f4f6" + integrity sha512-ays0I7XYq9xbjCSvT+EvysLgfc3tOkwCULHjrnscGT3A9qD4sk3wXnJ3of0MAWsWGjdinFvajHU2smYuqXKMrw== + dependencies: + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-member-expression-to-functions" "^7.13.0" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/helper-replace-supers" "^7.13.0" + "@babel/helper-split-export-declaration" "^7.12.13" + +"@babel/helper-create-regexp-features-plugin@^7.12.13": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz#a2ac87e9e319269ac655b8d4415e94d38d663cb7" + integrity sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + regexpu-core "^4.7.1" + +"@babel/helper-define-polyfill-provider@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.5.tgz#3c2f91b7971b9fc11fe779c945c014065dea340e" + integrity sha512-nXuzCSwlJ/WKr8qxzW816gwyT6VZgiJG17zR40fou70yfAcqjoNyTLl/DQ+FExw5Hx5KNqshmN8Ldl/r2N7cTg== + dependencies: + "@babel/helper-compilation-targets" "^7.13.0" + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/traverse" "^7.13.0" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-explode-assignable-expression@^7.12.13": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz#17b5c59ff473d9f956f40ef570cf3a76ca12657f" + integrity sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA== + dependencies: + "@babel/types" "^7.13.0" + +"@babel/helper-function-name@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a" + integrity sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA== + dependencies: + "@babel/helper-get-function-arity" "^7.12.13" + "@babel/template" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/helper-get-function-arity@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583" + integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-hoist-variables@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.0.tgz#5d5882e855b5c5eda91e0cadc26c6e7a2c8593d8" + integrity sha512-0kBzvXiIKfsCA0y6cFEIJf4OdzfpRuNk4+YTeHZpGGc666SATFKTz6sRncwFnQk7/ugJ4dSrCj6iJuvW4Qwr2g== + dependencies: + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + +"@babel/helper-member-expression-to-functions@^7.13.0", "@babel/helper-member-expression-to-functions@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72" + integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977" + integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-module-transforms@^7.13.0": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.13.12.tgz#600e58350490828d82282631a1422268e982ba96" + integrity sha512-7zVQqMO3V+K4JOOj40kxiCrMf6xlQAkewBB0eu2b03OO/Q21ZutOzjpfD79A5gtE/2OWi1nv625MrDlGlkbknQ== + dependencies: + "@babel/helper-module-imports" "^7.13.12" + "@babel/helper-replace-supers" "^7.13.12" + "@babel/helper-simple-access" "^7.13.12" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/helper-validator-identifier" "^7.12.11" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.12" + +"@babel/helper-optimise-call-expression@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea" + integrity sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" + integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ== + +"@babel/helper-remap-async-to-generator@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz#376a760d9f7b4b2077a9dd05aa9c3927cadb2209" + integrity sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-wrap-function" "^7.13.0" + "@babel/types" "^7.13.0" + +"@babel/helper-replace-supers@^7.12.13", "@babel/helper-replace-supers@^7.13.0", "@babel/helper-replace-supers@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz#6442f4c1ad912502481a564a7386de0c77ff3804" + integrity sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.13.12" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.12" + +"@babel/helper-simple-access@^7.12.13", "@babel/helper-simple-access@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6" + integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-skip-transparent-expression-wrappers@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf" + integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA== + dependencies: + "@babel/types" "^7.12.1" + +"@babel/helper-split-export-declaration@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05" + integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-validator-identifier@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" + integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== + +"@babel/helper-validator-option@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831" + integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw== + +"@babel/helper-wrap-function@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz#bdb5c66fda8526ec235ab894ad53a1235c79fcc4" + integrity sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA== + dependencies: + "@babel/helper-function-name" "^7.12.13" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + +"@babel/helpers@^7.13.10": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.10.tgz#fd8e2ba7488533cdeac45cc158e9ebca5e3c7df8" + integrity sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ== + dependencies: + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + +"@babel/highlight@^7.12.13": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1" + integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.12.13", "@babel/parser@^7.13.0", "@babel/parser@^7.13.10": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.12.tgz#ba320059420774394d3b0c0233ba40e4250b81d1" + integrity sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw== + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz#a3484d84d0b549f3fc916b99ee4783f26fabad2a" + integrity sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/plugin-proposal-optional-chaining" "^7.13.12" + +"@babel/plugin-proposal-async-generator-functions@^7.13.8": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.8.tgz#87aacb574b3bc4b5603f6fe41458d72a5a2ec4b1" + integrity sha512-rPBnhj+WgoSmgq+4gQUtXx/vOcU+UYtjy1AA/aeD61Hwj410fwYyqfUcRP3lR8ucgliVJL/G7sXcNUecC75IXA== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-remap-async-to-generator" "^7.13.0" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-proposal-class-properties@^7.10.4", "@babel/plugin-proposal-class-properties@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz#146376000b94efd001e57a40a88a525afaab9f37" + integrity sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-proposal-dynamic-import@^7.13.8": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz#876a1f6966e1dec332e8c9451afda3bebcdf2e1d" + integrity sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-proposal-export-namespace-from@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz#393be47a4acd03fa2af6e3cde9b06e33de1b446d" + integrity sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-proposal-json-strings@^7.13.8": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz#bf1fb362547075afda3634ed31571c5901afef7b" + integrity sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-proposal-logical-assignment-operators@^7.13.8": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz#93fa78d63857c40ce3c8c3315220fd00bfbb4e1a" + integrity sha512-aul6znYB4N4HGweImqKn59Su9RS8lbUIqxtXTOcAGtNIDczoEFv+l1EhmX8rUBp3G1jMjKJm8m0jXVp63ZpS4A== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz#3730a31dafd3c10d8ccd10648ed80a2ac5472ef3" + integrity sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-proposal-numeric-separator@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz#bd9da3188e787b5120b4f9d465a8261ce67ed1db" + integrity sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-object-rest-spread@^7.10.1", "@babel/plugin-proposal-object-rest-spread@^7.13.8": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz#5d210a4d727d6ce3b18f9de82cc99a3964eed60a" + integrity sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g== + dependencies: + "@babel/compat-data" "^7.13.8" + "@babel/helper-compilation-targets" "^7.13.8" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.13.0" + +"@babel/plugin-proposal-optional-catch-binding@^7.13.8": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz#3ad6bd5901506ea996fc31bdcf3ccfa2bed71107" + integrity sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.12.tgz#ba9feb601d422e0adea6760c2bd6bbb7bfec4866" + integrity sha512-fcEdKOkIB7Tf4IxrgEVeFC4zeJSTr78no9wTdBuZZbqF64kzllU0ybo2zrzm7gUQfxGhBgq4E39oRs8Zx/RMYQ== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-proposal-private-methods@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz#04bd4c6d40f6e6bbfa2f57e2d8094bad900ef787" + integrity sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-proposal-unicode-property-regex@^7.12.13", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz#bebde51339be829c17aaaaced18641deb62b39ba" + integrity sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz#c5f0fa6e249f5b739727f923540cf7a806130178" + integrity sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-arrow-functions@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz#10a59bebad52d637a027afa692e8d5ceff5e3dae" + integrity sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-async-to-generator@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz#8e112bf6771b82bf1e974e5e26806c5c99aa516f" + integrity sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg== + dependencies: + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-remap-async-to-generator" "^7.13.0" + +"@babel/plugin-transform-block-scoped-functions@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz#a9bf1836f2a39b4eb6cf09967739de29ea4bf4c4" + integrity sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-block-scoping@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz#f36e55076d06f41dfd78557ea039c1b581642e61" + integrity sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-classes@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz#0265155075c42918bf4d3a4053134176ad9b533b" + integrity sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-replace-supers" "^7.13.0" + "@babel/helper-split-export-declaration" "^7.12.13" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz#845c6e8b9bb55376b1fa0b92ef0bdc8ea06644ed" + integrity sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-destructuring@^7.10.1", "@babel/plugin-transform-destructuring@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz#c5dce270014d4e1ebb1d806116694c12b7028963" + integrity sha512-zym5em7tePoNT9s964c0/KU3JPPnuq7VhIxPRefJ4/s82cD+q1mgKfuGRDMCPL0HTyKz4dISuQlCusfgCJ86HA== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-dotall-regex@^7.12.13", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz#3f1601cc29905bfcb67f53910f197aeafebb25ad" + integrity sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-duplicate-keys@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz#6f06b87a8b803fd928e54b81c258f0a0033904de" + integrity sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-exponentiation-operator@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz#4d52390b9a273e651e4aba6aee49ef40e80cd0a1" + integrity sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-for-of@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz#c799f881a8091ac26b54867a845c3e97d2696062" + integrity sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-function-name@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz#bb024452f9aaed861d374c8e7a24252ce3a50051" + integrity sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ== + dependencies: + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-literals@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz#2ca45bafe4a820197cf315794a4d26560fe4bdb9" + integrity sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-member-expression-literals@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz#5ffa66cd59b9e191314c9f1f803b938e8c081e40" + integrity sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-modules-amd@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz#19f511d60e3d8753cc5a6d4e775d3a5184866cc3" + integrity sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ== + dependencies: + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-commonjs@^7.13.8": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz#7b01ad7c2dcf2275b06fa1781e00d13d420b3e1b" + integrity sha512-9QiOx4MEGglfYZ4XOnU79OHr6vIWUakIj9b4mioN8eQIoEh+pf5p/zEB36JpDFWA12nNMiRf7bfoRvl9Rn79Bw== + dependencies: + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-simple-access" "^7.12.13" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-systemjs@^7.13.8": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz#6d066ee2bff3c7b3d60bf28dec169ad993831ae3" + integrity sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A== + dependencies: + "@babel/helper-hoist-variables" "^7.13.0" + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-validator-identifier" "^7.12.11" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-umd@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz#8a3d96a97d199705b9fd021580082af81c06e70b" + integrity sha512-D/ILzAh6uyvkWjKKyFE/W0FzWwasv6vPTSqPcjxFqn6QpX3u8DjRVliq4F2BamO2Wee/om06Vyy+vPkNrd4wxw== + dependencies: + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz#2213725a5f5bbbe364b50c3ba5998c9599c5c9d9" + integrity sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + +"@babel/plugin-transform-new-target@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz#e22d8c3af24b150dd528cbd6e685e799bf1c351c" + integrity sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-object-super@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz#b4416a2d63b8f7be314f3d349bd55a9c1b5171f7" + integrity sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-replace-supers" "^7.12.13" + +"@babel/plugin-transform-parameters@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz#8fa7603e3097f9c0b7ca1a4821bc2fb52e9e5007" + integrity sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-property-literals@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz#4e6a9e37864d8f1b3bc0e2dce7bf8857db8b1a81" + integrity sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-regenerator@^7.10.1", "@babel/plugin-transform-regenerator@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz#b628bcc9c85260ac1aeb05b45bde25210194a2f5" + integrity sha512-lxb2ZAvSLyJ2PEe47hoGWPmW22v7CtSl9jW8mingV4H2sEX/JOcrAj2nPuGWi56ERUm2bUpjKzONAuT6HCn2EA== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz#7d9988d4f06e0fe697ea1d9803188aa18b472695" + integrity sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-runtime@^7.11.0": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.10.tgz#a1e40d22e2bf570c591c9c7e5ab42d6bf1e419e1" + integrity sha512-Y5k8ipgfvz5d/76tx7JYbKQTcgFSU6VgJ3kKQv4zGTKr+a9T/KBvfRvGtSFgKDQGt/DBykQixV0vNWKIdzWErA== + dependencies: + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + babel-plugin-polyfill-corejs2 "^0.1.4" + babel-plugin-polyfill-corejs3 "^0.1.3" + babel-plugin-polyfill-regenerator "^0.1.2" + semver "^6.3.0" + +"@babel/plugin-transform-shorthand-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz#db755732b70c539d504c6390d9ce90fe64aff7ad" + integrity sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-spread@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz#84887710e273c1815ace7ae459f6f42a5d31d5fd" + integrity sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + +"@babel/plugin-transform-sticky-regex@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz#760ffd936face73f860ae646fb86ee82f3d06d1f" + integrity sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-template-literals@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz#a36049127977ad94438dee7443598d1cefdf409d" + integrity sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-typeof-symbol@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz#785dd67a1f2ea579d9c2be722de8c84cb85f5a7f" + integrity sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-unicode-escapes@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz#840ced3b816d3b5127dd1d12dcedc5dead1a5e74" + integrity sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-unicode-regex@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz#b52521685804e155b1202e83fc188d34bb70f5ac" + integrity sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/preset-env@^7.11.0": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.13.12.tgz#6dff470478290582ac282fb77780eadf32480237" + integrity sha512-JzElc6jk3Ko6zuZgBtjOd01pf9yYDEIH8BcqVuYIuOkzOwDesoa/Nz4gIo4lBG6K861KTV9TvIgmFuT6ytOaAA== + dependencies: + "@babel/compat-data" "^7.13.12" + "@babel/helper-compilation-targets" "^7.13.10" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-validator-option" "^7.12.17" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.13.12" + "@babel/plugin-proposal-async-generator-functions" "^7.13.8" + "@babel/plugin-proposal-class-properties" "^7.13.0" + "@babel/plugin-proposal-dynamic-import" "^7.13.8" + "@babel/plugin-proposal-export-namespace-from" "^7.12.13" + "@babel/plugin-proposal-json-strings" "^7.13.8" + "@babel/plugin-proposal-logical-assignment-operators" "^7.13.8" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.13.8" + "@babel/plugin-proposal-numeric-separator" "^7.12.13" + "@babel/plugin-proposal-object-rest-spread" "^7.13.8" + "@babel/plugin-proposal-optional-catch-binding" "^7.13.8" + "@babel/plugin-proposal-optional-chaining" "^7.13.12" + "@babel/plugin-proposal-private-methods" "^7.13.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.12.13" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.12.13" + "@babel/plugin-transform-arrow-functions" "^7.13.0" + "@babel/plugin-transform-async-to-generator" "^7.13.0" + "@babel/plugin-transform-block-scoped-functions" "^7.12.13" + "@babel/plugin-transform-block-scoping" "^7.12.13" + "@babel/plugin-transform-classes" "^7.13.0" + "@babel/plugin-transform-computed-properties" "^7.13.0" + "@babel/plugin-transform-destructuring" "^7.13.0" + "@babel/plugin-transform-dotall-regex" "^7.12.13" + "@babel/plugin-transform-duplicate-keys" "^7.12.13" + "@babel/plugin-transform-exponentiation-operator" "^7.12.13" + "@babel/plugin-transform-for-of" "^7.13.0" + "@babel/plugin-transform-function-name" "^7.12.13" + "@babel/plugin-transform-literals" "^7.12.13" + "@babel/plugin-transform-member-expression-literals" "^7.12.13" + "@babel/plugin-transform-modules-amd" "^7.13.0" + "@babel/plugin-transform-modules-commonjs" "^7.13.8" + "@babel/plugin-transform-modules-systemjs" "^7.13.8" + "@babel/plugin-transform-modules-umd" "^7.13.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.13" + "@babel/plugin-transform-new-target" "^7.12.13" + "@babel/plugin-transform-object-super" "^7.12.13" + "@babel/plugin-transform-parameters" "^7.13.0" + "@babel/plugin-transform-property-literals" "^7.12.13" + "@babel/plugin-transform-regenerator" "^7.12.13" + "@babel/plugin-transform-reserved-words" "^7.12.13" + "@babel/plugin-transform-shorthand-properties" "^7.12.13" + "@babel/plugin-transform-spread" "^7.13.0" + "@babel/plugin-transform-sticky-regex" "^7.12.13" + "@babel/plugin-transform-template-literals" "^7.13.0" + "@babel/plugin-transform-typeof-symbol" "^7.12.13" + "@babel/plugin-transform-unicode-escapes" "^7.12.13" + "@babel/plugin-transform-unicode-regex" "^7.12.13" + "@babel/preset-modules" "^0.1.4" + "@babel/types" "^7.13.12" + babel-plugin-polyfill-corejs2 "^0.1.4" + babel-plugin-polyfill-corejs3 "^0.1.3" + babel-plugin-polyfill-regenerator "^0.1.2" + core-js-compat "^3.9.0" + semver "^6.3.0" + +"@babel/preset-modules@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e" + integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d" + integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" + integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/parser" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/traverse@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.0.tgz#6d95752475f86ee7ded06536de309a65fc8966cc" + integrity sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.0" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/parser" "^7.13.0" + "@babel/types" "^7.13.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.4.4": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.12.tgz#edbf99208ef48852acdff1c8a681a1e4ade580cd" + integrity sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@csstools/convert-colors@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" + integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== + +"@emotion/hash@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" + integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== + +"@fluentui/react-component-event-listener@~0.51.6": + version "0.51.7" + resolved "https://registry.yarnpkg.com/@fluentui/react-component-event-listener/-/react-component-event-listener-0.51.7.tgz#158adb970d8bc982c91c57fd1322a0036042d86e" + integrity sha512-NjVm+crN0T9A7vITL8alZeHnuV8zi2gos0nezU/2YOxaUAB9E4zKiPxt/6k5U50rJs/gj8Nu45iXxnjO41HbZg== + dependencies: + "@babel/runtime" "^7.10.4" + +"@fluentui/react-component-ref@~0.51.6": + version "0.51.7" + resolved "https://registry.yarnpkg.com/@fluentui/react-component-ref/-/react-component-ref-0.51.7.tgz#bfb0312e926c213bed35e53ee5105a68732eea99" + integrity sha512-CX27jVJYaFoBCWpuWAizQZ2se137ku1dmDyn8sw+ySNJa+kkQf7LnMydiPW5K7cRdUSqUJW3eS4EjKRvVAx8xA== + dependencies: + "@babel/runtime" "^7.10.4" + react-is "^16.6.3" + +"@material-ui/core@^4.11.3": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.11.3.tgz#f22e41775b0bd075e36a7a093d43951bf7f63850" + integrity sha512-Adt40rGW6Uds+cAyk3pVgcErpzU/qxc7KBR94jFHBYretU4AtWZltYcNsbeMn9tXL86jjVL1kuGcIHsgLgFGRw== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/styles" "^4.11.3" + "@material-ui/system" "^4.11.3" + "@material-ui/types" "^5.1.0" + "@material-ui/utils" "^4.11.2" + "@types/react-transition-group" "^4.2.0" + clsx "^1.0.4" + hoist-non-react-statics "^3.3.2" + popper.js "1.16.1-lts" + prop-types "^15.7.2" + react-is "^16.8.0 || ^17.0.0" + react-transition-group "^4.4.0" + +"@material-ui/styles@^4.11.3": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.3.tgz#1b8d97775a4a643b53478c895e3f2a464e8916f2" + integrity sha512-HzVzCG+PpgUGMUYEJ2rTEmQYeonGh41BYfILNFb/1ueqma+p1meSdu4RX6NjxYBMhf7k+jgfHFTTz+L1SXL/Zg== + dependencies: + "@babel/runtime" "^7.4.4" + "@emotion/hash" "^0.8.0" + "@material-ui/types" "^5.1.0" + "@material-ui/utils" "^4.11.2" + clsx "^1.0.4" + csstype "^2.5.2" + hoist-non-react-statics "^3.3.2" + jss "^10.5.1" + jss-plugin-camel-case "^10.5.1" + jss-plugin-default-unit "^10.5.1" + jss-plugin-global "^10.5.1" + jss-plugin-nested "^10.5.1" + jss-plugin-props-sort "^10.5.1" + jss-plugin-rule-value-function "^10.5.1" + jss-plugin-vendor-prefixer "^10.5.1" + prop-types "^15.7.2" + +"@material-ui/system@^4.11.3": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.11.3.tgz#466bc14c9986798fd325665927c963eb47cc4143" + integrity sha512-SY7otguNGol41Mu2Sg6KbBP1ZRFIbFLHGK81y4KYbsV2yIcaEPOmsCK6zwWlp+2yTV3J/VwT6oSBARtGIVdXPw== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/utils" "^4.11.2" + csstype "^2.5.2" + prop-types "^15.7.2" + +"@material-ui/types@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2" + integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A== + +"@material-ui/utils@^4.11.2": + version "4.11.2" + resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.11.2.tgz#f1aefa7e7dff2ebcb97d31de51aecab1bb57540a" + integrity sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA== + dependencies: + "@babel/runtime" "^7.4.4" + prop-types "^15.7.2" + react-is "^16.8.0 || ^17.0.0" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@popperjs/core@^2.6.0": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.1.tgz#7f554e7368c9ab679a11f4a042ca17149d70cf12" + integrity sha512-DvJbbn3dUgMxDnJLH+RZQPnXak1h4ZVYQ7CWiFWjQwBFkVajT4rfw2PdpHLTSTwxrYfnoEXkuBiwkDm6tPMQeA== + +"@rails/actioncable@^6.0.0": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.3.tgz#c8a67ec4d22ecd6931f7ebd98143fddbc815419a" + integrity sha512-m02524MR9cTnUNfGz39Lkx9jVvuL0tle4O7YgvouJ7H83FILxzG1nQ5jw8pAjLAr9XQGu+P1sY4SKE3zyhCNjw== + +"@rails/activestorage@^6.0.0": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@rails/activestorage/-/activestorage-6.1.3.tgz#d76ce0fe59b5778e05b967c22c61b2964fde112a" + integrity sha512-9it2rc+79E+GP4GWX/P2M9/INcp1SicJaiF5yqj+k40x/JY+/eVdSAGfRCgq2G/FwkUdGlaKnG2OoDcXchczlw== + dependencies: + spark-md5 "^3.0.0" + +"@rails/ujs@^6.0.0": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.3.tgz#90ef26caa0925492b1a3b1495db09cfbe49e745e" + integrity sha512-9mip5o+LVouWAqLMNJWhxda+D5uP+4RziNECgOGJlL6k3rc5SC/ljCHpV9Cym4i3oeGZkpZJ2tu4frCwt84kzQ== + +"@rails/webpacker@5.2.1": + version "5.2.1" + resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.2.1.tgz#87cdbd4af2090ae2d74bdc51f6f04717d907c5b3" + integrity sha512-rO0kOv0o4ESB8ZnKX+b54ZKogNJGWSMULGmsJacREfm9SahKEQwXBeHNsqSGtS9NAPsU6YUFhGKRd4i/kbMNrQ== + dependencies: + "@babel/core" "^7.11.1" + "@babel/plugin-proposal-class-properties" "^7.10.4" + "@babel/plugin-proposal-object-rest-spread" "^7.10.1" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-destructuring" "^7.10.1" + "@babel/plugin-transform-regenerator" "^7.10.1" + "@babel/plugin-transform-runtime" "^7.11.0" + "@babel/preset-env" "^7.11.0" + "@babel/runtime" "^7.11.2" + babel-loader "^8.1.0" + babel-plugin-dynamic-import-node "^2.3.3" + babel-plugin-macros "^2.8.0" + case-sensitive-paths-webpack-plugin "^2.3.0" + compression-webpack-plugin "^4.0.0" + core-js "^3.6.5" + css-loader "^3.5.3" + file-loader "^6.0.0" + flatted "^3.0.4" + glob "^7.1.6" + js-yaml "^3.14.0" + mini-css-extract-plugin "^0.9.0" + node-sass "^4.14.1" + optimize-css-assets-webpack-plugin "^5.0.3" + path-complete-extname "^1.0.0" + pnp-webpack-plugin "^1.6.4" + postcss-flexbugs-fixes "^4.2.1" + postcss-import "^12.0.1" + postcss-loader "^3.0.0" + postcss-preset-env "^6.7.0" + postcss-safe-parser "^4.0.2" + regenerator-runtime "^0.13.7" + sass-loader "^8.0.2" + style-loader "^1.2.1" + terser-webpack-plugin "^4.0.0" + webpack "^4.44.1" + webpack-assets-manifest "^3.1.1" + webpack-cli "^3.3.12" + webpack-sources "^1.4.3" + +"@semantic-ui-react/event-stack@^3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@semantic-ui-react/event-stack/-/event-stack-3.1.2.tgz#14fac9796695aa3967962d94ea9733a85325f9c4" + integrity sha512-Yd0Qf7lPCIjzJ9bZYfurlNu2RDXT6KKSyubHfYK3WjRauhxCsq6Fk2LMRI9DEvShoEU+AsLSv3NGkqXAcVp0zg== + dependencies: + exenv "^1.2.2" + prop-types "^15.6.2" + +"@types/glob@^7.1.1": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" + integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== + +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + +"@types/node@*": + version "14.14.35" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313" + integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + +"@types/q@^1.5.1": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" + integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== + +"@types/react-transition-group@^4.2.0": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.1.tgz#e1a3cb278df7f47f17b5082b1b3da17170bd44b1" + integrity sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ== + dependencies: + "@types/react" "*" + +"@types/react@*": + version "17.0.3" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79" + integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" + integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== + +"@webassemblyjs/ast@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" + integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== + dependencies: + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + +"@webassemblyjs/floating-point-hex-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" + integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== + +"@webassemblyjs/helper-api-error@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" + integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== + +"@webassemblyjs/helper-buffer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" + integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== + +"@webassemblyjs/helper-code-frame@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" + integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== + dependencies: + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/helper-fsm@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" + integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== + +"@webassemblyjs/helper-module-context@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" + integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== + dependencies: + "@webassemblyjs/ast" "1.9.0" + +"@webassemblyjs/helper-wasm-bytecode@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" + integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== + +"@webassemblyjs/helper-wasm-section@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" + integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + +"@webassemblyjs/ieee754@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" + integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" + integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" + integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== + +"@webassemblyjs/wasm-edit@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" + integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/helper-wasm-section" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-opt" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/wasm-gen@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" + integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wasm-opt@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" + integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + +"@webassemblyjs/wasm-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" + integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wast-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" + integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-code-frame" "1.9.0" + "@webassemblyjs/helper-fsm" "1.9.0" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/wast-printer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" + integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +acorn@^6.4.1: + version "6.4.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv-errors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== + +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +alphanum-sort@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= + +ansi-colors@^3.0.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" + integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== + +ansi-html@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" + integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +aproba@^1.0.3, aproba@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +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" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +array-flatten@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= + 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" + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +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" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assert@^1.1.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + 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" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +async-foreach@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" + integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +async@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autoprefixer@^9.6.1: + version "9.8.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" + integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== + dependencies: + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + colorette "^1.2.1" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + +babel-loader@^8.1.0: + version "8.2.2" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81" + integrity sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g== + dependencies: + find-cache-dir "^3.3.1" + loader-utils "^1.4.0" + make-dir "^3.1.0" + schema-utils "^2.6.5" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-macros@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" + integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== + dependencies: + "@babel/runtime" "^7.7.2" + cosmiconfig "^6.0.0" + resolve "^1.12.0" + +babel-plugin-polyfill-corejs2@^0.1.4: + version "0.1.10" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.1.10.tgz#a2c5c245f56c0cac3dbddbf0726a46b24f0f81d1" + integrity sha512-DO95wD4g0A8KRaHKi0D51NdGXzvpqVLnLu5BTvDlpqUEpTmeEtypgC1xqesORaWmiUOQI14UHKlzNd9iZ2G3ZA== + dependencies: + "@babel/compat-data" "^7.13.0" + "@babel/helper-define-polyfill-provider" "^0.1.5" + semver "^6.1.1" + +babel-plugin-polyfill-corejs3@^0.1.3: + version "0.1.7" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.7.tgz#80449d9d6f2274912e05d9e182b54816904befd0" + integrity sha512-u+gbS9bbPhZWEeyy1oR/YaaSpod/KDT07arZHb80aTpl8H5ZBq+uN1nN9/xtX7jQyfLdPfoqI4Rue/MQSWJquw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.1.5" + core-js-compat "^3.8.1" + +babel-plugin-polyfill-regenerator@^0.1.2: + version "0.1.6" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.1.6.tgz#0fe06a026fe0faa628ccc8ba3302da0a6ce02f3f" + integrity sha512-OUrYG9iKPKz8NxswXbRAdSwF0GhRdIEMTloQATJi4bDuFqrXaXcCUT/VGNrr8pBcjMh1RxZ7Xt9cytVJTJfvMg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.1.5" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.0.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + 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" + integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= + dependencies: + inherits "~2.0.0" + +bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.0.0, bn.js@^5.1.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== + +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= + 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" + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + 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" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +brorand@^1.0.1, brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + 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.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== + dependencies: + bn.js "^5.0.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + dependencies: + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.3" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.3, browserslist@^4.6.4: + version "4.16.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" + integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== + dependencies: + caniuse-lite "^1.0.30001181" + colorette "^1.2.1" + electron-to-chromium "^1.3.649" + escalade "^3.1.1" + node-releases "^1.1.70" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + +buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +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" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +cacache@^12.0.2: + version "12.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== + dependencies: + bluebird "^3.5.5" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.4" + graceful-fs "^4.1.15" + infer-owner "^1.0.3" + lru-cache "^5.1.1" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.3" + ssri "^6.0.1" + unique-filename "^1.1.1" + y18n "^4.0.0" + +cacache@^15.0.5: + version "15.0.6" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.6.tgz#65a8c580fda15b59150fb76bf3f3a8e45d583099" + integrity sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w== + dependencies: + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + 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" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001181: + version "1.0.30001204" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001204.tgz#256c85709a348ec4d175e847a3b515c66e79f2aa" + integrity sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ== + +case-sensitive-paths-webpack-plugin@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" + integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + 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, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chokidar@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +chokidar@^3.4.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" + integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.5.0" + optionalDependencies: + fsevents "~2.3.1" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== + dependencies: + tslib "^1.9.0" + +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" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clsx@^1.0.4, clsx@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" + integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== + +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + 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" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0, color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.4: + version "1.5.5" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" + integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" + integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.4" + +colorette@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression-webpack-plugin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-4.0.1.tgz#33eda97f1170dd38c5556771de10f34245aa0274" + integrity sha512-0mg6PgwTsUe5LEcUrOu3ob32vraDx2VdbMGAT1PARcOV+UJWDYZFdkSo6RbHoGQ061mmmkC7XpRKOlvwm/gzJQ== + dependencies: + cacache "^15.0.5" + find-cache-dir "^3.3.1" + schema-utils "^2.7.0" + serialize-javascript "^4.0.0" + webpack-sources "^1.4.3" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + 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" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@^1.5.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +connect-history-api-fallback@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== + +console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + +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" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +copy-concurrently@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" + integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== + 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" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-js-compat@^3.8.1, core-js-compat@^3.9.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.9.1.tgz#4e572acfe90aff69d76d8c37759d21a5c59bb455" + integrity sha512-jXAirMQxrkbiiLsCx9bQPJFA6llDadKMpYrBJQJ3/c4/vsPP/fAf29h24tviRlvwUL6AmY5CHLu2GvjuYviQqA== + dependencies: + browserslist "^4.16.3" + semver "7.0.0" + +core-js@^3.6.5: + version "3.9.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.9.1.tgz#cec8de593db8eb2a85ffb0dbdeb312cb6e5460ae" + integrity sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg== + +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" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + +create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + 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" + +create-react-class@^15.7.0: + version "15.7.0" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.7.0.tgz#7499d7ca2e69bb51d13faf59bd04f0c65a1d6c1e" + integrity sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng== + dependencies: + loose-envify "^1.3.1" + object-assign "^4.1.1" + +cross-spawn@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" + integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI= + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + +cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + 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-blank-pseudo@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" + integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== + dependencies: + postcss "^7.0.5" + +css-color-names@0.0.4, 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" + integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= + +css-declaration-sorter@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" + integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== + dependencies: + postcss "^7.0.1" + timsort "^0.3.0" + +css-has-pseudo@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" + integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^5.0.0-rc.4" + +css-loader@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" + integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ== + dependencies: + camelcase "^5.3.1" + cssesc "^3.0.0" + icss-utils "^4.1.1" + loader-utils "^1.2.3" + normalize-path "^3.0.0" + postcss "^7.0.32" + postcss-modules-extract-imports "^2.0.0" + postcss-modules-local-by-default "^3.0.2" + postcss-modules-scope "^2.2.0" + postcss-modules-values "^3.0.0" + postcss-value-parser "^4.1.0" + schema-utils "^2.7.0" + semver "^6.3.0" + +css-prefers-color-scheme@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" + integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== + dependencies: + postcss "^7.0.5" + +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + +css-select@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + +css-tree@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.2.tgz#9ae393b5dafd7dae8a622475caec78d3d8fbd7b5" + integrity sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-vendor@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d" + integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ== + dependencies: + "@babel/runtime" "^7.8.3" + is-in-browser "^1.0.2" + +css-what@^3.2.1: + version "3.4.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" + integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== + +cssdb@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" + integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== + +cssesc@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" + integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" + integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== + dependencies: + css-declaration-sorter "^4.0.1" + cssnano-util-raw-cache "^4.0.1" + postcss "^7.0.0" + postcss-calc "^7.0.1" + postcss-colormin "^4.0.3" + postcss-convert-values "^4.0.1" + postcss-discard-comments "^4.0.2" + postcss-discard-duplicates "^4.0.2" + postcss-discard-empty "^4.0.1" + postcss-discard-overridden "^4.0.1" + postcss-merge-longhand "^4.0.11" + postcss-merge-rules "^4.0.3" + postcss-minify-font-values "^4.0.2" + postcss-minify-gradients "^4.0.2" + postcss-minify-params "^4.0.2" + postcss-minify-selectors "^4.0.2" + postcss-normalize-charset "^4.0.1" + postcss-normalize-display-values "^4.0.2" + postcss-normalize-positions "^4.0.2" + postcss-normalize-repeat-style "^4.0.2" + postcss-normalize-string "^4.0.2" + postcss-normalize-timing-functions "^4.0.2" + postcss-normalize-unicode "^4.0.1" + postcss-normalize-url "^4.0.1" + postcss-normalize-whitespace "^4.0.2" + postcss-ordered-values "^4.1.2" + postcss-reduce-initial "^4.0.3" + postcss-reduce-transforms "^4.0.2" + postcss-svgo "^4.0.2" + postcss-unique-selectors "^4.0.1" + +cssnano-util-get-arguments@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" + integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= + +cssnano-util-get-match@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" + integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= + +cssnano-util-raw-cache@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" + integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== + dependencies: + postcss "^7.0.0" + +cssnano-util-same-parent@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" + integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== + +cssnano@^4.1.10: + version "4.1.10" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" + integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== + dependencies: + cosmiconfig "^5.0.0" + cssnano-preset-default "^4.0.7" + is-resolvable "^1.0.0" + postcss "^7.0.0" + +csso@^4.0.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + +csstype@^2.5.2: + version "2.6.16" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.16.tgz#544d69f547013b85a40d15bff75db38f34fe9c39" + integrity sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q== + +csstype@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b" + integrity sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g== + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= + dependencies: + array-find-index "^1.0.1" + +cyclist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" + integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.1.1, debug@^3.2.6: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +decamelize@^1.1.2, decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +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" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-equal@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + +default-gateway@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" + integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== + dependencies: + execa "^1.0.0" + ip-regex "^2.1.0" + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + 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" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +del@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" + integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== + dependencies: + "@types/glob" "^7.1.1" + globby "^6.1.0" + is-path-cwd "^2.0.0" + is-path-in-cwd "^2.0.0" + p-map "^2.0.0" + pify "^4.0.1" + rimraf "^2.6.3" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + 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" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" + integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= + +detect-node@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79" + integrity sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw== + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + 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" + integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= + +dns-packet@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" + integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== + 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" + integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= + dependencies: + buffer-indexof "^1.0.0" + +dom-helpers@^5.0.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" + integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.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" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + +domelementtype@1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" + integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== + +domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +duplexify@^3.4.2, duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + 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.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.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" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +electron-to-chromium@^1.3.649: + version "1.3.698" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.698.tgz#5de813960f23581a268718a0058683dffa15d221" + integrity sha512-VEXDzYblnlT+g8Q3gedwzgKOso1evkeJzV8lih7lV8mL8eAnGVnKyC3KsFT6S+R5PQO4ffdr1PI16/ElibY/kQ== + +elliptic@^6.5.3: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enhanced-resolve@^4.1.1, enhanced-resolve@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +errno@^0.1.3, errno@~0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.2, es-abstract@^1.18.0-next.2: + version "1.18.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" + integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.2" + is-string "^1.0.5" + object-inspect "^1.9.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +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" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esrecurse@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +eventsource@^1.0.7: + version "1.1.0" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.0.tgz#00e8ca7c92109e94b0ddf32dac677d841028cfaf" + integrity sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg== + dependencies: + original "^1.0.0" + +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" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.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" + +exenv@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + 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-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= + dependencies: + homedir-polyfill "^1.0.1" + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + 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" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + 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" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +faye-websocket@^0.11.3: + version "0.11.3" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" + integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== + dependencies: + websocket-driver ">=0.5.1" + +figgy-pudding@^3.5.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== + +file-loader@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-cache-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +findup-sync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + +flatted@^3.0.4: + version "3.1.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" + integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== + +flatten@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" + integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== + +flush-write-stream@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + +follow-redirects@^1.0.0: + version "1.13.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" + integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + 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" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + 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" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +from2@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +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" + integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= + 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" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.13" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +fsevents@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +fstream@^1.0.0, fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + 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.3" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" + integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== + dependencies: + globule "^1.0.0" + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +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" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.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" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + 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" + +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= + 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.3.2" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.2.tgz#d8bdd9e9e4eef8f96e245999a5dee7eb5d8529c4" + integrity sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA== + dependencies: + glob "~7.1.1" + lodash "~4.17.10" + minimatch "~3.0.2" + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + 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" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-bigints@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + 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" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + 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" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.0, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +homedir-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" + integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= + +hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" + integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= + +html-comment-regex@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" + integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== + +html-entities@^1.3.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc" + integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-parser-js@>=0.5.1: + version "0.5.3" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" + integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== + +http-proxy-middleware@0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" + integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== + dependencies: + http-proxy "^1.17.0" + is-glob "^4.0.0" + lodash "^4.17.11" + micromatch "^3.1.10" + +http-proxy@^1.17.0: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + 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" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + +hyphenate-style-name@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" + integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-utils@^4.0.0, icss-utils@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" + integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== + dependencies: + postcss "^7.0.14" + +ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +iferr@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" + integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= + +import-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" + integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= + dependencies: + import-from "^2.1.0" + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +import-fresh@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-from@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" + integrity sha1-M1238qev/VOqpHHUuAId7ja387E= + dependencies: + resolve-from "^3.0.0" + +import-local@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== + dependencies: + pkg-dir "^3.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" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +in-publish@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.1.tgz#948b1a535c8030561cea522f73f78f4be357e00c" + integrity sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ== + +indefinite-observable@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/indefinite-observable/-/indefinite-observable-2.0.1.tgz#574af29bfbc17eb5947793797bddc94c9d859400" + integrity sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ== + dependencies: + symbol-observable "1.2.0" + +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= + dependencies: + repeating "^2.0.0" + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + +infer-owner@^1.0.3, infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@^1.3.4, ini@^1.3.5: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +internal-ip@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" + integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== + dependencies: + default-gateway "^4.2.0" + ipaddr.js "^1.9.0" + +interpret@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + +ip@^1.1.0, ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + +ipaddr.js@1.9.1, ipaddr.js@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +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" + integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= + +is-absolute-url@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + +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" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + 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" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arguments@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" + integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== + dependencies: + call-bind "^1.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" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-bigint@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2" + integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg== + +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" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0" + integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA== + dependencies: + call-bind "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== + +is-color-stop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" + integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= + dependencies: + css-color-names "^0.0.4" + hex-color-regex "^1.1.0" + hsl-regex "^1.0.0" + hsla-regex "^1.0.0" + rgb-regex "^1.0.1" + rgba-regex "^1.0.0" + +is-core-module@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + +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" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + 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" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + 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" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + +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" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +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" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-finite@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" + integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== + +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" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + 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" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-in-browser@^1.0.2, is-in-browser@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" + integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= + +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-number-object@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" + integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-cwd@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-in-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" + integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== + dependencies: + is-path-inside "^2.1.0" + +is-path-inside@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" + integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== + dependencies: + path-is-inside "^1.0.2" + +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" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + +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" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-regex@^1.0.4, is-regex@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" + integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== + dependencies: + call-bind "^1.0.2" + has-symbols "^1.0.1" + +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-svg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" + integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +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" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + 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" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +jest-worker@^26.5.0: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jquery@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" + integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== + +js-base64@^2.1.8: + version "2.6.4" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" + integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1, js-yaml@^3.14.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +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" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json3@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" + integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +jss-plugin-camel-case@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.6.0.tgz#93d2cd704bf0c4af70cc40fb52d74b8a2554b170" + integrity sha512-JdLpA3aI/npwj3nDMKk308pvnhoSzkW3PXlbgHAzfx0yHWnPPVUjPhXFtLJzgKZge8lsfkUxvYSQ3X2OYIFU6A== + dependencies: + "@babel/runtime" "^7.3.1" + hyphenate-style-name "^1.0.3" + jss "10.6.0" + +jss-plugin-default-unit@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.6.0.tgz#af47972486819b375f0f3a9e0213403a84b5ef3b" + integrity sha512-7y4cAScMHAxvslBK2JRK37ES9UT0YfTIXWgzUWD5euvR+JR3q+o8sQKzBw7GmkQRfZijrRJKNTiSt1PBsLI9/w== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.6.0" + +jss-plugin-global@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.6.0.tgz#3e8011f760f399cbadcca7f10a485b729c50e3ed" + integrity sha512-I3w7ji/UXPi3VuWrTCbHG9rVCgB4yoBQLehGDTmsnDfXQb3r1l3WIdcO8JFp9m0YMmyy2CU7UOV6oPI7/Tmu+w== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.6.0" + +jss-plugin-nested@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.6.0.tgz#5f83c5c337d3b38004834e8426957715a0251641" + integrity sha512-fOFQWgd98H89E6aJSNkEh2fAXquC9aZcAVjSw4q4RoQ9gU++emg18encR4AT4OOIFl4lQwt5nEyBBRn9V1Rk8g== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.6.0" + tiny-warning "^1.0.2" + +jss-plugin-props-sort@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.6.0.tgz#297879f35f9fe21196448579fee37bcde28ce6bc" + integrity sha512-oMCe7hgho2FllNc60d9VAfdtMrZPo9n1Iu6RNa+3p9n0Bkvnv/XX5San8fTPujrTBScPqv9mOE0nWVvIaohNuw== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.6.0" + +jss-plugin-rule-value-function@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.6.0.tgz#3c1a557236a139d0151e70a82c810ccce1c1c5ea" + integrity sha512-TKFqhRTDHN1QrPTMYRlIQUOC2FFQb271+AbnetURKlGvRl/eWLswcgHQajwuxI464uZk91sPiTtdGi7r7XaWfA== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.6.0" + tiny-warning "^1.0.2" + +jss-plugin-vendor-prefixer@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.6.0.tgz#e1fcd499352846890c38085b11dbd7aa1c4f2c78" + integrity sha512-doJ7MouBXT1lypLLctCwb4nJ6lDYqrTfVS3LtXgox42Xz0gXusXIIDboeh6UwnSmox90QpVnub7au8ybrb0krQ== + dependencies: + "@babel/runtime" "^7.3.1" + css-vendor "^2.0.8" + jss "10.6.0" + +jss@10.6.0, jss@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss/-/jss-10.6.0.tgz#d92ff9d0f214f65ca1718591b68e107be4774149" + integrity sha512-n7SHdCozmxnzYGXBHe0NsO0eUf9TvsHVq2MXvi4JmTn3x5raynodDVE/9VQmBdWFyyj9HpHZ2B4xNZ7MMy7lkw== + dependencies: + "@babel/runtime" "^7.3.1" + csstype "^3.0.2" + indefinite-observable "^2.0.1" + is-in-browser "^1.1.3" + tiny-warning "^1.0.2" + +keyboard-key@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/keyboard-key/-/keyboard-key-1.1.0.tgz#6f2e8e37fa11475bb1f1d65d5174f1b35653f5b7" + integrity sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ== + +killable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" + integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + 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" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +last-call-webpack-plugin@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" + integrity sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w== + dependencies: + lodash "^4.17.5" + webpack-sources "^1.1.0" + +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + +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" + integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= + 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" + +loader-runner@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" + integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== + +loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +loader-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash-es@^4.17.15: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + +lodash.get@^4.0: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.has@^4.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" + integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.template@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== + 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" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.5, lodash@~4.17.10: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loglevel@^1.6.8: + version "1.7.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" + integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.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" + integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +lru-cache@^4.0.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^3.0.2, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.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" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +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" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +memory-fs@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +meow@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= + 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" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mime-db@1.46.0, "mime-db@>= 1.43.0 < 2": + version "1.46.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" + integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== + +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: + version "2.1.29" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" + integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== + dependencies: + mime-db "1.46.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@^2.4.4: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== + +mini-create-react-context@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e" + integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ== + dependencies: + "@babel/runtime" "^7.12.1" + tiny-warning "^1.0.3" + +mini-css-extract-plugin@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e" + integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A== + dependencies: + loader-utils "^1.1.0" + normalize-url "1.9.1" + schema-utils "^1.0.0" + webpack-sources "^1.1.0" + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +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" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + +minimatch@^3.0.4, minimatch@~3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== + 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 "^3.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +"mkdirp@>=0.5 0", mkdirp@^0.5, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +move-concurrently@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" + integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= + 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" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +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" + integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= + +multicast-dns@^6.0.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" + integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== + dependencies: + dns-packet "^1.3.1" + thunky "^1.0.2" + +nan@^2.12.1, nan@^2.13.2: + version "2.14.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" + integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +neo-async@^2.5.0, neo-async@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== + +node-gyp@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" + integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA== + dependencies: + fstream "^1.0.0" + glob "^7.0.3" + graceful-fs "^4.1.2" + mkdirp "^0.5.0" + nopt "2 || 3" + npmlog "0 || 1 || 2 || 3 || 4" + osenv "0" + request "^2.87.0" + rimraf "2" + semver "~5.3.0" + tar "^2.0.0" + which "1" + +node-libs-browser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + 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 "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + 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.11.0" + vm-browserify "^1.0.1" + +node-releases@^1.1.70: + version "1.1.71" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" + integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== + +node-sass@^4.14.1: + version "4.14.1" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.1.tgz#99c87ec2efb7047ed638fb4c9db7f3a42e2217b5" + integrity sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g== + 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 "^4.17.15" + meow "^3.7.0" + mkdirp "^0.5.1" + nan "^2.13.2" + node-gyp "^3.8.0" + npmlog "^4.0.0" + request "^2.88.0" + sass-graph "2.2.5" + 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" + integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= + dependencies: + abbrev "1" + +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.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" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-url@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + +normalize-url@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" + integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== + +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" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +nth-check@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +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" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +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" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" + integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== + +object-is@^1.0.1: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.getownpropertydescriptors@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz#1bd63aeacf0d5d2d2f31b5e393b03a7c601a23f7" + integrity sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +object.values@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" + integrity sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + has "^1.0.3" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +opn@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== + dependencies: + is-wsl "^1.1.0" + +optimize-css-assets-webpack-plugin@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz#85883c6528aaa02e30bbad9908c92926bb52dc90" + integrity sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A== + dependencies: + cssnano "^4.1.10" + last-call-webpack-plugin "^3.0.0" + +original@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" + integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== + dependencies: + url-parse "^1.4.3" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@0: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + 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" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-retry@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" + integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== + dependencies: + retry "^0.12.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parallel-transform@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== + dependencies: + cyclist "^1.0.1" + inherits "^2.0.3" + readable-stream "^2.1.5" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-asn1@^5.0.0, parse-asn1@^5.1.5: + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== + dependencies: + asn1.js "^5.2.0" + browserify-aes "^1.0.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + +path-complete-extname@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/path-complete-extname/-/path-complete-extname-1.0.0.tgz#f889985dc91000c815515c0bfed06c5acda0752b" + integrity sha512-CVjiWcMRdGU8ubs08YQVzhutOR5DEfO97ipRIlOGMK5Bek5nQySknBpuxVAVJ36hseTNs+vdIcv57ZrWxH7zvg== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= + 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" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +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" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pbkdf2@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" + integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== + 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@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + 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" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pnp-webpack-plugin@^1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" + integrity sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg== + dependencies: + ts-pnp "^1.1.6" + +popper.js@1.16.1-lts: + version "1.16.1-lts" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05" + integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA== + +portfinder@^1.0.26: + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== + dependencies: + async "^2.6.2" + debug "^3.1.1" + mkdirp "^0.5.5" + +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" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +postcss-attribute-case-insensitive@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" + integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^6.0.2" + +postcss-calc@^7.0.1: + version "7.0.5" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" + integrity sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg== + dependencies: + postcss "^7.0.27" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" + +postcss-color-functional-notation@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" + integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-gray@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" + integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-color-hex-alpha@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" + integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== + dependencies: + postcss "^7.0.14" + postcss-values-parser "^2.0.1" + +postcss-color-mod-function@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" + integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-rebeccapurple@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" + integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-colormin@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" + integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== + dependencies: + browserslist "^4.0.0" + color "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-convert-values@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" + integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-custom-media@^7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" + integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== + dependencies: + postcss "^7.0.14" + +postcss-custom-properties@^8.0.11: + version "8.0.11" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" + integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== + dependencies: + postcss "^7.0.17" + postcss-values-parser "^2.0.1" + +postcss-custom-selectors@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" + integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-dir-pseudo-class@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" + integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-discard-comments@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" + integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== + dependencies: + postcss "^7.0.0" + +postcss-discard-duplicates@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" + integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== + dependencies: + postcss "^7.0.0" + +postcss-discard-empty@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" + integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== + dependencies: + postcss "^7.0.0" + +postcss-discard-overridden@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" + integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== + dependencies: + postcss "^7.0.0" + +postcss-double-position-gradients@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" + integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== + dependencies: + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-env-function@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" + integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-flexbugs-fixes@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz#9218a65249f30897deab1033aced8578562a6690" + integrity sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ== + dependencies: + postcss "^7.0.26" + +postcss-focus-visible@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" + integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== + dependencies: + postcss "^7.0.2" + +postcss-focus-within@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" + integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== + dependencies: + postcss "^7.0.2" + +postcss-font-variant@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz#42d4c0ab30894f60f98b17561eb5c0321f502641" + integrity sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA== + dependencies: + postcss "^7.0.2" + +postcss-gap-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" + integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== + dependencies: + postcss "^7.0.2" + +postcss-image-set-function@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" + integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-import@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-12.0.1.tgz#cf8c7ab0b5ccab5649024536e565f841928b7153" + integrity sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw== + dependencies: + postcss "^7.0.1" + postcss-value-parser "^3.2.3" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-initial@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.2.tgz#f018563694b3c16ae8eaabe3c585ac6319637b2d" + integrity sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA== + dependencies: + lodash.template "^4.5.0" + postcss "^7.0.2" + +postcss-lab-function@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" + integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-load-config@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a" + integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw== + dependencies: + cosmiconfig "^5.0.0" + import-cwd "^2.0.0" + +postcss-loader@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" + integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== + dependencies: + loader-utils "^1.1.0" + postcss "^7.0.0" + postcss-load-config "^2.0.0" + schema-utils "^1.0.0" + +postcss-logical@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" + integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== + dependencies: + postcss "^7.0.2" + +postcss-media-minmax@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" + integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== + dependencies: + postcss "^7.0.2" + +postcss-merge-longhand@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" + integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== + dependencies: + css-color-names "0.0.4" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + stylehacks "^4.0.0" + +postcss-merge-rules@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" + integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + cssnano-util-same-parent "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + vendors "^1.0.0" + +postcss-minify-font-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" + integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-gradients@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" + integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + is-color-stop "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-params@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" + integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== + dependencies: + alphanum-sort "^1.0.0" + browserslist "^4.0.0" + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + uniqs "^2.0.0" + +postcss-minify-selectors@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" + integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== + dependencies: + alphanum-sort "^1.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +postcss-modules-extract-imports@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" + integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== + dependencies: + postcss "^7.0.5" + +postcss-modules-local-by-default@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" + integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== + dependencies: + icss-utils "^4.1.1" + postcss "^7.0.32" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" + integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^6.0.0" + +postcss-modules-values@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" + integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== + dependencies: + icss-utils "^4.0.0" + postcss "^7.0.6" + +postcss-nesting@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" + integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== + dependencies: + postcss "^7.0.2" + +postcss-normalize-charset@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" + integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== + dependencies: + postcss "^7.0.0" + +postcss-normalize-display-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" + integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-positions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" + integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== + dependencies: + cssnano-util-get-arguments "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-repeat-style@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" + integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-string@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" + integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== + dependencies: + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-timing-functions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" + integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-unicode@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" + integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-url@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" + integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-whitespace@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" + integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-ordered-values@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" + integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== + dependencies: + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-overflow-shorthand@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" + integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== + dependencies: + postcss "^7.0.2" + +postcss-page-break@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" + integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== + dependencies: + postcss "^7.0.2" + +postcss-place@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" + integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-preset-env@^6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz#c34ddacf8f902383b35ad1e030f178f4cdf118a5" + integrity sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg== + dependencies: + autoprefixer "^9.6.1" + browserslist "^4.6.4" + caniuse-lite "^1.0.30000981" + css-blank-pseudo "^0.1.4" + css-has-pseudo "^0.10.0" + css-prefers-color-scheme "^3.1.1" + cssdb "^4.4.0" + postcss "^7.0.17" + postcss-attribute-case-insensitive "^4.0.1" + postcss-color-functional-notation "^2.0.1" + postcss-color-gray "^5.0.0" + postcss-color-hex-alpha "^5.0.3" + postcss-color-mod-function "^3.0.3" + postcss-color-rebeccapurple "^4.0.1" + postcss-custom-media "^7.0.8" + postcss-custom-properties "^8.0.11" + postcss-custom-selectors "^5.1.2" + postcss-dir-pseudo-class "^5.0.0" + postcss-double-position-gradients "^1.0.0" + postcss-env-function "^2.0.2" + postcss-focus-visible "^4.0.0" + postcss-focus-within "^3.0.0" + postcss-font-variant "^4.0.0" + postcss-gap-properties "^2.0.0" + postcss-image-set-function "^3.0.1" + postcss-initial "^3.0.0" + postcss-lab-function "^2.0.1" + postcss-logical "^3.0.0" + postcss-media-minmax "^4.0.0" + postcss-nesting "^7.0.0" + postcss-overflow-shorthand "^2.0.0" + postcss-page-break "^2.0.0" + postcss-place "^4.0.1" + postcss-pseudo-class-any-link "^6.0.0" + postcss-replace-overflow-wrap "^3.0.0" + postcss-selector-matches "^4.0.0" + postcss-selector-not "^4.0.0" + +postcss-pseudo-class-any-link@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" + integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-reduce-initial@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" + integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + +postcss-reduce-transforms@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" + integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== + dependencies: + cssnano-util-get-match "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-replace-overflow-wrap@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" + integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== + dependencies: + postcss "^7.0.2" + +postcss-safe-parser@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz#a6d4e48f0f37d9f7c11b2a581bf00f8ba4870b96" + integrity sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g== + dependencies: + postcss "^7.0.26" + +postcss-selector-matches@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" + integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-not@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz#263016eef1cf219e0ade9a913780fc1f48204cbf" + integrity sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-parser@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" + integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== + dependencies: + dot-prop "^5.2.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" + integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== + dependencies: + cssesc "^2.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" + integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + util-deprecate "^1.0.2" + +postcss-svgo@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" + integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== + dependencies: + is-svg "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + svgo "^1.0.0" + +postcss-unique-selectors@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" + integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== + dependencies: + alphanum-sort "^1.0.0" + postcss "^7.0.0" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + +postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" + integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.35" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" + integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.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" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + +prop-types@^15.6.2, prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + 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" + safe-buffer "^5.1.2" + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + 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" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= + 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" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +react-dom@16: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89" + integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.1" + +react-fast-compare@^3.0.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" + integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== + +react-is@^16.6.0, react-is@^16.6.3, react-is@^16.7.0, react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +"react-is@^16.8.0 || ^17.0.0", "react-is@^16.8.6 || ^17.0.0": + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-popper@^2.2.4: + version "2.2.5" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96" + integrity sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw== + dependencies: + react-fast-compare "^3.0.1" + warning "^4.0.2" + +react-router-dom@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" + integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.2.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.2.0, react-router@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293" + integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.4.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-transition-group@^4.4.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" + integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react@16: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" + integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + +react_ujs@^2.5.0, react_ujs@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/react_ujs/-/react_ujs-2.6.1.tgz#a202a33c95c9e2bb18ab56926b7e79f3325ec855" + integrity sha512-9M33/A8cubStkZ2cpJSimcTD0RlCWiqXF6e90IQmMw/Caf/W0dtAzOtHtiQE3JjLbt/nhRR7NLPxMfnlm141ig== + dependencies: + react_ujs "^2.6.0" + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= + 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" + integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= + dependencies: + find-up "^1.0.0" + read-pkg "^1.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" + integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.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.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + 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.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== + dependencies: + picomatch "^2.2.1" + +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + +regenerate-unicode-properties@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + +regenerator-transform@^0.14.2: + version "0.14.5" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" + integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== + dependencies: + "@babel/runtime" "^7.8.4" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexp.prototype.flags@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +regexpu-core@^4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" + integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + +regjsgen@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" + integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== + +regjsparser@^0.6.4: + version "0.6.9" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.9.tgz#b489eef7c9a2ce43727627011429cf833a7183e6" + integrity sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ== + 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" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + dependencies: + is-finite "^1.0.0" + +request@^2.87.0, request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= + dependencies: + resolve-from "^3.0.0" + +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.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" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + +rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" + integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= + +rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" + integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= + +rimraf@2, rimraf@^2.5.4, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.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" + integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= + dependencies: + aproba "^1.1.1" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sass-graph@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.5.tgz#a981c87446b8319d96dce0671e487879bd24c2e8" + integrity sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag== + dependencies: + glob "^7.0.0" + lodash "^4.0.0" + scss-tokenizer "^0.2.3" + yargs "^13.3.2" + +sass-loader@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" + integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ== + dependencies: + clone-deep "^4.0.1" + loader-utils "^1.2.3" + neo-async "^2.6.1" + schema-utils "^2.6.1" + semver "^6.3.0" + +sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + dependencies: + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" + +schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.7.0: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" + integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== + dependencies: + "@types/json-schema" "^7.0.6" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +scss-tokenizer@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" + integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE= + 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" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= + +selfsigned@^1.10.8: + version "1.10.8" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" + integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== + dependencies: + node-forge "^0.10.0" + +semantic-ui-react@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/semantic-ui-react/-/semantic-ui-react-2.0.3.tgz#39091e24078e28129ff9b1beb7dbfc84ca85544b" + integrity sha512-a0hGN6XXw64sRSKwWqMCKSI/AGLohxNeWuErS39eswvBbUnLjBij8ZoEdiqDiz/PuWpwYIRjgmQVrut+7h3b2g== + dependencies: + "@babel/runtime" "^7.10.5" + "@fluentui/react-component-event-listener" "~0.51.6" + "@fluentui/react-component-ref" "~0.51.6" + "@popperjs/core" "^2.6.0" + "@semantic-ui-react/event-stack" "^3.1.2" + clsx "^1.1.1" + keyboard-key "^1.1.0" + lodash "^4.17.19" + lodash-es "^4.17.15" + prop-types "^15.7.2" + react-is "^16.8.6 || ^17.0.0" + react-popper "^2.2.4" + shallowequal "^1.1.0" + +"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= + 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.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.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" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + 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" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + 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" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + 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" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + 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 "^3.1.0" + +sockjs-client@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.5.0.tgz#2f8ff5d4b659e0d092f7aba0b7c386bd2aa20add" + integrity sha512-8Dt3BDi4FYNrCFGTL/HtwVzkARrENdwOUf1ZoW/9p3M8lZdFT35jVdrHza+qgxuG9H3/shR4cuX/X9umUrjP8Q== + dependencies: + debug "^3.2.6" + eventsource "^1.0.7" + faye-websocket "^0.11.3" + inherits "^2.0.4" + json3 "^3.3.3" + url-parse "^1.4.7" + +sockjs@^0.3.21: + version "0.3.21" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.21.tgz#b34ffb98e796930b60a0cfa11904d6a339a7d417" + integrity sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw== + dependencies: + faye-websocket "^0.11.3" + uuid "^3.4.0" + websocket-driver "^0.7.4" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= + dependencies: + is-plain-obj "^1.0.0" + +source-list-map@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + 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.5.12, source-map-support@~0.5.19: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + +source-map@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + integrity sha1-66T12pwNyZneaAMti092FzZSA2s= + dependencies: + amdefine ">=0.0.4" + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, 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" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@~0.7.2: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +spark-md5@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.1.tgz#83a0e255734f2ab4e5c466e5a2cfc9ba2aa2124d" + integrity sha512-0tF3AGSD1ppQeuffsLDIOWlKUd3lS92tFxcsrh5Pe3ZphhnoK+oXIBTzOAThZCiuINZLvpiLH/1VS1/ANEJVig== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" + integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +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" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + 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" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +ssri@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" + integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + dependencies: + figgy-pudding "^3.5.1" + +ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +stdout-stream@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" + integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== + dependencies: + readable-stream "^2.0.1" + +stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-each@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" + integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== + dependencies: + end-of-stream "^1.1.0" + stream-shift "^1.0.0" + +stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +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" + integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string_decoder@^1.0.0, string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +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" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + 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" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= + dependencies: + is-utf8 "^0.2.0" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= + dependencies: + get-stdin "^4.0.1" + +style-loader@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e" + integrity sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q== + dependencies: + loader-utils "^2.0.0" + schema-utils "^2.7.0" + +stylehacks@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" + integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +svgo@^1.0.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + +symbol-observable@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + +tapable@^1.0.0, tapable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tar@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" + integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== + dependencies: + block-stream "*" + fstream "^1.0.12" + inherits "2" + +tar@^6.0.2: + version "6.1.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" + integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +terser-webpack-plugin@^1.4.3: + version "1.4.5" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b" + integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw== + dependencies: + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" + schema-utils "^1.0.0" + serialize-javascript "^4.0.0" + source-map "^0.6.1" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" + +terser-webpack-plugin@^4.0.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a" + integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ== + dependencies: + cacache "^15.0.5" + find-cache-dir "^3.3.1" + jest-worker "^26.5.0" + p-limit "^3.0.2" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.3.4" + webpack-sources "^1.4.3" + +terser@^4.1.2: + version "4.8.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" + integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +terser@^5.3.4: + version "5.6.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.1.tgz#a48eeac5300c0a09b36854bf90d9c26fb201973c" + integrity sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + +through2@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +timers-browserify@^2.0.4: + version "2.0.12" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" + integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== + dependencies: + setimmediate "^1.0.4" + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +tiny-invariant@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + +tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +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" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + 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" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= + +"true-case-path@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" + integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew== + dependencies: + glob "^7.1.2" + +ts-pnp@^1.1.6: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" + integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== + +tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +turbolinks@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/turbolinks/-/turbolinks-5.2.0.tgz#e6877a55ea5c1cb3bb225f0a4ae303d6d32ff77c" + integrity sha512-pMiez3tyBo6uRHFNNZoYMmrES/IaGgMhQQM+VFF36keryjb5ms0XkVpmKHkfW/4Vy96qiGW3K9bz0tF5sK9bBw== + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +unbox-primitive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.0.tgz#eeacbc4affa28e9b3d36b5eaeccc50b3251b1d3f" + integrity sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.0" + has-symbols "^1.0.0" + which-boxed-primitive "^1.0.1" + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url-parse@^1.4.3, url-parse@^1.4.7: + version "1.5.1" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b" + integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q== + dependencies: + querystringify "^2.1.1" + 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" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@^1.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" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +util@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= + dependencies: + inherits "2.0.1" + +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@^3.3.2, uuid@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-compile-cache@^2.1.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +vendors@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" + integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + +warning@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +watchpack-chokidar2@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" + integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww== + dependencies: + chokidar "^2.1.8" + +watchpack@^1.7.4: + version "1.7.5" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" + integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== + dependencies: + graceful-fs "^4.1.2" + neo-async "^2.5.0" + optionalDependencies: + chokidar "^3.4.1" + watchpack-chokidar2 "^2.0.1" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +webpack-assets-manifest@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-3.1.1.tgz#39bbc3bf2ee57fcd8ba07cda51c9ba4a3c6ae1de" + integrity sha512-JV9V2QKc5wEWQptdIjvXDUL1ucbPLH2f27toAY3SNdGZp+xSaStAgpoMcvMZmqtFrBc9a5pTS1058vxyMPOzRQ== + dependencies: + chalk "^2.0" + lodash.get "^4.0" + lodash.has "^4.0" + mkdirp "^0.5" + schema-utils "^1.0.0" + tapable "^1.0.0" + webpack-sources "^1.0.0" + +webpack-cli@^3.3.12: + version "3.3.12" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" + integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== + dependencies: + chalk "^2.4.2" + cross-spawn "^6.0.5" + enhanced-resolve "^4.1.1" + findup-sync "^3.0.0" + global-modules "^2.0.0" + import-local "^2.0.0" + interpret "^1.4.0" + loader-utils "^1.4.0" + supports-color "^6.1.0" + v8-compile-cache "^2.1.1" + yargs "^13.3.2" + +webpack-dev-middleware@^3.7.2: + version "3.7.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" + integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== + dependencies: + memory-fs "^0.4.1" + mime "^2.4.4" + mkdirp "^0.5.1" + range-parser "^1.2.1" + webpack-log "^2.0.0" + +webpack-dev-server@^3.11.2: + version "3.11.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz#695ebced76a4929f0d5de7fd73fafe185fe33708" + integrity sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ== + dependencies: + ansi-html "0.0.7" + bonjour "^3.5.0" + chokidar "^2.1.8" + compression "^1.7.4" + connect-history-api-fallback "^1.6.0" + debug "^4.1.1" + del "^4.1.1" + express "^4.17.1" + html-entities "^1.3.1" + http-proxy-middleware "0.19.1" + import-local "^2.0.0" + internal-ip "^4.3.0" + ip "^1.1.5" + is-absolute-url "^3.0.3" + killable "^1.0.1" + loglevel "^1.6.8" + opn "^5.5.0" + p-retry "^3.0.1" + portfinder "^1.0.26" + schema-utils "^1.0.0" + selfsigned "^1.10.8" + semver "^6.3.0" + serve-index "^1.9.1" + sockjs "^0.3.21" + sockjs-client "^1.5.0" + spdy "^4.0.2" + strip-ansi "^3.0.1" + supports-color "^6.1.0" + url "^0.11.0" + webpack-dev-middleware "^3.7.2" + webpack-log "^2.0.0" + ws "^6.2.1" + yargs "^13.3.2" + +webpack-log@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" + integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== + dependencies: + ansi-colors "^3.0.0" + uuid "^3.3.2" + +webpack-sources@^1.0.0, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack@^4.44.1: + version "4.46.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" + integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/wasm-edit" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + acorn "^6.4.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" + enhanced-resolve "^4.5.0" + eslint-scope "^4.0.3" + json-parse-better-errors "^1.0.2" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.3" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" + schema-utils "^1.0.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.3" + watchpack "^1.7.4" + webpack-sources "^1.4.1" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +which-boxed-primitive@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@1, which@^1.2.14, which@^1.2.9, which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +worker-farm@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== + dependencies: + errno "~0.1.7" + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +xtend@^4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" + integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.7.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^13.3.2: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/docs/tutorial/intro_to_hyper_components.md b/docs/tutorial/intro_to_hyper_components.md new file mode 100644 index 000000000..e9b4a2632 --- /dev/null +++ b/docs/tutorial/intro_to_hyper_components.md @@ -0,0 +1,132 @@ +#Tutorial: Intro to React + +This tutorial doesn’t assume any existing HyperStack or React knowledge. + +##Before We Start the Tutorial + +> This tutorial is shamelessly stolen for pedagogical reasons from this [React tutorial](https://reactjs.org/tutorial/tutorial.html) + +In this tutorial you will build a small game. Even though its just a small game, the techniques you’ll learn are fundamental to building any HyperStack app, and mastering it will give you a deep understanding of not only Hyper Conponents but also of React. + + +> Tip: This tutorial is designed for people who prefer to learn by doing. If you prefer learning concepts from the ground up, check out our step-by-step guide. You might find this tutorial and the guide complementary to each other. + +The tutorial is divided into several sections: + ++ Setup for the Tutorial will give you a starting point to follow the tutorial. ++ Overview will teach you the fundamentals of HyperStack: Components, Params and State. ++ Completing the game will teach you the most common techniques in Hyperstack development. ++ Adding Time Travel will give you a deeper insight into the unique strengths of HyperStack and the underlying React technologies. + +You don’t have to complete all of the sections at once to get the value out of this tutorial. Try to get as far as you can — even if it’s one or two sections. + +### What Are We Building? + +In this tutorial, we’ll show how to build an interactive tic-tac-toe game with HyperStack. + +###Prerequisites + +We’ll assume that you have some familiarity with HTML and Ruby, but you should be able to follow along even if you’re coming from a different programming language such as Javascript, or if you have familiarit with React. We’ll also assume that you’re familiar with programming concepts like methods (functions), objects, arrays, and to a lesser extent, classes. + +Setup for the Tutorial +There are two ways to complete this tutorial: you can either write the code in your browser, or you can set up a local development environment on your computer. + +Setup Option 1: Write Code in the Browser +This is the quickest way to get started! + +First, open this Starter Code in a new tab. The new tab should display an empty tic-tac-toe game board and React code. We will be editing the React code in this tutorial. + +You can now skip the second setup option, and go to the Overview section to get an overview of React. + +Setup Option 2: Local Development Environment +This is completely optional and not required for this tutorial! + + +Optional: Instructions for following along locally using your preferred text editor +Help, I’m Stuck! +If you get stuck, check out the community support resources. In particular, Reactiflux Chat is a great way to get help quickly. If you don’t receive an answer, or if you remain stuck, please file an issue, and we’ll help you out. + +Overview +Now that you’re set up, let’s get an overview of React! + +What Is React? +React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called “components”. + +React has a few different kinds of components, but we’ll start with React.Component subclasses: + +class ShoppingList extends React.Component { + render() { + return ( +
    +

    Shopping List for {this.props.name}

    +
      +
    • Instagram
    • +
    • WhatsApp
    • +
    • Oculus
    • +
    +
    + ); + } +} + +// Example usage: +We’ll get to the funny XML-like tags soon. We use components to tell React what we want to see on the screen. When our data changes, React will efficiently update and re-render our components. + +Here, ShoppingList is a React component class, or React component type. A component takes in parameters, called props (short for “properties”), and returns a hierarchy of views to display via the render method. + +The render method returns a description of what you want to see on the screen. React takes the description and displays the result. In particular, render returns a React element, which is a lightweight description of what to render. Most React developers use a special syntax called “JSX” which makes these structures easier to write. The
    syntax is transformed at build time to React.createElement('div'). The example above is equivalent to: + +return React.createElement('div', {className: 'shopping-list'}, + React.createElement('h1', /* ... h1 children ... */), + React.createElement('ul', /* ... ul children ... */) +); +See full expanded version. + +If you’re curious, createElement() is described in more detail in the API reference, but we won’t be using it in this tutorial. Instead, we will keep using JSX. + +JSX comes with the full power of JavaScript. You can put any JavaScript expressions within braces inside JSX. Each React element is a JavaScript object that you can store in a variable or pass around in your program. + +The ShoppingList component above only renders built-in DOM components like
    and
  • . But you can compose and render custom React components too. For example, we can now refer to the whole shopping list by writing . Each React component is encapsulated and can operate independently; this allows you to build complex UIs from simple components. + +Inspecting the Starter Code +If you’re going to work on the tutorial in your browser, open this code in a new tab: Starter Code. If you’re going to work on the tutorial locally, instead open src/index.js in your project folder (you have already touched this file during the setup). + +This Starter Code is the base of what we’re building. We’ve provided the CSS styling so that you only need to focus on learning React and programming the tic-tac-toe game. + +By inspecting the code, you’ll notice that we have three React components: + +Square +Board +Game +The Square component renders a single + ); + } +} +Before: + +React Devtools +After: You should see a number in each square in the rendered output. + +React Devtools +View the full code at this point diff --git a/docs/tutorial/todo.md b/docs/tutorial/todo.md index 56d109745..41fb314fd 100644 --- a/docs/tutorial/todo.md +++ b/docs/tutorial/todo.md @@ -1,9 +1,5 @@ # TodoMVC Tutorial Part I -todo\# TodoMVC Tutorial - -_Based on Ruby on Rails 5.2.x_ - ## Prerequisites * Linux or Mac system @@ -657,7 +653,7 @@ end All states in Hyperstack are simply Ruby instance variables \(ivars for short which are variables with a leading @\). Here we use the `@editing` ivar. -We have already used a lot of states that are built into the HyperModel and HyperRouter. The states of these components are built out collections of instance variables like `@editing`. +We have already used a lot of states that are built into the HyperModel and HyperRouter. The states of these components are built out of collections of instance variables like `@editing`. In the `TodoItem` component the value of `@editing` controls whether to render the `EditItem` or the INPUT, LABEL, and Anchor tags. @@ -1002,5 +998,4 @@ end 3: Its possible to get things so messed up the hot-reloader will not work. Restart the server and reload the browser. -4: Reach out to us on Gitter, we are always happy to help get you onboarded! - +4: Reach out to us on Slack, we are always happy to help get you onboarded! diff --git a/docs/wip.png b/docs/wip.png new file mode 100644 index 000000000..311d55ec5 Binary files /dev/null and b/docs/wip.png differ diff --git a/readme.md b/readme.md index e8fb08cd3..a255dbe7b 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,9 @@ # Hyperstack +[![Build Status](https://travis-ci.com/hyperstack-org/hyperstack.svg?branch=edge)](https://travis-ci.com/hyperstack-org/hyperstack) +[![Gem Version](https://badge.fury.io/rb/rails-hyperstack.svg)](https://badge.fury.io/rb/rails-hyperstack) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Slack](https://img.shields.io/badge/slack-hyperstack.org/slack-yellow.svg?logo=slack)](https://join.slack.com/t/hyperstack-org/shared_invite/enQtNTg4NTI5NzQyNTYyLWQ4YTZlMGU0OGIxMDQzZGIxMjNlOGY5MjRhOTdlMWUzZWYyMTMzYWJkNTZmZDRhMDEzODA0NWRkMDM4MjdmNDE) -[![Build Status](https://travis-ci.org/hyperstack-org/hyperstack.svg?branch=edge)](https://travis-ci.org/hyperstack-org/hyperstack) - -This is the edge branch - the system is stable, and there are approx 1000 test specs passig. For current status on development see [current status.](https://github.com/hyperstack-org/hyperstack/blob/edge/current-status.md) Hyperstack is a Ruby-based DSL and modern web toolkit for building spectacular, interactive web applications fast! @@ -11,13 +12,14 @@ Hyperstack is a Ruby-based DSL and modern web toolkit for building spectacular, + A well documented and stable Ruby DSL for wrapping **React** and **ReactRouter** as well as **any** JavaScript library or component. No need to learn JavaScript! + **Isomorphic Models with bi-directional data** so you can access your models as if they were on the client. -All that means you can write simple front-end code like this: +This means you can write simple front-end code like this: ```ruby class GoodBooksToRead < HyperComponent render(UL) do Book.good_books.each do |book| - LI { "Read #{book.name}" }.on(:click) { display book } if book.available? + LI { "Read #{book.name}" } + .on(:click) { display book } if book.available? end end end @@ -27,16 +29,31 @@ In the code above, if the `good_books` scope changed (even on the server), the U ## Website and documentation -Please see the documentation site for full documentation, or find the same content in the [/docs](/docs) folder in this repo if you prefer. +Please see the website site for full documentation: -+ Website: [hyperstack.org](https://hyperstack.org) -+ Docs: [docs.hyperstack.org](https://docs.hyperstack.org/) ++ [hyperstack.org](https://hyperstack.org) ## Setup and installation -You can be up and running in **less than 5 minutes**. Just follow the simple setup guide for a new Rails application all correctly configured and ready to go with Hyperstack. +You can be up and running in **less than 5 minutes**. Just follow the simple setup guide for to add Hyperstack to a new or existing Rails application: + ++ [Setup and Installation docs](https://docs.hyperstack.org/rails-installation/using-the-installer) + +## Development Status + +We now are issuing 1.0 release candidates weekly until all issues are either closed or moved to post 1.0 release status. **Your opinion matters, plus take some time to up/down vote or comment on issues of interest.** + + +| Release
    Date | Version | Open
    Issues | Documentation
    Sections
    Draft Ready | Documentation
    Sections
    WIP | +|--------------|---------|-------------|-------|------| +| April 12, 2021 | 1.0.alpha1.8 | 128 | 36 | 9 | + +> Open issues includes enhancements, documentation, and discussion issues as well as few bugs. +> +> The documentation WIP (work in progress) numbers are approx, as more sections may be added. + ++ [Older Status Reports](https://github.com/hyperstack-org/hyperstack/blob/edge/current-status.md) -+ [Setup and Installation docs](https://docs.hyperstack.org/installation/man-installation) ## Community and support @@ -52,39 +69,14 @@ Please ask technical questions on StackOverflow as the answers help people in th + Please ask questions here: https://hyperstack.org/question + All the `hyperstack` tagged questions are here: https://hyperstack.org/questions -## Roadmap - -Hyperstack is evolving; we are improving it all the time. As much as we love Ruby today, we see ourselves embracing new languages in the future. [Crystal](https://crystal-lang.org/) perhaps? We are also watching [Wasm](https://webassembly.org/) carefully. - -Please see the [ROADMAP][] file for more information. - -[roadmap]: ROADMAP.md -[current status]: current-status.md - ## Contributing If you would like to help, please read the [CONTRIBUTING][] file for suggestions. [contributing]: CONTRIBUTING.md -## Links - -+ Rubygems: https://rubygems.org/profiles/hyperstack -+ Travis: https://travis-ci.org/hyperstack-org -+ Website edge: https://edge.hyperstack.org/ -+ Website master: https://hyperstack.org/ - ## License Released under the MIT License. See the [LICENSE][] file for further details. [license]: LICENSE - -## History - -Hyperstack is an evolution of [Ruby-Hyperloop](https://github.com/ruby-hyperloop). We decided to rename the project to drop the Ruby suffix and also took the opportunity to simplify the repos and project overall. - -+ Old website: http://ruby-hyperloop.org/ -+ Old Github: https://github.com/ruby-hyperloop -+ Legacy branch: https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy -+ Legacy install script: https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy/install diff --git a/release-notes/1.0.alpha1.5.md b/release-notes/1.0.alpha1.5.md new file mode 100644 index 000000000..26c1bb102 --- /dev/null +++ b/release-notes/1.0.alpha1.5.md @@ -0,0 +1,42 @@ +## 1.0alpha1.5 - 2019-06-19 +### Security ++ [#165](https://github.com/hyperstack-org/hyperstack/issues/165) Secure access to `composed_of` relationships. + +### Added ++ [#114](https://github.com/hyperstack-org/hyperstack/issues/114) Add Polymorphic Models ++ [#186](https://github.com/hyperstack-org/hyperstack/issues/186) Allow components to override built-in event names (i.e. `fires :click`) ++ [#185](https://github.com/hyperstack-org/hyperstack/issues/185) Allow import of es6 modules that have a single component ++ [#183](https://github.com/hyperstack-org/hyperstack/issues/183) Add new Rails 6 active support methods: `extract!` and `index_with` ++ [#180](https://github.com/hyperstack-org/hyperstack/issues/180) `sleep` now returns a promise so it works nicely with Operations ++ [#176](https://github.com/hyperstack-org/hyperstack/issues/176) The `render` callback is now optional. See issue for details. ++ [#168](https://github.com/hyperstack-org/hyperstack/issues/168) Allow custom headers in `ServerOp`s ++ [#160](https://github.com/hyperstack-org/hyperstack/issues/160) Allows for dynamically attaching events: `.on(false || nil)` is ignored. ++ [#159](https://github.com/hyperstack-org/hyperstack/issues/159) Hyperstack.connect behaves nicely if passed a dummy value. ++ [#148](https://github.com/hyperstack-org/hyperstack/issues/148) Rails installer works with existing Rails apps. ++ [#146](https://github.com/hyperstack-org/hyperstack/issues/146) Allow ActiveModel attribute methods to be overridden. + + +### Fixed ++ [#196](https://github.com/hyperstack-org/hyperstack/issues/196) The `empty?` method no longer forces fetch of entire collection ++ [#195](https://github.com/hyperstack-org/hyperstack/issues/195) UI will not update until after all relationships of a destroyed record are completely updated. ++ [#194](https://github.com/hyperstack-org/hyperstack/issues/194) Fetching STI models via scope and finder will now return the same backing record. ++ [#193](https://github.com/hyperstack-org/hyperstack/issues/193) Allow the `super` method in hyper-spec examples. ++ [#192](https://github.com/hyperstack-org/hyperstack/issues/192) Dummy values will be initialized with schema default value. ++ [#191](https://github.com/hyperstack-org/hyperstack/issues/191) Fixed incompatibility between the Router and Legacy style param method. ++ [#181](https://github.com/hyperstack-org/hyperstack/issues/181) Fixed nested class component lookup. ++ [#179](https://github.com/hyperstack-org/hyperstack/issues/179) Once an operation moves to the failed track it now stays on the failed track. ++ [#178](https://github.com/hyperstack-org/hyperstack/issues/178) Resetting system now correctly reinitializes all variables. ++ [#173](https://github.com/hyperstack-org/hyperstack/issues/173) Both sides of a relationship can be new and will get saved properly. ++ [#170](https://github.com/hyperstack-org/hyperstack/issues/170) HyperSpec `pause` method working again. ++ [#169](https://github.com/hyperstack-org/hyperstack/issues/169) Fixes to ActiveRecord model equality test. ++ [#166](https://github.com/hyperstack-org/hyperstack/issues/166) Allow `Element#dom_node` to work with native components. ++ [#164](https://github.com/hyperstack-org/hyperstack/issues/164) Insure state change notification when scopes change remotely. ++ [#163](https://github.com/hyperstack-org/hyperstack/issues/163) Ignore hotloader and hotloader errors during prerendering. ++ [#154](https://github.com/hyperstack-org/hyperstack/issues/154) Stop raising deprecation notices when using `imports` directive. ++ [#153](https://github.com/hyperstack-org/hyperstack/issues/153) `.to_n` working properly on Component classes. ++ [#144](https://github.com/hyperstack-org/hyperstack/issues/144) Timeout if connection between console and server fails. ++ [#143](https://github.com/hyperstack-org/hyperstack/issues/143) `Errors#full_messages` working properly. ++ [#138](https://github.com/hyperstack-org/hyperstack/issues/138) Count of has_many :through relations working properly ++ [#126](https://github.com/hyperstack-org/hyperstack/issues/126) Scopes no longer returning extra `DummyValue`. ++ [#125](https://github.com/hyperstack-org/hyperstack/issues/125) Belongs-to relationships on new records will react to updates to the relationship. ++ [#120](https://github.com/hyperstack-org/hyperstack/issues/120) `ActiveRecord::Base.new?` renamed to `new_record?` (you can still use `new?` or override it) diff --git a/release-notes/1.0.alpha1.6.md b/release-notes/1.0.alpha1.6.md new file mode 100644 index 000000000..7eda148e9 --- /dev/null +++ b/release-notes/1.0.alpha1.6.md @@ -0,0 +1,63 @@ +## 1.0alpha1.6 - 2021-03-29 + +| Release
    Date | Version | Open
    Issues | Documentation
    Sections
    Draft Ready | Documentation
    Sections
    WIP | +|--------------|---------|-------------|-------|------| +| March 29, 2021 | 1.0.alpha1.6 | 167 | 35 | 10 | + +> Open issues includes enhancements, documentation, and discussion issues as well as few bugs. +> +> The documentation WIP (work in progress) numbers are approx, as more sections may be added. + +### New Major Features ++ Now compatible with Opal 1.x and Rails 6.x: Tested with Opal ~>1.0 + Rails ~>5.0 and Rails ~>6.0, and Opal ~>0.11 and Rails ~>5.0. ++ You can run Hypermodel connections on Redis (instead of ActiveRecord). This gives about a 10% performance boost, and there will be even better +performance as the Redis adapter is optimized. ++ The `rails-hyperstack` gem now includes a file `hyperstack/server_side_auto_require` that you may require from the `hyperstack.rb` initializer. +This file will add the capability to Rails ActiveSupport Dependencies to automatically look for files in the matching main app sub directory when loaded first from the `hyperstack` directory. This allows you to leave serverside functionality in the main app subdirectories, and only include definitions relevant to the client side in the `hyperstack` directories. See https://github.com/hyperstack-org/hyperstack/issues/361 for more info. ATM requiring this file will set the Rails autoloader mode to :classic. ++ Complete rewrite of Hyperspec with much improved syntax and many new features including ability to use with any Rack application. See [the docs](https://docs.hyperstack.org/development-workflow/hyper-spec) for details. But don't worry its all backwards compatible with the old syntax. ++ Much more robust gem installer. + + +### Breaking Changes ++ [#350](https://github.com/hyperstack-org/hyperstack/issues/350) Moved the server side `after` and `every` methods to an include module. You are only effected if you are using the `after` or `every` methods on the server. ++ You may encounter some breakage due to configuration changes. Rails and the JS world have changed a lot recently, and its hard to insure that the new Gems will work correctly in all situations without some adjustment to your configuration. Please report any issues that you encounter. + +### Security ++ [#205](https://github.com/hyperstack-org/hyperstack/issues/205) Now filters ServerOp params from log files. + +### Added ++ [#379](https://github.com/hyperstack-org/hyperstack/issues/379) If operation dispatch raises an error the operation now fails ++ [#376](https://github.com/hyperstack-org/hyperstack/issues/376) Control Arity Check From HyperSpec ++ [#372](https://github.com/hyperstack-org/hyperstack/issues/372) More flexibility with render block return values ++ [#365](https://github.com/hyperstack-org/hyperstack/issues/365) Added ActiveRecord `increment!` and `decrement!` methods ++ [#364](https://github.com/hyperstack-org/hyperstack/issues/364) Added ActiveRecord `has_and_belongs_to_many` to HyperModel ++ [#356](https://github.com/hyperstack-org/hyperstack/issues/356) Added `json` and `jsonb` ActiveRecord attribute types ++ [#353](https://github.com/hyperstack-org/hyperstack/issues/353) Allow for empty `policy...to(...)` call ++ [#322](https://github.com/hyperstack-org/hyperstack/issues/322) Correctly pass back the return value from observer block ++ [#306](https://github.com/hyperstack-org/hyperstack/issues/306) Relaxed libV8 dependency and removed where possible ++ [#280](https://github.com/hyperstack-org/hyperstack/issues/280) Better error messages for active record failures ++ [#220](https://github.com/hyperstack-org/hyperstack/issues/220) Added shims for browsers not supporting ECMA 6.0 classes ++ [#218](https://github.com/hyperstack-org/hyperstack/issues/218) `on: create` hooks now run BEFORE create not after ++ [#158](https://github.com/hyperstack-org/hyperstack/issues/158) HyperComponent multiple value and `FRAGMENT` returns + + +### Fixed ++ [#380](https://github.com/hyperstack-org/hyperstack/issues/380) Specs now running with Opal `arity_checking` enabled ++ [#375](https://github.com/hyperstack-org/hyperstack/issues/375) Scopes could get out of sync ++ [#370](https://github.com/hyperstack-org/hyperstack/issues/370) Fixed deprecation message during hyperstack:install ++ [#369](https://github.com/hyperstack-org/hyperstack/issues/369) hyperstack:install now adds javascripts link to manifest.js file ++ [#368](https://github.com/hyperstack-org/hyperstack/issues/368) hyperstack:install now checks for webpacker gem ++ [#358](https://github.com/hyperstack-org/hyperstack/issues/358) Incoming broadcast messages were not working if primary key was not `:id` ++ [#354](https://github.com/hyperstack-org/hyperstack/issues/354) Correctly set react variant in production ++ [#347](https://github.com/hyperstack-org/hyperstack/issues/347) Fixed Rails generator and React import misleading comments ++ [#326](https://github.com/hyperstack-org/hyperstack/issues/326) No longer raises Rails `previous_changes` behavior deprecation notices ++ [#325](https://github.com/hyperstack-org/hyperstack/issues/325) Schema default value conversion now supports strings ++ [#275](https://github.com/hyperstack-org/hyperstack/issues/275) Fixed issue with reflects on associations of sibling STI class ++ [#269](https://github.com/hyperstack-org/hyperstack/issues/269) Fixed: TypeError raised when prerendering. ++ [#215](https://github.com/hyperstack-org/hyperstack/issues/215) Collection `any?` method now accepts args. ++ [#184](https://github.com/hyperstack-org/hyperstack/issues/184) ActiveRecord::Base.find([1,2,3]) returns mutiple records same as AR ++ [#162](https://github.com/hyperstack-org/hyperstack/issues/162) Prevent footer from rendering multiple times on the same page (performance issue) + +### Deprecated ++ [#374](https://github.com/hyperstack-org/hyperstack/issues/374) Support for React 15 Dropped ++ [#373](https://github.com/hyperstack-org/hyperstack/issues/373) `componentWillMount` and friends deprecated from React diff --git a/release-notes/1.0.alpha1.7.md b/release-notes/1.0.alpha1.7.md new file mode 100644 index 000000000..3d5c82225 --- /dev/null +++ b/release-notes/1.0.alpha1.7.md @@ -0,0 +1,46 @@ +## 1.0alpha1.7 - 2021-04-05 + +| Release
    Date | Version | Open
    Issues | Documentation
    Sections
    Draft Ready | Documentation
    Sections
    WIP | +|--------------|---------|-------------|-------|------| +| April 5, 2021 | 1.0.alpha1.7 | 147 | 35 | 10 | +| March 29, 2021 | 1.0.alpha1.6 | 167 | 35 | 10 | +> Open issues includes enhancements, documentation, and discussion issues as well as few bugs. Additional issues +may be closed that are not documented below because of duplicates, documentation updates, and old issues previously closed. +> +> The documentation WIP (work in progress) numbers are approx, as more sections may be added. + +### New Major Features + +None + +### Breaking Changes + +None + +### Security Fixes + +None + +### Feature Added ++ [#400](https://github.com/hyperstack-org/hyperstack/issues/400) Relationship and Scope Collections will delegate to the Target model on method missing. ++ [#116](https://github.com/hyperstack-org/hyperstack/issues/116) ActiveRecord `where` implemented + +### Fixed ++ [#399](https://github.com/hyperstack-org/hyperstack/issues/399) Pluck now takes multiple keys ++ [#396](https://github.com/hyperstack-org/hyperstack/issues/396) Fixed: Rejected promises do not move operations to the failure track ++ [#388](https://github.com/hyperstack-org/hyperstack/issues/388) HyperModel and HyperOperation will load without ActiveRecord ++ [#358](https://github.com/hyperstack-org/hyperstack/issues/358) Fixed: (again) changing primary_key causes some failures ++ [#322](https://github.com/hyperstack-org/hyperstack/issues/322) Fixed: Could not return falsy value from an observe block ++ [#127](https://github.com/hyperstack-org/hyperstack/issues/127) Complex expressions work better in on_client (due to upgrade in Parser gem) ++ [#123](https://github.com/hyperstack-org/hyperstack/issues/123) `public_columns_hash` now thread safe. ++ [#119](https://github.com/hyperstack-org/hyperstack/issues/119) `destroy` now updates errors properly and will not mark the record as destroyed unless destroy was successful + + + +### Not Reproducible ++ [#108](https://github.com/hyperstack-org/hyperstack/issues/108) Can't repeat - possibly STI class != STI type field while data is loading ++ [#47](https://github.com/hyperstack-org/hyperstack/issues/47) Added spec - passing a proc for children works fine. + +### Deprecated + +None diff --git a/release-notes/1.0.alpha1.8.md b/release-notes/1.0.alpha1.8.md new file mode 100644 index 000000000..349055818 --- /dev/null +++ b/release-notes/1.0.alpha1.8.md @@ -0,0 +1,39 @@ +## 1.0alpha1.8 - 2021-04-05 + +| Release
    Date | Version | Open
    Issues | Documentation
    Sections
    Draft Ready | Documentation
    Sections
    WIP | +|--------------|---------|-------------|-------|------| +| April 12, 2021 | 1.0.alpha1.8 | 127 | 36 | 9 | +| April 5, 2021 | 1.0.alpha1.7 | 147 | 35 | 10 | +| March 29, 2021 | 1.0.alpha1.6 | 167 | 35 | 10 | +> Open issues includes enhancements, documentation, and discussion issues as well as few bugs. Issues tagged for post launch are not included. Additional issues may be closed that are not documented below because of duplicates, documentation updates, and old issues previously closed. +> +> The documentation WIP (work in progress) numbers are approx, as more sections may be added. + +### New Major Features + +None + +### Breaking Changes + +None + +### Security Fixes + +None + +### Feature Added + ++ [#402](https://github.com/hyperstack-org/hyperstack/issues/402) Class level +`initialize` method will be called at boot for Observers ++ [#401](https://github.com/hyperstack-org/hyperstack/issues/401) Can call `receives` inside the singleton class. + + +### Fixed + ++ [#403](https://github.com/hyperstack-org/hyperstack/issues/403) Fixed: `isomorphic` method only worked before mounting + +### Not Reproducible + +### Deprecated + +None diff --git a/ruby/examples/misc/sinatra_app/Gemfile b/ruby/examples/misc/sinatra_app/Gemfile new file mode 100644 index 000000000..f95cfe828 --- /dev/null +++ b/ruby/examples/misc/sinatra_app/Gemfile @@ -0,0 +1,14 @@ +source "https://rubygems.org" + +gem "sinatra" +gem "rspec" +gem "pry" +gem "opal" +gem "opal-sprockets" +gem "rack" +gem "puma" +gem "hyper-spec", path: "../../../hyper-spec" +# gem 'hyper-spec', +# git: 'git://github.com/hyperstack-org/hyperstack.git', +# branch: 'edge', +# glob: 'ruby/*/*.gemspec' diff --git a/ruby/examples/misc/sinatra_app/Gemfile.lock b/ruby/examples/misc/sinatra_app/Gemfile.lock new file mode 100644 index 000000000..72b796b6e --- /dev/null +++ b/ruby/examples/misc/sinatra_app/Gemfile.lock @@ -0,0 +1,159 @@ +PATH + remote: ../../../hyper-spec + specs: + hyper-spec (1.0.alpha1.5) + actionview + capybara + chromedriver-helper (= 1.2.0) + filecache + method_source + opal (>= 0.11.0, < 2.0) + parser (>= 2.3.3.1) + rspec + selenium-webdriver + timecop (~> 0.8.1) + uglifier + unparser (>= 0.4.2) + webdrivers + +GEM + remote: https://rubygems.org/ + specs: + actionview (6.1.3) + activesupport (= 6.1.3) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activesupport (6.1.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + archive-zip (0.12.0) + io-like (~> 0.3.0) + ast (2.4.2) + builder (3.2.4) + capybara (3.35.3) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + childprocess (3.0.0) + chromedriver-helper (1.2.0) + archive-zip (~> 0.10) + nokogiri (~> 1.8) + coderay (1.1.3) + concurrent-ruby (1.1.8) + crass (1.0.6) + diff-lcs (1.4.4) + erubi (1.10.0) + execjs (2.7.0) + filecache (1.0.2) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + io-like (0.3.1) + loofah (2.9.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + method_source (1.0.0) + mini_mime (1.0.2) + mini_portile2 (2.5.0) + minitest (5.14.4) + mustermann (1.1.1) + ruby2_keywords (~> 0.0.1) + nio4r (2.5.7) + nokogiri (1.11.2) + mini_portile2 (~> 2.5.0) + racc (~> 1.4) + opal (1.1.1) + ast (>= 2.3.0) + parser (~> 3.0) + opal-sprockets (1.0.0) + opal (>= 1.0, < 1.2) + sprockets (~> 4.0) + tilt (>= 1.4) + parser (3.0.0.0) + ast (~> 2.4.1) + pry (0.14.0) + coderay (~> 1.1) + method_source (~> 1.0) + public_suffix (4.0.6) + puma (5.2.2) + nio4r (~> 2.0) + racc (1.5.2) + rack (2.2.3) + rack-protection (2.1.0) + rack + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + regexp_parser (2.1.1) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-support (3.10.2) + ruby2_keywords (0.0.4) + rubyzip (2.3.0) + selenium-webdriver (3.142.7) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) + sinatra (2.1.0) + mustermann (~> 1.0) + rack (~> 2.2) + rack-protection (= 2.1.0) + tilt (~> 2.0) + sprockets (4.0.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + tilt (2.0.10) + timecop (0.8.1) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + uglifier (4.2.0) + execjs (>= 0.3.0, < 3) + unparser (0.6.0) + diff-lcs (~> 1.3) + parser (>= 3.0.0) + webdrivers (4.6.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (>= 3.0, < 4.0) + xpath (3.2.0) + nokogiri (~> 1.8) + zeitwerk (2.4.2) + +PLATFORMS + ruby + +DEPENDENCIES + hyper-spec! + opal + opal-sprockets + pry + puma + rack + rspec + sinatra + +BUNDLED WITH + 2.1.4 diff --git a/ruby/examples/misc/sinatra_app/app.rb b/ruby/examples/misc/sinatra_app/app.rb new file mode 100644 index 000000000..e6897f9be --- /dev/null +++ b/ruby/examples/misc/sinatra_app/app.rb @@ -0,0 +1,32 @@ +require "bundler" +Bundler.require + +module OpalSprocketsServer + def self.opal + @opal ||= Opal::Sprockets::Server.new do |s| + s.append_path "app" + s.main = "application" + s.debug = ENV["RACK_ENV"] != "production" + end + end +end + +get "/" do + <<~HTML + + + + #{Opal::Sprockets.javascript_include_tag('application', debug: OpalSprocketsServer.opal.debug, sprockets: OpalSprocketsServer.opal.sprockets, prefix: '/assets')} + + + HTML +end + +def app + Rack::Builder.app do + map "/assets" do + run OpalSprocketsServer.opal.sprockets + end + run Sinatra::Application + end +end diff --git a/ruby/examples/misc/sinatra_app/app/application.rb b/ruby/examples/misc/sinatra_app/app/application.rb new file mode 100644 index 000000000..42ddc8e00 --- /dev/null +++ b/ruby/examples/misc/sinatra_app/app/application.rb @@ -0,0 +1,3 @@ +require 'opal' + +puts 'hello world' diff --git a/ruby/examples/misc/sinatra_app/config.ru b/ruby/examples/misc/sinatra_app/config.ru new file mode 100644 index 000000000..565f58587 --- /dev/null +++ b/ruby/examples/misc/sinatra_app/config.ru @@ -0,0 +1,3 @@ +require "./app.rb" + +run app diff --git a/ruby/examples/misc/sinatra_app/spec/spec_helper.rb b/ruby/examples/misc/sinatra_app/spec/spec_helper.rb new file mode 100644 index 000000000..5a5daab18 --- /dev/null +++ b/ruby/examples/misc/sinatra_app/spec/spec_helper.rb @@ -0,0 +1,18 @@ +# spec/spec_helper.rb + +require "bundler" +Bundler.require +ENV["RACK_ENV"] ||= "test" + +require File.join(File.dirname(__FILE__), "..", "app.rb") + +require "rspec" +require "rack/test" +require "hyper-spec/rack" + +Capybara.app = HyperSpecTestController.wrap(app: app) + +set :environment, :test +set :run, false +set :raise_errors, true +set :logging, false diff --git a/ruby/examples/misc/sinatra_app/spec/test_spec.rb b/ruby/examples/misc/sinatra_app/spec/test_spec.rb new file mode 100644 index 000000000..d5353b91f --- /dev/null +++ b/ruby/examples/misc/sinatra_app/spec/test_spec.rb @@ -0,0 +1,6 @@ +require "spec_helper" +describe "The App", no_reset: true, js: true do + it "works" do + expect { 12 + 12 }.on_client_to eq 24 + end +end diff --git a/ruby/hyper-component/Gemfile b/ruby/hyper-component/Gemfile index 679fc2462..052aad50c 100644 --- a/ruby/hyper-component/Gemfile +++ b/ruby/hyper-component/Gemfile @@ -4,5 +4,10 @@ gem 'hyper-spec', path: '../hyper-spec' gem 'hyperstack-config', path: '../hyperstack-config' gem 'hyper-store', path: '../hyper-store' gem 'hyper-state', path: '../hyper-state' +# unless ENV['OPAL_VERSION']&.match("0.11") +# gem 'opal-browser', git: 'https://github.com/opal/opal-browser' +# end +gem 'hyper-trace', path: '../hyper-trace' + #gem 'puma', '~> 3.11.0' # As of adding, version 3.12.0 isn't working so we are locking gemspec diff --git a/ruby/hyper-component/hyper-component.gemspec b/ruby/hyper-component/hyper-component.gemspec index 572e55295..9c1c773c4 100644 --- a/ruby/hyper-component/hyper-component.gemspec +++ b/ruby/hyper-component/hyper-component.gemspec @@ -8,45 +8,38 @@ Gem::Specification.new do |spec| spec.authors = ['David Chang', 'Adam Jahn', 'Mitch VanDuyn', 'Jan Biedermann', 'Adam Creekroad'] spec.email = ['mitch@catprint.com'] - spec.homepage = 'http://ruby-hyperloop.org' + spec.homepage = 'http://ruby-hyperstack.org' + spec.metadata = { 'documentation_uri' => 'https://docs.hyperstack.org/' } spec.summary = 'Opal Ruby wrapper of React.js library.' spec.license = 'MIT' spec.description = 'Write React UI components in pure Ruby.' - # spec.metadata = { - # "homepage_uri" => 'http://ruby-hyperloop.org', - # "source_code_uri" => 'https://github.com/ruby-hyperloop/hyper-component' - # } - spec.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(gemfiles|spec)/}) } spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") spec.require_paths = ['lib'] spec.add_dependency 'hyper-state', Hyperstack::Component::VERSION spec.add_dependency 'hyperstack-config', Hyperstack::Component::VERSION - spec.add_dependency 'libv8', '~> 7.3.492.27.1' - spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' spec.add_dependency 'opal-activesupport', '~> 0.3.1' spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-spec', Hyperstack::Component::VERSION spec.add_development_dependency 'jquery-rails' spec.add_development_dependency 'listen' spec.add_development_dependency 'mime-types' + spec.add_development_dependency 'mini_racer', '< 0.4.0' # something is busted with 0.4.0 and its libv8-node dependency spec.add_development_dependency 'nokogiri' spec.add_development_dependency 'opal-jquery' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' - spec.add_development_dependency 'opal-rspec' - spec.add_development_dependency 'pry' + spec.add_development_dependency 'opal-rails' spec.add_development_dependency 'pry-rescue' - spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'pry-stack_explorer' + spec.add_development_dependency 'puma', '<= 5.4.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rails-controller-testing' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec-rails' - spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'rubocop' #, '~> 0.51.0' + spec.add_development_dependency 'sqlite3', '~> 1.4.2' spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyper-component/lib/hyper-component.rb b/ruby/hyper-component/lib/hyper-component.rb index 03cc1e6a0..b68d23cdf 100644 --- a/ruby/hyper-component/lib/hyper-component.rb +++ b/ruby/hyper-component/lib/hyper-component.rb @@ -1,9 +1,9 @@ require 'hyperstack/internal/component' -Hyperstack.import 'hyper-state' Hyperstack.js_import 'react/react-source-browser', client_only: true, defines: %w[ReactDOM React] Hyperstack.js_import 'react/react-source-server', server_only: true, defines: 'React' Hyperstack.import 'browser/delay', client_only: true +Hyperstack.import 'browser/interval', client_only: true Hyperstack.js_import 'react_ujs', defines: 'ReactRailsUJS' Hyperstack.import 'hyper-component' # TODO: confirm this does not break anything. Added while converting hyperloop->hyperstack Hyperstack.import 'hyperstack/component/auto-import' # TODO: confirm we can cancel the import @@ -12,6 +12,7 @@ require 'hyperstack/internal/callbacks' require 'hyperstack/internal/auto_unmount' require 'native' + require 'json' require 'hyperstack/state/observer' require 'hyperstack/internal/component/validator' require 'hyperstack/component/element' @@ -27,6 +28,7 @@ require 'hyperstack/ext/component/boolean' require 'hyperstack/ext/component/array' require 'hyperstack/ext/component/enumerable' + require 'hyperstack/ext/component/time' require 'hyperstack/component/isomorphic_helpers' require 'hyperstack/component/react_api' require 'hyperstack/internal/component/top_level_rails_component' @@ -38,6 +40,7 @@ require 'hyperstack/component/version' else require 'opal' + require 'hyper-state' require 'opal-activesupport' require 'hyperstack/component/version' require 'hyperstack/internal/component/rails' diff --git a/ruby/hyper-component/lib/hyperstack/component.rb b/ruby/hyper-component/lib/hyperstack/component.rb index 40eb43879..cca0597e6 100644 --- a/ruby/hyper-component/lib/hyperstack/component.rb +++ b/ruby/hyper-component/lib/hyperstack/component.rb @@ -1,7 +1,6 @@ require 'hyperstack/ext/component/string' require 'hyperstack/ext/component/hash' require 'active_support/core_ext/class/attribute' -require 'hyperstack/internal/auto_unmount' require 'hyperstack/internal/component/rendering_context' require 'hyperstack/internal/component' require 'hyperstack/internal/component/instance_methods' @@ -19,15 +18,40 @@ def self.included(base) base.include(Hyperstack::Internal::Component::ShouldComponentUpdate) base.class_eval do class_attribute :initial_state - define_callback :before_mount + + method_args_deprecation_check = lambda do |name, sself, proc, *args| + if proc.arity.zero? + args = [] + else + deprecation_warning "In the future #{name} callbacks will not receive any parameters." + end + sself.instance_exec(*args, &proc) + args + end + + define_callback :before_mount, before_call_hook: method_args_deprecation_check define_callback :after_mount - define_callback :before_new_params - define_callback :before_update + define_callback( + :before_new_params, + after_define_hook: lambda do |klass| + klass.deprecation_warning "`before_new_params` has been deprecated. The base "\ + "method componentWillReceiveProps is deprecated in React without replacement" + end + ) + define_callback(:before_update, before_call_hook: method_args_deprecation_check) define_callback :after_update - define_callback :__hyperstack_component_after_render_hook - define_callback :__hyperstack_component_rescue_hook - #define_callback :before_unmount defined already by Async module - define_callback(:after_error) { Hyperstack::Internal::Component::ReactWrapper.add_after_error_hook(base) } + define_callback( + :__hyperstack_component_after_render_hook, + before_call_hook: ->(_, sself, proc, *args) { [*sself.instance_exec(*args, &proc)] } + ) + define_callback( + :__hyperstack_component_rescue_hook, + before_call_hook: ->(_, sself, proc, *args) { sself.instance_exec(*args, &proc) } + ) + define_callback( + :after_error, + after_define_hook: ->(klass) { Hyperstack::Internal::Component::ReactWrapper.add_after_error_hook(klass) } + ) end base.extend(Hyperstack::Internal::Component::ClassMethods) unless `Opal.__hyperstack_component_original_defn` @@ -165,13 +189,25 @@ def waiting_on_resources end def __hyperstack_component_run_post_render_hooks(element) - run_callback(:__hyperstack_component_after_render_hook, element) { |*args| args }.first + run_callback(:__hyperstack_component_after_render_hook, element).first end + def _run_before_render_callbacks + # eventually add before_update if @__component_mounted + # but that will not perfectly match the current React behavior. + # However that behavior is deprecated, and so once we have + # given a chance for the code to be updated we can switch this over + # and switch the deprecation notice to an error. + component_will_mount unless @__component_mounted + @__component_mounted = true + end + + def _render_wrapper + _run_before_render_callbacks observing(rendering: true) do element = Hyperstack::Internal::Component::RenderingContext.render(nil) do - render || '' + render || "" end @__hyperstack_component_waiting_on_resources = element.waiting_on_resources if element.respond_to? :waiting_on_resources diff --git a/ruby/hyper-component/lib/hyperstack/component/children.rb b/ruby/hyper-component/lib/hyperstack/component/children.rb index 5be4cb2cd..0555fdeec 100644 --- a/ruby/hyper-component/lib/hyperstack/component/children.rb +++ b/ruby/hyper-component/lib/hyperstack/component/children.rb @@ -22,7 +22,7 @@ def each(&block) %x{ React.Children.forEach(#{@children}, function(context){ #{ - element = Element.new(`context`) + element = Element.new(`context`, :wrap_child) block.call(element) collection << element } diff --git a/ruby/hyper-component/lib/hyperstack/component/element.rb b/ruby/hyper-component/lib/hyperstack/component/element.rb index dd873f233..f09dcf43a 100644 --- a/ruby/hyper-component/lib/hyperstack/component/element.rb +++ b/ruby/hyper-component/lib/hyperstack/component/element.rb @@ -15,33 +15,73 @@ module Component # by using method missing # class Element - include Native - alias_native :element_type, :type - alias_native :props, :props +# $$typeof: Symbol(react.element) +# key: null +# props: {} +# ref: null +# type: "div" +# _ _owner: null - attr_reader :type + attr_reader :type + + attr_reader :element_type # change this so name does not conflict - change to element type attr_reader :properties attr_reader :block + attr_reader :to_n attr_accessor :waiting_on_resources - def initialize(native_element, type = nil, properties = {}, block = nil) - @type = type + def set_native_attributes(native_element) + @key = `native_element.key` + @props = `native_element.props` + @ref = `native_element.ref` + @type = `native_element.type` + @_owner = `native_element._owner` + @_props_as_hash = Hash.new(@props) + end + + def props + @_props_as_hash + end + + def convert_string(native_element, element_type, props, block) + return native_element unless `native_element['$is_a?']` + return native_element unless native_element.is_a? String + raise "Internal Error Element.new called with string, but non-nil props or block" if !props.empty? || block + + if element_type == :wrap_child + `React.createElement(React.Fragment, null, [native_element])` + else + `React.createElement(native_element, null)` + end + end + + def initialize(native_element, element_type = nil, properties = {}, block = nil) + + native_element = convert_string(native_element, element_type, properties, block) + @element_type = element_type unless element_type == :wrap_child @properties = (`typeof #{properties} === 'undefined'` ? nil : properties) || {} @block = block - @native = native_element + `#{self}.$$typeof = native_element.$$typeof` + @to_n = self + set_native_attributes(native_element) + rescue Exception + end + + def children + `#{@props}.children` end def _update_ref(x) - @ref = x + @_ref = x @_child_element._update_ref(x) if @_child_element end - def ref - return @ref if @ref - raise("The instance of #{self.type} has not been mounted yet") if properties[:ref] - raise("Attempt to get a ref on #{self.type} which is a static component.") + def ref # this will not conflict with React's on ref attribute okay because its $ref!!! + return @_ref if @_ref + raise("The instance of #{self.element_type} has not been mounted yet") if properties[:ref] + raise("Attempt to get a ref on #{self.element_type} which is a static component.") end def dom_node @@ -57,7 +97,7 @@ def on(*event_names, &block) merge_event_prop!(event_name, &block) any_found = true end - @native = `React.cloneElement(#{@native}, #{@properties.shallow_to_n})` if any_found + set_native_attributes(`React.cloneElement(#{self}, #{@properties.shallow_to_n})`) if any_found self end @@ -69,10 +109,10 @@ def render(*props) if props.empty? Hyperstack::Internal::Component::RenderingContext.render(self) else - props = Hyperstack::Internal::Component::ReactWrapper.convert_props(@type, @properties, *props) + props = Hyperstack::Internal::Component::ReactWrapper.convert_props(element_type, @properties, *props) @_child_element = Hyperstack::Internal::Component::RenderingContext.render( - Element.new(`React.cloneElement(#{@native}, #{props.shallow_to_n})`, - type, props, block) + Element.new(`React.cloneElement(#{self}, #{props.shallow_to_n})`, + element_type, props, block) ) end end @@ -80,11 +120,12 @@ def render(*props) # Delete (remove) element from rendering context, the element may later be added back in # using the render method. - def delete + def ~ Hyperstack::Internal::Component::RenderingContext.delete(self) end # Deprecated version of delete method - alias as_node delete + alias as_node ~ + alias delete ~ private @@ -109,7 +150,7 @@ def merge_event_prop!(event_name, &block) merge_built_in_event_prop! name, &block elsif event_name == :enter merge_built_in_event_prop!('onKeyDown') { |evt| yield(evt) if evt.key_code == 13 } - elsif @type.instance_variable_get('@native_import') + elsif element_type.instance_variable_get('@native_import') merge_component_event_prop! name, &block else merge_component_event_prop! "on_#{event_name}", &block diff --git a/ruby/hyper-component/lib/hyperstack/component/event.rb b/ruby/hyper-component/lib/hyperstack/component/event.rb index f8e9fe77f..bbf048e5c 100644 --- a/ruby/hyper-component/lib/hyperstack/component/event.rb +++ b/ruby/hyper-component/lib/hyperstack/component/event.rb @@ -1,7 +1,7 @@ module Hyperstack module Component class Event - include Native + include Native::Wrapper alias_native :bubbles, :bubbles alias_native :cancelable, :cancelable alias_native :current_target, :currentTarget diff --git a/ruby/hyper-component/lib/hyperstack/component/isomorphic_helpers.rb b/ruby/hyper-component/lib/hyperstack/component/isomorphic_helpers.rb index b622d1be1..509a4d2ed 100644 --- a/ruby/hyper-component/lib/hyperstack/component/isomorphic_helpers.rb +++ b/ruby/hyper-component/lib/hyperstack/component/isomorphic_helpers.rb @@ -145,7 +145,6 @@ def eval(js) def send_to_opal(method_name, *args) return unless @ctx - args = [1] if args.length == 0 Hyperstack::Internal::Component::Rails::ComponentLoader.new(@ctx).load! method_args = args.collect do |arg| quarg = "#{arg}".tr('"', "'") @@ -226,8 +225,6 @@ def isomorphic_method(name, &block) end end else - require 'json' - def isomorphic_method(name, &block) self.class.send(:define_method, name) do | *args | IsomorphicHelpers::IsomorphicProcCall.new(name, block, self, *args).result diff --git a/ruby/hyper-component/lib/hyperstack/component/version.rb b/ruby/hyper-component/lib/hyperstack/component/version.rb index 8782c7348..de5a13179 100644 --- a/ruby/hyper-component/lib/hyperstack/component/version.rb +++ b/ruby/hyper-component/lib/hyperstack/component/version.rb @@ -1,5 +1,5 @@ module Hyperstack module Component - VERSION = '1.0.alpha1.5' # '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' # '1.0.alpha1.5' end end diff --git a/ruby/hyper-component/lib/hyperstack/ext/component/time.rb b/ruby/hyper-component/lib/hyperstack/ext/component/time.rb new file mode 100644 index 000000000..5725da652 --- /dev/null +++ b/ruby/hyper-component/lib/hyperstack/ext/component/time.rb @@ -0,0 +1,5 @@ +class Time + def to_json + strftime("%FT%T.%3N%z").to_json + end +end diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/class_methods.rb b/ruby/hyper-component/lib/hyperstack/internal/component/class_methods.rb index d031a543a..1d9a07ab9 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/class_methods.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/class_methods.rb @@ -50,7 +50,7 @@ def before_receive_props(*args, &block) def render(container = nil, params = {}, &block) Tags.included(self) if container - container = container.type if container.is_a? Hyperstack::Component::Element + container = container.element_type if container.is_a? Hyperstack::Component::Element define_method(:__hyperstack_component_render) do __hyperstack_component_select_wrappers do RenderingContext.render(container, params) do diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/haml.rb b/ruby/hyper-component/lib/hyperstack/internal/component/haml.rb index d0cbff23d..cbbd5f610 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/haml.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/haml.rb @@ -28,7 +28,7 @@ def method_missing(class_name, args = {}, &new_block) self, Hyperstack::Internal::Component::RenderingContext.build do Hyperstack::Internal::Component::RenderingContext.render( - type, @properties, args, class: haml_class_name(class_name), &new_block + element_type, @properties, args, class: haml_class_name(class_name), &new_block ) end ) diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rails/component_mount.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rails/component_mount.rb index d8f05f365..10744683d 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rails/component_mount.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rails/component_mount.rb @@ -43,6 +43,9 @@ def top_level_name end def footers + return if @hyperstack_footers_rendered + + @hyperstack_footers_rendered = true Hyperstack::Component::IsomorphicHelpers.prerender_footers(controller) end end diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb index 20f789385..b9159e596 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb @@ -13,7 +13,11 @@ def self.context_instance_for(context) class ContextualRenderer < React::ServerRendering::BundleRenderer def initialize(options = {}) - super(options) + unless v8_runtime? + raise "Hyperstack prerendering only works with MiniRacer. Add 'mini_racer' to your Gemfile" + end + + super({ files: ['hyperstack-prerender-loader.js'] }.merge(options)) ComponentLoader.new(v8_context).load end diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb index bf9ce120a..f253a671e 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb @@ -9,7 +9,9 @@ module Rails module ServerRendering class HyperTestAssetContainer def find_asset(logical_path) - ::Rails.cache.read(logical_path) + # we skip the container if it raises an error so we + # don't care if we are running under hyperspec or not + HyperSpec::Internal::Controller.cache_read(logical_path) end end @@ -24,7 +26,7 @@ def initialize if React::ServerRendering::WebpackerManifestContainer.compatible? @ass_containers << React::ServerRendering::WebpackerManifestContainer.new end - @ass_containers << HyperTestAssetContainer.new if ::Rails.env.test? + @ass_containers << HyperTestAssetContainer.new end def find_asset(logical_path) diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb b/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb index ed3026957..bc0db975c 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb @@ -19,7 +19,7 @@ class ReactWrapper @@component_classes = {} def self.stateless?(ncc) - `typeof #{ncc} === 'function' && !(#{ncc}.prototype && #{ncc}.prototype.isReactComponent)` + `typeof #{ncc} === 'symbol' || (typeof #{ncc} === 'function' && !(#{ncc}.prototype && #{ncc}.prototype.isReactComponent))` end def self.import_native_component(opal_class, native_class) @@ -30,6 +30,7 @@ def self.import_native_component(opal_class, native_class) def self.eval_native_react_component(name) component = `eval(name)` raise "#{name} is not defined" if `#{component} === undefined` + component = `component.default` if `component.__esModule` is_component_class = `#{component}.prototype !== undefined` && (`!!#{component}.prototype.isReactComponent` || @@ -43,9 +44,11 @@ def self.eval_native_react_component(name) def self.native_react_component?(name = nil) return false unless name + eval_native_react_component(name) true - rescue + # Exception to be compatible with all versions of opal + rescue Exception # rubocop:disable Lint/RescueException false end @@ -66,9 +69,9 @@ def self.add_after_error_hook_to_native(native_comp) def self.create_native_react_class(type) raise "createReactClass is undefined. Add the 'react-create-class' npm module, and import it as 'createReactClass'" if `typeof(createReactClass)=='undefined'` raise "Provided class should define `render` method" if !(type.method_defined? :render) - render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render + old_school = !type.method_defined?(:_render_wrapper) + render_fn = old_school ? :render : :_render_wrapper # this was hashing type.to_s, not sure why but .to_s does not work as it Foo::Bar::View.to_s just returns "View" - @@component_classes[type] ||= begin comp = %x{ createReactClass({ @@ -88,7 +91,7 @@ def self.create_native_react_class(type) return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`}; }, propTypes: #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`}, - componentWillMount: function() { + componentWillMount: old_school && function() { if (#{type.method_defined? :component_will_mount}) { this.__opalInstanceSyncSetState = true; this.__opalInstance.$component_will_mount(); @@ -102,7 +105,7 @@ def self.create_native_react_class(type) this.__opalInstance.$component_did_mount(); } }, - componentWillReceiveProps: function(next_props) { + UNSAFE_componentWillReceiveProps: function(next_props) { if (#{type.method_defined? :component_will_receive_props}) { this.__opalInstanceSyncSetState = true; this.__opalInstance.$component_will_receive_props(Opal.Hash.$new(next_props)); @@ -115,7 +118,7 @@ def self.create_native_react_class(type) return this.__opalInstance["$should_component_update?"](Opal.Hash.$new(next_props), Opal.Hash.$new(next_state)); } else { return true; } }, - componentWillUpdate: function(next_props, next_state) { + UNSAFE_componentWillUpdate: function(next_props, next_state) { if (#{type.method_defined? :component_will_update}) { this.__opalInstanceSyncSetState = false; this.__opalInstance.$component_will_update(Opal.Hash.$new(next_props), Opal.Hash.$new(next_state)); diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb index 145ccd6c6..28813f8a3 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb @@ -19,27 +19,41 @@ def quiet_test(component) raise NotQuiet.new("#{component} is waiting on resources") end + def render_string(string) + @buffer ||= [] + @buffer << string + end + def render(name, *args, &block) was_outer_most = !@not_outer_most @not_outer_most = true remove_nodes_from_args(args) - @buffer ||= [] unless @buffer + @buffer ||= [] #unless @buffer if block element = build do saved_waiting_on_resources = nil #waiting_on_resources what was the purpose of this its used below to or in with the current elements waiting_for_resources self.waiting_on_resources = nil - run_child_block(name.nil?, &block) + run_child_block(&block) if name buffer = @buffer.dup ReactWrapper.create_element(name, *args) { buffer }.tap do |element| element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) } element.waiting_on_resources ||= waiting_on_resources if buffer.last.is_a?(String) end - elsif @buffer.last.is_a? Hyperstack::Component::Element - @buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources } else - buffer_s = @buffer.last.to_s - RenderingContext.render(:span) { buffer_s }.tap { |element| element.waiting_on_resources = saved_waiting_on_resources } + buffer = @buffer.collect do |item| + if item.is_a? Hyperstack::Component::Element + item.waiting_on_resources ||= saved_waiting_on_resources + item + else + RenderingContext.render(:span) { item.to_s }.tap { |element| element.waiting_on_resources = saved_waiting_on_resources } + end + end + if buffer.length > 1 + buffer + else + buffer.first + end end end elsif name.is_a? Hyperstack::Component::Element @@ -65,6 +79,7 @@ def build def delete(element) @buffer.delete(element) + @last_deleted = element element end alias as_node delete @@ -86,55 +101,74 @@ def remove_nodes_from_args(args) end if args[0] && args[0].is_a?(Hash) end - # run_child_block gathers the element(s) generated by a child block. + # run_child_block yields to the child rendering block which will put any + # elements to be rendered into the current rendering buffer. + # # for example when rendering this div: div { "hello".span; "goodby".span } # two child Elements will be generated. # - # the final value of the block should either be - # 1 an object that responds to :acts_as_string? - # 2 a string, - # 3 an element that is NOT yet pushed on the rendering buffer - # 4 or the last element pushed on the buffer + # However the block itself will return a value, which in some cases should + # also added to the buffer: + # + # If the final value of the block is a # - # in case 1 we render a span - # in case 2 we automatically push the string onto the buffer - # in case 3 we also push the Element onto the buffer IF the buffer is empty - # case 4 requires no special processing + # a hyper model dummy value that is being loaded, then wrap it in a span and add it to the buffer + # a string (or if the buffer is empty any value), then add it to the buffer + # an Element, then add it on the buffer unless it has been just deleted + # # + # Note that the reason we don't always allow Strings to be automatically pushed is + # to avoid confusing results in situations like this: + # DIV { collection.each { |item| SPAN { item } } } + # If we accepted any object to be rendered this would generate: + # DIV { SPAN { collection[0] } SPAN { collection[n] } collection.to_s } + # which is probably not the desired output. If it was you would just append to_s + # to the end of the expression, to force it to be added to the output buffer. # - # Once we have taken care of these special cases we do a check IF we are in an - # outer rendering scope. In this case react only allows us to generate 1 Element - # so we insure that is the case, and also check to make sure that element in the buffer - # is the element returned + # However if the buffer is empty then it makes sense to automatically apply the `.to_s` + # to the value, and push it on the buffer, unless it is a falsy value or an array - def run_child_block(is_outer_scope) + def run_child_block result = yield - if result.respond_to?(:acts_as_string?) && result.acts_as_string? - # hyper-mesh DummyValues respond to acts_as_string, and must + check_for_component_return(result) + if dummy_value?(result) + # hyper-mesh DummyValues must # be converted to spans INSIDE the parent, otherwise the waiting_on_resources # flag will get set in the wrong context RenderingContext.render(:span) { result.to_s } - elsif result.is_a?(String) || (result.is_a?(Hyperstack::Component::Element) && @buffer.empty?) - @buffer << result + elsif result.is_a?(Hyperstack::Component::Element) + @buffer << result if @buffer.empty? unless @last_deleted == result + elsif pushable_string?(result) + @buffer << result.to_s end - raise_render_error(result) if is_outer_scope && @buffer != [result] + @last_deleted = nil end - # heurestically raise a meaningful error based on the situation - - def raise_render_error(result) - improper_render 'A different element was returned than was generated within the DSL.', - 'Possibly improper use of Element#delete.' if @buffer.count == 1 - improper_render "Instead #{@buffer.count} elements were generated.", - 'Do you want to wrap your elements in a div?' if @buffer.count > 1 - improper_render "Instead the component #{result} was returned.", - "Did you mean #{result}()?" if result.try :hyper_component? - improper_render "Instead the #{result.class} #{result} was returned.", - 'You may need to convert this to a string.' + def check_for_component_return(result) + # check for a common error of saying (for example) DIV (without parens) + # which returns the DIV component class instead of a rendered DIV + return unless result.try :hyper_component? + + Hyperstack::Component::IsomorphicHelpers.log( + "a component's render method returned the component class #{result}, did you mean to say #{result}()", + :warning + ) + end + + def dummy_value?(result) + result.respond_to?(:loading?) && result.loading? + end + + def pushable_string?(result) + # if the buffer is not empty we will only push on strings, and ignore anything else + return result.is_a?(String) unless @buffer.empty? + + # if the buffer IS empty then we can push on anything except we avoid nil, false and arrays + # as these are almost never what you want to render, and if you do there are mechanisms + # to render them explicitly + result && result.respond_to?(:to_n) && !result.is_a?(Array) end def improper_render(message, solution) - raise "a component's render method must generate and return exactly 1 element or a string.\n"\ - " #{message} #{solution}" end end end @@ -143,7 +177,7 @@ def improper_render(message, solution) end class Object - [:span, :td, :th].each do |tag| + %i[span td th].each do |tag| define_method(tag) do |*args, &block| args.unshift(tag) # legacy hyperloop allowed tags to be lower case as well so if self is a component @@ -155,24 +189,26 @@ class Object # in the component. # If we fully deprecate lowercase tags, then this next line can go... return send(*args, &block) if respond_to?(:hyper_component?) && hyper_component? + Hyperstack::Internal::Component::RenderingContext.render(*args) { to_s } end end - def para(*args, &block) args.unshift(:p) # see above comment return send(*args, &block) if respond_to?(:hyper_component?) && hyper_component? + Hyperstack::Internal::Component::RenderingContext.render(*args) { to_s } end def br # see above comment return send(:br) if respond_to?(:hyper_component?) && hyper_component? - Hyperstack::Internal::Component::RenderingContext.render(:span) do - Hyperstack::Internal::Component::RenderingContext.render(to_s) - Hyperstack::Internal::Component::RenderingContext.render(:br) + + Hyperstack::Internal::Component::RenderingContext.render(Hyperstack::Internal::Component::Tags::FRAGMENT) do + Hyperstack::Internal::Component::RenderingContext.render(Hyperstack::Internal::Component::Tags::FRAGMENT) { to_s } + Hyperstack::Internal::Component::RenderingContext.render(Hyperstack::Internal::Component::Tags::BR) end end diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rescue_wrapper.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rescue_wrapper.rb index 429a59780..d3deb43ca 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rescue_wrapper.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rescue_wrapper.rb @@ -27,7 +27,7 @@ class << self after_error do |error, info| args = RescueWrapper.after_error_args || [error, info] - found, * = @Child.run_callback(:__hyperstack_component_rescue_hook, found, *args) { |a| a } + found, * = @Child.run_callback(:__hyperstack_component_rescue_hook, found, *args) unless found RescueWrapper.after_error_args = args raise error diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb b/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb index c8e010a08..d4b5b6e49 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb @@ -32,6 +32,14 @@ module Tags const_set tag.upcase, tag end + const_set "FRAGMENT", ( + Class.new do + include Hyperstack::Component + render {} + Hyperstack::Internal::Component::ReactWrapper.import_native_component(self, `React.Fragment`) + end + ) + # this is used for haml style (i.e. DIV.foo.bar) class tags which is deprecated def self.html_tag_class_for(tag) downcased_tag = tag.downcase @@ -85,6 +93,7 @@ def find_component(name) def lookup_const(name) return nil unless name =~ /^[A-Z]/ + return Hyperstack::Internal::Component::Tags::FRAGMENT if name == "FRAGMENT" scopes = self.class.name.to_s.split('::').inject([Object]) do |nesting, next_const| nesting + [nesting.last.const_get(next_const)] end.reverse diff --git a/ruby/hyper-component/lib/react/react-source.rb b/ruby/hyper-component/lib/react/react-source.rb index 23acdcf1d..cbe9b9f07 100644 --- a/ruby/hyper-component/lib/react/react-source.rb +++ b/ruby/hyper-component/lib/react/react-source.rb @@ -11,7 +11,7 @@ else require "hyperstack/internal/component" require "react/rails/asset_variant" - variant = Hyperstack.env.production? ? 'production' : 'development' - react_directory = React::Rails::AssetVariant.new({environment: variant}).react_directory + variant = Hyperstack.env.production? ? :production : :development + react_directory = React::Rails::AssetVariant.new({ variant: variant }).react_directory Opal.append_path react_directory.untaint end diff --git a/ruby/hyper-component/spec/active_support_spec.rb b/ruby/hyper-component/spec/active_support_spec.rb index 04ca6f3ec..a7614ba5f 100644 --- a/ruby/hyper-component/spec/active_support_spec.rb +++ b/ruby/hyper-component/spec/active_support_spec.rb @@ -18,6 +18,7 @@ def payments GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ]) end end + expect_evaluate_ruby do { Payment.new(5) => 5, Payment.new(15) => 15, Payment.new(10) => 10 } == payments.index_with(&:price) end.to be_truthy @@ -51,7 +52,7 @@ def numbers expect_evaluate_ruby do odd_numbers = numbers.extract! { |number| number.odd? } end.to eq [1, 3, 5, 7, 9] - expect_evaluate_ruby do + expect_evaluate_ruby do numbers.extract! { |number| number.odd? } numbers end.to eq [0, 2, 4, 6, 8] diff --git a/ruby/hyper-component/spec/client_features/children_spec.rb b/ruby/hyper-component/spec/client_features/children_spec.rb index 064f92c2c..ea0034d37 100644 --- a/ruby/hyper-component/spec/client_features/children_spec.rb +++ b/ruby/hyper-component/spec/client_features/children_spec.rb @@ -26,7 +26,7 @@ def self.get_children it 'is enumerable' do expect_evaluate_ruby do - InitTest.get_children.map { |elem| elem.element_type } + InitTest.get_children.map { |elem| elem.type } end.to eq(['a', 'li']) end @@ -40,7 +40,7 @@ def self.get_children describe '#each' do it 'returns an array of elements' do expect_evaluate_ruby do - nodes = InitTest.get_children.each { |elem| elem.element_type } + nodes = InitTest.get_children.each { |elem| elem.type } [nodes.class.name, nodes.map(&:class)] end.to eq(["Array", ["Hyperstack::Component::Element", "Hyperstack::Component::Element"]]) end @@ -80,7 +80,7 @@ def self.get_children it 'is enumerable containing single element' do expect_evaluate_ruby do - InitTest.get_children.map { |elem| elem.element_type } + InitTest.get_children.map { |elem| elem.type } end.to eq(["a"]) end @@ -117,7 +117,7 @@ def self.get_children it 'is enumerable containing no elements' do expect_evaluate_ruby do - InitTest.get_children.map { |elem| elem.element_type } + InitTest.get_children.map { |elem| elem.type } end.to eq([]) end diff --git a/ruby/hyper-component/spec/client_features/component_spec.rb b/ruby/hyper-component/spec/client_features/component_spec.rb index f3437bffd..adeca9003 100644 --- a/ruby/hyper-component/spec/client_features/component_spec.rb +++ b/ruby/hyper-component/spec/client_features/component_spec.rb @@ -122,7 +122,7 @@ class Foo mount 'Foo' do class Foo include Hyperstack::Component - before_mount { @count = 0} + before_mount { @count = 0 } after_render do @count += 1 after(0) { force_update! } if @count <= 2 @@ -143,7 +143,7 @@ class ErrorFoo raise 'ErrorFoo Error' end end - Foo.class_eval do + class Foo def self.get_error @@error end @@ -163,7 +163,7 @@ def self.get_info end end expect_evaluate_ruby('Foo.get_error').to eq('ErrorFoo Error') - expect_evaluate_ruby('Foo.get_info').to eq("\n in ErrorFoo\n in div\n in Foo\n in Hyperstack::Internal::Component::TopLevelRailsComponent") + expect_evaluate_ruby('Foo.get_info').to eq("\n in ErrorFoo (created by Foo)\n in div (created by Foo)\n in Foo (created by Hyperstack::Internal::Component::TopLevelRailsComponent)\n in Hyperstack::Internal::Component::TopLevelRailsComponent") end end @@ -208,6 +208,87 @@ class << self expect_evaluate_ruby('Foo.instance == Foo.instance.force_update!').to be_truthy end + it "can generate multiple elements on outer render using FRAGMENT" do + mount 'Foo' do + class Foo < Hyperloop::Component + render(FRAGMENT) do + UL do + SomeLIs() + LI { "the end" } + end + "random string at the end" + end + end + class SomeLIs < Hyperloop::Component + render(FRAGMENT) { LI { "hello" }; LI { "goodby" } } + end + end + expect(page.find('ul').all('li').collect(&:text)).to eq(['hello', 'goodby', 'the end']) + expect(page.find('div').text).to end_with("random string at the end") + end + + it "can generate multiple elements on outer render by rendering multiple values" do + mount 'Foo' do + class Foo < Hyperloop::Component + render do + UL(key: 1) do + SomeLIs() + LI(key: 3) { "the end" } + end + "random string at the end".span(key: 2) + end + end + class SomeLIs < Hyperloop::Component + render { LI(key: 1) { "hello" }; LI(key: 2) { "goodby" } } + end + end + expect(page.find('ul').all('li').collect(&:text)).to eq(['hello', 'goodby', 'the end']) + expect(page.find('div').text).to end_with("random string at the end") + end + + it "fragments can be nested and have keys" do + mount 'Foo' do + class Foo < Hyperloop::Component + render do + UL(key: 1) do + 3.times do |i| + FRAGMENT(key: i) do + LI { "first #{i}" } + LI { "second #{i}" } + end + end + end + end + end + end + expect(page.find('ul').all('li').collect(&:text)).to eq([*0..2].collect { |i| ["first #{i}", "second #{i}"] }.flatten) + end + + xit "will only render once" do # see issue #329 + mount "Parent" do + class Child + include Hyperstack::Component + param_accessor_style :accessors + param :do_something + render do + puts "child: #{do_something.object_id}" + end + end + class Parent + include Hyperstack::Component + param_accessor_style :accessors + before_mount do + @do_something = -> { puts "we did it!" } + after(2) { force_update! } + end + render do + puts "parent: #{@do_something.object_id}" + Child(do_something: @do_something) + end + end + end + end + it 'can buffer an element' do mount 'Foo' do class Bar < Hyperloop::Component @@ -216,7 +297,7 @@ class Bar < Hyperloop::Component end class Foo < Hyperloop::Component render do - Bar.insert_element(p: "param") { "child"} + Bar.insert_element(p: "param") { "child" } end end end @@ -237,6 +318,26 @@ class Foo < Hyperloop::Component expect(page).to have_content("paramchildparamchild") end + it "will convert only the final value to a string if the buffer is empty" do + mount 'Foo' do + class Foo < Hyperloop::Component + render { {'foo' => 'bar'} } + end + end + expect(page).to have_content("#{{'foo' => 'bar'}}") + end + + it "will convert only the final value to a string if the buffer is empty" do + # note that the spec 'can create an element without buffering' effectively + # checks other cases where the return value is elements have been rendered to the buffer + mount 'Foo' do + class Foo < Hyperloop::Component + render { DIV { SPAN { 'foo-' }; 'bar' } } + end + end + expect(page).to have_content("foo-bar") + end + it 'can receive and render a component class' do mount 'Baz' do class Bar < Hyperloop::Component @@ -441,7 +542,7 @@ class Lorem; end end Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo, bar: 10, lorem: Lorem.new) end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Warning: Failed prop( type|Type): In component `Foo`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String/) end @@ -459,13 +560,13 @@ class Lorem; end end Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo, foo: 10, bar: '10', lorem: Lorem.new) end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")).to_not match(/prop/) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")).to_not match(/prop/) end end describe 'Default props' do it 'sets default props using validation helper' do - on_client do + before_mount do class Foo include Hyperstack::Component params do @@ -474,14 +575,14 @@ class Foo end render do - DIV { @Foo + '-' + @Bar} + DIV(class: :foo) { @Foo + '-' + @Bar} end end end mount 'Foo' - expect(page.body[-40..-19]).to include("
    foo-bar
    ") + expect(find('div.foo')).to have_content('foo-bar') mount 'Foo', foo: 'lorem' - expect(page.body[-40..-19]).to include("
    lorem-bar
    ") + expect(find('div.foo')).to have_content('lorem-bar') end end end @@ -493,10 +594,10 @@ class Foo foo.class_eval do render { "hello" } end - + Hyperstack::Component::ReactTestUtils.render_component_into_document(foo) end - expect(page.driver.browser.manage.logs.get(:browser) + expect(page.driver.browser.logs.get(:browser) .reject { |entry| entry.to_s.include?('Deprecated feature') } .reject { |entry| entry.to_s.include?('Object freezing is not supported by Opal')} .map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n").size) @@ -505,41 +606,14 @@ class Foo end describe 'Render Error Handling' do - it "will generate a message if render returns something other than an Element or a String" do - mount 'Foo' do - class Foo < Hyperloop::Component - render { Hash.new } - end - end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) - .to match(/You may need to convert this to a string./) - end it "will generate a message if render returns a Component class" do mount 'Foo' do class Foo < Hyperloop::Component render { Foo } end end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) - .to match(/Did you mean Foo()/) - end - it "will generate a message if more than 1 element is generated" do - mount 'Foo' do - class Foo < Hyperloop::Component - render { "hello".span; "goodby".span } - end - end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) - .to match(/Do you want to wrap your elements in a div?/) - end - it "will generate a message if the element generated is not the element returned" do - mount 'Foo' do - class Foo < Hyperloop::Component - render { "hello".span; "goodby".span.delete } - end - end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) - .to match(/Possibly improper use of Element#delete./) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + .to match(/did you mean to say Foo()/) end end @@ -581,7 +655,14 @@ class Foo end end end - expect(page.body).to include('
    ') + expect( + JSON.parse( + find( + 'div[data-react-class="Hyperstack.Internal.Component.TopLevelRailsComponent"]', + visible: false + )['data-react-props'] + ).with_indifferent_access + ).to include render_params: {}, component_name: 'Foo', controller: 'HyperSpecTest' end end @@ -657,10 +738,17 @@ def needs_update?(next_params, next_state) expect_evaluate_ruby do @foo = Foo.new(nil) return_values = [] - EMPTIES.each do |empty1| - EMPTIES.each do |empty2| - @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return #{empty1};}bla();") } - return_values << @foo.should_component_update?({}, Hash.new(empty2)) + EMPTIES.length.times do |i| + EMPTIES.length.times do |j| + e1 = EMPTIES[i] + @foo.instance_eval do + # semantically check if we are using Opal 1.0 or better + # if so we need to stringify e1 + e1 = `JSON.stringify(e1)` if 24 == `12+12` + @native.JS[:state] = + JS.call(:eval, "function bla(){return #{e1};}bla();") + end + return_values << @foo.should_component_update?({}, Hash.new(EMPTIES[j])) end end return_values @@ -668,11 +756,19 @@ def needs_update?(next_params, next_state) end it "returns true if old state is empty, but new state is not" do + expect_evaluate_ruby do @foo = Foo.new(nil) return_values = [] - EMPTIES.each do |empty| - @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return #{empty};}bla();") } + EMPTIES.length.times do |i| + empty = EMPTIES[i] + @foo.instance_eval do + # semantically check if we are using Opal 1.0 or better + # if so we need to stringify e1 + empty = `JSON.stringify(empty)` if 24 == `12+12` + @native.JS[:state] = + JS.call(:eval, "function bla(){return #{empty};}bla();") + end return_values << @foo.should_component_update?({}, {foo: 12}) end return_values @@ -763,7 +859,7 @@ class Foo end expect_evaluate_ruby("CHILDREN.class.name").to eq('Hyperstack::Component::Children') expect_evaluate_ruby("CHILDREN.count").to eq(2) - expect_evaluate_ruby("CHILDREN.map(&:element_type)").to eq(['a', 'li']) + expect_evaluate_ruby("CHILDREN.map(&:type)").to eq(['a', 'li']) end it 'returns an empty Enumerator if there are no children' do diff --git a/ruby/hyper-component/spec/client_features/dsl_spec.rb b/ruby/hyper-component/spec/client_features/dsl_spec.rb index 6b82a9329..68daab280 100644 --- a/ruby/hyper-component/spec/client_features/dsl_spec.rb +++ b/ruby/hyper-component/spec/client_features/dsl_spec.rb @@ -55,7 +55,7 @@ class Foo expect(page.body[-60..-19]).to include('
    hello
    ') end - it "in prerender will pass converted props through event handlers" do + it "in prerender will pass converted props through event handlers", :prerendering_on do client_option render_on: :both mount 'Foo' do class Foo @@ -65,6 +65,7 @@ class Foo end end end + expect(find('input')['data-reactroot']).to eq('') expect(find('input')['data-foo']).to eq('12') end @@ -93,18 +94,22 @@ class Foo expect(page.body[-70..-19]).to include('
    hellogoodby
    ') end - it "in prerendering has a .br short hand String method" do - client_option render_on: :both + it "in prerendering has a .br short hand String method", :prerendering_on do + client_option render_on: :server_only client_option raise_on_js_errors: :off mount 'Foo' do class Foo include Hyperstack::Component render do - DIV { "hello".br } + DIV(class: :foo) { "hello".br } end end end - expect(page.body[-285..-233]).to match(/(
    hello<(br|br\/|br \/)><\/span><\/div>/) + expect( + find( + 'div[data-react-class="Hyperstack.Internal.Component.TopLevelRailsComponent"] div.foo' + )['innerHTML'] + ).to eq 'hello
    ' end it "has a .td short hand String method" do @@ -177,11 +182,11 @@ class NestedComp < Hyperloop::Component class Comp; end end end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Comp does not appear to be a react component./) end - it 'raises a method missing error' do + it 'raises a method missing error', :prerendering_on do client_option render_on: :both client_option raise_on_js_errors: :off expect_evaluate_ruby do @@ -233,18 +238,27 @@ class Foo expect(page.body[-60..-19]).to include('hello') end - it "can generate a unrendered node using the .as_node method" do # DIV { "hello" }.as_node + it "can generate a unrendered node using the ~ operator" do mount 'Foo' do class Foo include Hyperstack::Component render do - SPAN(data: {size: 12}) { "hello".span.as_node.class.name }.as_node.render + (~SPAN(data: {size: 12}) { (~"hello".span).class.name }).render end end end expect(page.body[-80..-19]).to include('Hyperstack::Component::Element') end + it "~ operator removes the node from the render buffer" do + mount 'Foo' do + class Foo < Hyperloop::Component + render { "hello".span; ~"goodby".span } + end + end + expect(find('span').text).to eq('hello') + end + it "has a dom_node method" do mount 'Foo' do class Foo diff --git a/ruby/hyper-component/spec/client_features/element_spec.rb b/ruby/hyper-component/spec/client_features/element_spec.rb index b4390dc28..1ba9c8eac 100644 --- a/ruby/hyper-component/spec/client_features/element_spec.rb +++ b/ruby/hyper-component/spec/client_features/element_spec.rb @@ -18,7 +18,7 @@ end describe "Event Subscription" do - it "keeps the original params, and ignores false, nil, and blank event names" do + it "keeps the original params, and ignores false, nil, and blank event names", :prerendering_on do client_option render_on: :both mount 'Foo' do class Foo diff --git a/ruby/hyper-component/spec/client_features/native_library_spec.rb b/ruby/hyper-component/spec/client_features/native_library_spec.rb index 2b1f17f3c..1d54d80ba 100644 --- a/ruby/hyper-component/spec/client_features/native_library_spec.rb +++ b/ruby/hyper-component/spec/client_features/native_library_spec.rb @@ -171,7 +171,7 @@ class Foo < Hyperstack::Component::NativeLibrary rename "MispelledComponent" => "Bar" end end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/NativeLibrary.MispelledComponent is undefined/) # TODO was testing for cannot import, but that message gets trunkated end diff --git a/ruby/hyper-component/spec/client_features/opal_jquery_extensions_spec.rb b/ruby/hyper-component/spec/client_features/opal_jquery_extensions_spec.rb index bc1e37332..089333340 100644 --- a/ruby/hyper-component/spec/client_features/opal_jquery_extensions_spec.rb +++ b/ruby/hyper-component/spec/client_features/opal_jquery_extensions_spec.rb @@ -86,11 +86,12 @@ class Foo < Hyperloop::Component end it "accepts plain js object as selector" do - evaluate_ruby do + expect_evaluate_ruby do Element[JS.call(:eval, "(function () { return window; })();")] - end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) - .not_to match(/Exception|Error/) + true + end.to be_truthy + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + .not_to match(/Exception|Error/) end it "can dynamically mount components" do @@ -110,9 +111,10 @@ class MountPoint < Hyperloop::Component end end end - evaluate_ruby do + expect_evaluate_ruby do Element['body'].mount_components - end + true + end.to be_truthy expect(page).to have_content('I got rendered') end end diff --git a/ruby/hyper-component/spec/client_features/param_declaration_spec.rb b/ruby/hyper-component/spec/client_features/param_declaration_spec.rb index 2df289b69..7d159fb2a 100644 --- a/ruby/hyper-component/spec/client_features/param_declaration_spec.rb +++ b/ruby/hyper-component/spec/client_features/param_declaration_spec.rb @@ -7,11 +7,11 @@ class Foo < Hyperloop::Component collect_other_params_as :foo render do - DIV { @Foo[:bar] } + DIV(class: :foo) { @Foo[:bar] } end end end - expect(page.body[-35..-19]).to include("
    biz
    ") + expect(find('div.foo')).to have_content 'biz' end it 'can override PropsWrapper.instance_var_name' do @@ -28,11 +28,11 @@ class Foo < Hyperloop::Component collect_other_params_as :foo render do - DIV { @foo[:bar] } + DIV(class: :foo) { @foo[:bar] } end end end - expect(page.body[-35..-19]).to include("
    biz
    ") + expect(find('div.foo')).to have_content 'biz' end it 'defines collect_other_params_as method on params proxy' do @@ -66,11 +66,11 @@ class Foo < Hyperloop::Component param :foo render do - DIV { @Foo } + DIV(class: :foo) { @Foo } end end end - expect(page.body[-35..-19]).to include("
    bar
    ") + expect(find('div.foo')).to have_content 'bar' end it "can give a param an accessor alias" do @@ -79,11 +79,11 @@ class Foo < Hyperloop::Component param :foo, alias: :bar render do - DIV { @bar } + DIV(class: :foo) { @bar } end end end - expect(page.body[-35..-19]).to include("
    bar
    ") + expect(find('div.foo')).to have_content 'bar' end it "can create and access an optional params" do @@ -125,7 +125,7 @@ class Foo < Hyperloop::Component end end expect(page.body[-60..-19]).to include('
    12-string
    ') - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo1` could not be converted to String/) end @@ -155,7 +155,7 @@ class Foo2 < Hyperloop::Component Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo2, bar: 10, lorem: Lorem.new) end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Warning: Failed prop( type|Type): In component `Foo2`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String/) end @@ -171,7 +171,7 @@ class Foo < Hyperloop::Component end Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo, foo: 10, bar: '10', lorem: Lorem.new) end - expect(page.driver.browser.manage.logs.get(:browser).reject { |m| m.message =~ /(D|d)eprecated/ }.map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).reject { |m| m.message =~ /(D|d)eprecated/ }.map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .not_to match(/Warning|Error/) end @@ -191,7 +191,7 @@ class Foo < Hyperloop::Component param :bar, type: [] end end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo` could not be converted to Array/) end @@ -202,7 +202,7 @@ class Foo < Hyperloop::Component param :bar, type: [String] end end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo`\[0\] could not be converted to String/) end @@ -227,7 +227,7 @@ def self._react_param_conversion(json, validate_only) end end expect(page.body[-60..-19]).to include('1, 2') - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo` could not be converted to BazWoggle/) end diff --git a/ruby/hyper-component/spec/client_features/react_spec.rb b/ruby/hyper-component/spec/client_features/react_spec.rb index 58f9e9261..224c04bfd 100644 --- a/ruby/hyper-component/spec/client_features/react_spec.rb +++ b/ruby/hyper-component/spec/client_features/react_spec.rb @@ -11,7 +11,6 @@ end it "should return false is passed a non React element" do - expect_evaluate_ruby do element = Hyperstack::Component::Element.new(JS.call(:eval, "{}")) Hyperstack::Component::ReactAPI.is_valid_element?(element) @@ -35,9 +34,20 @@ ELEMENT = Hyperstack::Component::ReactAPI.create_element('div') { "lorem ipsum" } end expect_evaluate_ruby("Hyperstack::Component::ReactAPI.is_valid_element?(ELEMENT)").to eq(true) - expect_evaluate_ruby("ELEMENT.props.children").to eq("lorem ipsum") + expect_evaluate_ruby("ELEMENT.children").to eq("lorem ipsum") end + it "should create a valid element with children supplied as a proc" do + + evaluate_ruby do + children = -> { "lorem ipsum" } + ELEMENT = Hyperstack::Component::ReactAPI.create_element('div', &children) + end + expect_evaluate_ruby("Hyperstack::Component::ReactAPI.is_valid_element?(ELEMENT)").to eq(true) + expect_evaluate_ruby("ELEMENT.children").to eq("lorem ipsum") + end + + it "should create a valid element with children as array when block yield Array of element" do evaluate_ruby do @@ -46,7 +56,7 @@ end end expect_evaluate_ruby("Hyperstack::Component::ReactAPI.is_valid_element?(ELEMENT)").to eq(true) - expect_evaluate_ruby("ELEMENT.props.children.length").to eq(3) + expect_evaluate_ruby("ELEMENT.children.length").to eq(3) end it "should render element with children as array when block yield Array of element" do @@ -88,7 +98,7 @@ def props true end expect_evaluate_ruby("INSTANCE.props[:children].is_a?(Array)").to be_falsy - expect_evaluate_ruby("INSTANCE.props[:children][:type]").to eq("span") + expect_evaluate_ruby("INSTANCE.props[:children].type").to eq("span") end it "should render element with more than one children correctly" do @@ -116,7 +126,7 @@ def props param :foo end element = Hyperstack::Component::ReactAPI.create_element(Foo, foo: "bar") - element.props.foo + element.props[:foo] end.to eq("bar") end @@ -155,7 +165,7 @@ def component_will_mount it "should match the instance cycle to ReactComponent life cycle" do expect_evaluate_ruby do - Foo.class_eval do + class Foo def initialize(native) @@count ||= 0 @@count += 1 @@ -180,7 +190,7 @@ def self.count expect_evaluate_ruby do element = Hyperstack::Component::ReactAPI.create_element("div", class_name: "foo") - element.props.className + element.props[:className] end.to eq("foo") end @@ -188,7 +198,7 @@ def self.count expect_evaluate_ruby do element = Hyperstack::Component::ReactAPI.create_element("div", foo: "bar") - element.props.foo + element.props[:foo] end.to eq("bar") end @@ -196,7 +206,7 @@ def self.count expect_evaluate_ruby do element = Hyperstack::Component::ReactAPI.create_element("div", foo_bar: "foo") - element.props.foo_bar + element.props[:foo_bar] end.to eq("foo") end end @@ -206,7 +216,7 @@ def self.count expect_evaluate_ruby do element = Hyperstack::Component::ReactAPI.create_element("div", class_name: "foo bar") - element.props.className + element.props[:className] end.to eq("foo bar") end end diff --git a/ruby/hyper-component/spec/client_features/refs_element_method_spec.rb b/ruby/hyper-component/spec/client_features/refs_element_method_spec.rb index ac0e9530e..5f2978d3c 100644 --- a/ruby/hyper-component/spec/client_features/refs_element_method_spec.rb +++ b/ruby/hyper-component/spec/client_features/refs_element_method_spec.rb @@ -76,7 +76,6 @@ class AnotherComponent < HyperComponent render(DIV) { "another component named #{@Others[:foo]}"} end class TestComponent < HyperComponent - after_mount { debugger unless @ref_1.ref == @ref_2.ref; nil } render do @ref_1 = AnotherComponent().as_node @ref_2 = @ref_1.render(foo: :bar) diff --git a/ruby/hyper-component/spec/client_features/server_spec.rb b/ruby/hyper-component/spec/client_features/server_spec.rb index 6ff1774f8..d57c05b2f 100644 --- a/ruby/hyper-component/spec/client_features/server_spec.rb +++ b/ruby/hyper-component/spec/client_features/server_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe 'React::Server', js: true do +describe 'React::Server', :js, :prerendering_on do describe "render_to_string" do it "should render a React.Element to string" do diff --git a/ruby/hyper-component/spec/client_features/sleep_spec.rb b/ruby/hyper-component/spec/client_features/sleep_spec.rb index d7515fe42..bda6505fe 100644 --- a/ruby/hyper-component/spec/client_features/sleep_spec.rb +++ b/ruby/hyper-component/spec/client_features/sleep_spec.rb @@ -5,7 +5,7 @@ [1, 3].each do |t| start_time = Time.parse(evaluate_ruby("Time.now")) evaluate_promise "sleep(#{t})" - expect(Time.now-start_time).to be_between(t, t+1) + expect(Time.now-start_time).to be_between(t, t+1.5) end end diff --git a/ruby/hyper-component/spec/client_features/state_spec.rb b/ruby/hyper-component/spec/client_features/state_spec.rb index 3c0a6db35..29d0b9b40 100644 --- a/ruby/hyper-component/spec/client_features/state_spec.rb +++ b/ruby/hyper-component/spec/client_features/state_spec.rb @@ -16,7 +16,7 @@ class << self end.to eq('bar') end - it 'ignores state updates during rendering' do + it 'ignores state updates during rendering', :prerendering_on do client_option render_on: :both evaluate_ruby do class StateTest < Hyperloop::Component @@ -40,7 +40,7 @@ class << self end expect_evaluate_ruby("MARKUP").to eq('Boom') expect_evaluate_ruby("StateTest.boom").to be_falsy - expect(page.driver.browser.manage.logs.get(:browser).reject { |entry| + expect(page.driver.browser.logs.get(:browser).reject { |entry| entry_s = entry.to_s entry_s.include?("Deprecated feature") || entry_s.include?("Mount() on the server. This is a no-op.") || diff --git a/ruby/hyper-component/spec/deprecated_features/param_declaration_legacy_spec.rb b/ruby/hyper-component/spec/deprecated_features/param_declaration_legacy_spec.rb index b80ca8392..d767b7b1b 100644 --- a/ruby/hyper-component/spec/deprecated_features/param_declaration_legacy_spec.rb +++ b/ruby/hyper-component/spec/deprecated_features/param_declaration_legacy_spec.rb @@ -49,7 +49,7 @@ def setup element = Hyperstack::Component::ReactAPI.create_element(Foo).on(:foo_submit) { 'bar' } Hyperstack::Component::ReactTestUtils.render_into_document(element) end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to_not match(/Exception raised/) end @@ -73,7 +73,7 @@ def setup element = Hyperstack::Component::ReactAPI.create_element(Foo).on(:foo_invoked) { return [1,2,3], 'bar' } Hyperstack::Component::ReactTestUtils.render_into_document(element) end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to_not match(/Exception raised/) end end @@ -102,7 +102,7 @@ class Foo end end end - + it 'defines collect_other_params_as method on params proxy' do mount 'Foo', bar: 'biz' do class Foo < Hyperloop::Component @@ -110,11 +110,11 @@ class Foo < Hyperloop::Component collect_other_params_as :foo render do - DIV { params.foo[:bar] } + DIV(class: :foo) { params.foo[:bar] } end end end - expect(page.body[-35..-19]).to include("
    biz
    ") + expect(find('div.foo')).to have_content 'biz' end it 'defines collect_other_params_as method on params proxy' do @@ -151,11 +151,11 @@ class Foo < Hyperloop::Component param :foo render do - DIV { params.foo } + DIV(class: :foo) { params.foo } end end end - expect(page.body[-35..-19]).to include("
    bar
    ") + expect(find('div.foo')).to have_content 'bar' end it "can create and access an optional params" do @@ -199,7 +199,7 @@ class Foo < Hyperloop::Component end end expect(page.body[-60..-19]).to include('
    12-string
    ') - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo1` could not be converted to String/) end @@ -216,7 +216,7 @@ class Foo2 < Hyperloop::Component Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo2, bar: 10, lorem: Lorem.new) end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Warning: Failed prop( type|Type): In component `Foo2`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String/) end @@ -233,7 +233,7 @@ class Foo < Hyperloop::Component end Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo, foo: 10, bar: '10', lorem: Lorem.new) end - expect(page.driver.browser.manage.logs.get(:browser).reject { |m| m.message =~ /(D|d)eprecated/ }.map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).reject { |m| m.message =~ /(D|d)eprecated/ }.map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .not_to match(/Warning|Error/) end @@ -254,7 +254,7 @@ class Foo < Hyperloop::Component param :bar, type: [] end end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo` could not be converted to Array/) end @@ -265,7 +265,7 @@ class Foo < Hyperloop::Component param :bar, type: [String] end end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo`\[0\] could not be converted to String/) end @@ -290,7 +290,7 @@ def self._react_param_conversion(json, validate_only) end end expect(page.body[-60..-19]).to include('1, 2') - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) + expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) .to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo` could not be converted to BazWoggle/) end diff --git a/ruby/hyper-component/spec/deprecation_notices/render.rb b/ruby/hyper-component/spec/deprecation_notices/render.rb deleted file mode 100644 index 5a6c55d7f..000000000 --- a/ruby/hyper-component/spec/deprecation_notices/render.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe 'Deprecation Notices', js: true do - - it "using `defx render` will give a deprecation notice, but still allow render to work" do - mount "TestComp" do - class TestComp < HyperComponent - def render - 'hello' - end - end - end - binding.pry - expect(page).to have_content('hello') - expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( - ["Warning: Deprecated feature used in TestComp. Do not directly define the render method. Use the render macro instead."] - ) - end -end diff --git a/ruby/hyper-component/spec/deprecation_notices/render_spec.rb b/ruby/hyper-component/spec/deprecation_notices/render_spec.rb new file mode 100644 index 000000000..dc79cf031 --- /dev/null +++ b/ruby/hyper-component/spec/deprecation_notices/render_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe 'Deprecation Notices', js: true do + + it "using `def render` will give a deprecation notice, but still allow render to work" do + mount "TestComp" do + class TestComp < HyperComponent + def render + 'hello' + end + end + end + expect(page).to have_content('hello') + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( + ["Warning: Deprecated feature used in TestComp. Do not directly define the render method. Use the render macro instead."] + ) + end + + it "when using before_new_params" do + mount "TestComp" do + class TestComp < HyperComponent + before_new_params { 'bingo' } + render { 'hello' } + end + end + expect(page).to have_content("hello") + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( + [ + "Warning: Deprecated feature used in TestComp. `before_new_params` has been deprecated. "\ + "The base method componentWillReceiveProps is deprecated in React without replacement" + ] + ) + end + + %i[mount update].each do |callback_name| + context "when params are expected in the before_#{callback_name} callback" do + + before(:all) do + before_mount { CALLBACK_NAME = "before_#{callback_name}" } + end + + it "no errors if no params" do + mount "TestComp" do + class TestComp < HyperComponent + def no_params_please + @message = "hello" + end + send(CALLBACK_NAME, :no_params_please) + after_mount { mutate @message = "goodby" unless @message } + render { @message } + end + end + expect(page).to have_content('hello') + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to be_nil + end + + it "when providing a block" do + mount "TestComp" do + class TestComp < HyperComponent + send(CALLBACK_NAME) { |x, y| @message = "hello" } + after_mount { mutate @message = "goodby" unless @message } + render { @message } + end + end + expect(page).to have_content('hello') + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( + ["Warning: Deprecated feature used in TestComp. In the future before_#{callback_name} callbacks will not receive any parameters."] + ) + end + + it "when providing a method name" do + mount "TestComp" do + class TestComp < HyperComponent + def foo(x, y=nil) # so it works with both before_mount (1 arg) and before_update (2 args) + @message = "hello" + end + send(CALLBACK_NAME, :foo) + after_mount { mutate @message = "goodby" unless @message} + render { @message } + end + end + expect(page).to have_content('hello') + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( + ["Warning: Deprecated feature used in TestComp. In the future before_#{callback_name} callbacks will not receive any parameters."] + ) + end + end + end +end diff --git a/ruby/hyper-component/spec/isomorphic/isomorphic_helpers_spec.rb b/ruby/hyper-component/spec/isomorphic/isomorphic_helpers_spec.rb index 623460fd6..f12b0bf3a 100644 --- a/ruby/hyper-component/spec/isomorphic/isomorphic_helpers_spec.rb +++ b/ruby/hyper-component/spec/isomorphic/isomorphic_helpers_spec.rb @@ -81,11 +81,14 @@ def test_context(files = nil) end def react_context - if ::Rails.application.assets['react-server.js'] - test_context(['server_rendering.js', 'react-server.js']) - else - test_context(['components', 'react.js']) - end + test_context(['hyperstack-prerender-loader.js']) + # TODO: remove following once we are sure everything works.... looks like legacy + # from some past history... + # if ::Rails.application.assets['react-server.js'] + # test_context(['server_rendering.js', 'react-server.js']) + # else + # test_context(['components', 'react.js']) + # end end let(:v8_context) { TestV8Context.new } @@ -129,7 +132,7 @@ def self.valediction end ]} - it 'raises an error when react cannot be loaded' do + it 'raises an error when react cannot be loaded', :prerendering_on do context = described_class.new('unique-id', v8_context, controller, name) context.instance_variable_set(:@ctx, test_context) expect { @@ -137,7 +140,7 @@ def self.valediction }.to raise_error(/No Hyperstack components found/) end - it 'executes method with args inside opal rubyracer context' do + it 'executes method with args inside opal rubyracer context', :prerendering_on do ctx = react_context context = described_class.new('unique-id', ctx, controller, name) context.eval(opal_code) @@ -145,7 +148,7 @@ def self.valediction expect(result).to eq('Hello, world!') end - it 'executes the method inside opal rubyracer context' do + it 'executes the method inside opal rubyracer context', :prerendering_on do ctx = react_context context = described_class.new('unique-id', ctx, controller, name) context.eval(opal_code) diff --git a/ruby/hyper-component/spec/isomorphic/rails/component_mount_spec.rb b/ruby/hyper-component/spec/isomorphic/rails/component_mount_spec.rb index 42470e37f..44b89a824 100644 --- a/ruby/hyper-component/spec/isomorphic/rails/component_mount_spec.rb +++ b/ruby/hyper-component/spec/isomorphic/rails/component_mount_spec.rb @@ -13,7 +13,7 @@ expect(html).to match(/<\/div>/) end - it 'accepts a pre-render option' do + it 'accepts a pre-render option', :prerendering_on do html = helper.react_component('Components::HelloWorld', {}, prerender: true) expect(html).to match(/Hello, World!<\/span><\/div>/) end diff --git a/ruby/hyper-component/spec/isomorphic/server_rendering/contextual_renderer_spec.rb b/ruby/hyper-component/spec/isomorphic/server_rendering/contextual_renderer_spec.rb index 9d11a2133..91d2e689c 100644 --- a/ruby/hyper-component/spec/isomorphic/server_rendering/contextual_renderer_spec.rb +++ b/ruby/hyper-component/spec/isomorphic/server_rendering/contextual_renderer_spec.rb @@ -5,7 +5,7 @@ let(:init) { Proc.new {} } let(:options) { { context_initializer: init } } - describe '#render' do + describe '#render', :prerendering_on do it 'pre-renders HTML' do result = renderer.render('Components.Todo', { todo: 'finish reactive-ruby' }, diff --git a/ruby/hyper-component/spec/spec_helper.rb b/ruby/hyper-component/spec/spec_helper.rb index 7e077b653..2c3f55c5c 100644 --- a/ruby/hyper-component/spec/spec_helper.rb +++ b/ruby/hyper-component/spec/spec_helper.rb @@ -1,7 +1,6 @@ ENV["RAILS_ENV"] ||= 'test' require 'opal' -require 'opal-rspec' require 'opal-jquery' begin @@ -12,7 +11,6 @@ require 'rspec/rails' require 'hyper-spec' require 'pry' -require 'opal-browser' require 'timecop' RSpec.configure do |config| @@ -32,6 +30,17 @@ Rails.cache.clear end + config.before :suite do + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end + + config.around(:each, :prerendering_on) do |example| + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) + end + config.filter_run_including focus: true config.filter_run_excluding opal: true config.run_all_when_everything_filtered = true @@ -39,19 +48,21 @@ # Fail tests on JavaScript errors in Chrome Headless class JavaScriptError < StandardError; end - config.after(:each, js: true) do |spec| - logs = page.driver.browser.manage.logs.get(:browser) + config.after(:each, js: true) do + logs = page.driver.browser.logs.get(:browser) errors = logs.select { |e| e.level == "SEVERE" && e.message.present? } - .map { |m| m.message.gsub(/\\n/, "\n") }.to_a + .map { |m| m.message.gsub(/\\n/, "\n") }.to_a if client_options[:deprecation_warnings] == :on warnings = logs.select { |e| e.level == "WARNING" && e.message.present? } - .map { |m| m.message.gsub(/\\n/, "\n") }.to_a + .map { |m| m.message.gsub(/\\n/, "\n") }.to_a puts "\033[0;33;1m\nJavascript client console warnings:\n\n" + warnings.join("\n\n") + "\033[0;30;21m" if warnings.present? end unless client_options[:raise_on_js_errors] == :off raise JavaScriptError, errors.join("\n\n") if errors.present? end end + + HyperSpec::Helpers.alias_method :on_client, :before_mount end # Stubbing the React calls so we can test outside of Opal diff --git a/ruby/hyper-component/spec/test_app/app/assets/config/manifest.js b/ruby/hyper-component/spec/test_app/app/assets/config/manifest.js index b16e53d6d..27de0df30 100644 --- a/ruby/hyper-component/spec/test_app/app/assets/config/manifest.js +++ b/ruby/hyper-component/spec/test_app/app/assets/config/manifest.js @@ -1,3 +1,4 @@ //= link_tree ../images //= link_directory ../javascripts .js +//= link application.css //= link_directory ../stylesheets .css diff --git a/ruby/hyper-component/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-component/spec/test_app/app/assets/javascripts/application.js new file mode 100644 index 000000000..50d230086 --- /dev/null +++ b/ruby/hyper-component/spec/test_app/app/assets/javascripts/application.js @@ -0,0 +1 @@ +//= require hyperstack-loader diff --git a/ruby/hyper-component/spec/test_app/app/assets/javascripts/application.rb b/ruby/hyper-component/spec/test_app/app/assets/javascripts/application.rb deleted file mode 100644 index 1d9dd625a..000000000 --- a/ruby/hyper-component/spec/test_app/app/assets/javascripts/application.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'jquery' -require 'react.js' -require 'react-server.js' -require 'react_ujs' -require 'opal' -require 'opal-jquery' -require 'components' diff --git a/ruby/hyper-component/spec/test_app/app/assets/javascripts/server_rendering.js b/ruby/hyper-component/spec/test_app/app/assets/javascripts/server_rendering.js deleted file mode 100644 index 3a82f2bf6..000000000 --- a/ruby/hyper-component/spec/test_app/app/assets/javascripts/server_rendering.js +++ /dev/null @@ -1,5 +0,0 @@ -//= require 'react-server' -//= require 'react_ujs' -//= require 'opal' -//= require 'components' -Opal.load('components'); \ No newline at end of file diff --git a/ruby/hyper-component/spec/test_app/app/controllers/home_controller.rb b/ruby/hyper-component/spec/test_app/app/controllers/home_controller.rb new file mode 100644 index 000000000..95f29929c --- /dev/null +++ b/ruby/hyper-component/spec/test_app/app/controllers/home_controller.rb @@ -0,0 +1,4 @@ +class HomeController < ApplicationController + def index + end +end diff --git a/ruby/hyper-component/spec/test_app/app/views/components/base_classes.rb b/ruby/hyper-component/spec/test_app/app/hyperstack/components/base_classes.rb similarity index 100% rename from ruby/hyper-component/spec/test_app/app/views/components/base_classes.rb rename to ruby/hyper-component/spec/test_app/app/hyperstack/components/base_classes.rb diff --git a/ruby/hyper-component/spec/test_app/app/views/components.rb b/ruby/hyper-component/spec/test_app/app/hyperstack/components/components.rb similarity index 89% rename from ruby/hyper-component/spec/test_app/app/views/components.rb rename to ruby/hyper-component/spec/test_app/app/hyperstack/components/components.rb index 70989c803..edac28d18 100644 --- a/ruby/hyper-component/spec/test_app/app/views/components.rb +++ b/ruby/hyper-component/spec/test_app/app/hyperstack/components/components.rb @@ -1,14 +1,4 @@ -require 'hyper-component' -if Hyperstack::Component::IsomorphicHelpers.on_opal_client? - require 'browser' - require 'browser/delay' - #require 'react/ext/opal-jquery/element' - require 'hyperstack/component/jquery' -end -require 'hyperstack/component/server' -require 'hyperstack/component/auto-import' require 'js' -require 'hyper-store' require 'hyperstack/internal/component/haml' # these mechanisms are deprecated in favor of using the features of hyper-spec. However @@ -88,4 +78,4 @@ def self.simulate_submit(element) end -require_tree './components' +#require_tree './components' diff --git a/ruby/hyper-component/spec/test_app/app/views/components/hello_world.rb b/ruby/hyper-component/spec/test_app/app/hyperstack/components/hello_world.rb similarity index 100% rename from ruby/hyper-component/spec/test_app/app/views/components/hello_world.rb rename to ruby/hyper-component/spec/test_app/app/hyperstack/components/hello_world.rb diff --git a/ruby/hyper-component/spec/test_app/app/hyperstack/components/test_it.rb b/ruby/hyper-component/spec/test_app/app/hyperstack/components/test_it.rb new file mode 100644 index 000000000..d3bc86d80 --- /dev/null +++ b/ruby/hyper-component/spec/test_app/app/hyperstack/components/test_it.rb @@ -0,0 +1,3 @@ +class TestIt + FOO = 12 +end diff --git a/ruby/hyper-component/spec/test_app/app/views/components/todo.rb b/ruby/hyper-component/spec/test_app/app/hyperstack/components/todo.rb similarity index 100% rename from ruby/hyper-component/spec/test_app/app/views/components/todo.rb rename to ruby/hyper-component/spec/test_app/app/hyperstack/components/todo.rb diff --git a/ruby/hyper-component/spec/test_app/app/views/home/index.html.erb b/ruby/hyper-component/spec/test_app/app/views/home/index.html.erb new file mode 100644 index 000000000..f157f05be --- /dev/null +++ b/ruby/hyper-component/spec/test_app/app/views/home/index.html.erb @@ -0,0 +1 @@ +HELLO! diff --git a/ruby/hyper-component/spec/test_app/app/xxxjavascript/packs/client_only.js b/ruby/hyper-component/spec/test_app/app/xxxjavascript/packs/client_only.js new file mode 100644 index 000000000..52f446dd4 --- /dev/null +++ b/ruby/hyper-component/spec/test_app/app/xxxjavascript/packs/client_only.js @@ -0,0 +1,7 @@ +//app/javascript/packs/client_only.js +// add any requires for packages that will run client side only +ReactDOM = require('react-dom'); // react-js client side code +jQuery = require('jquery'); // remove if you don't need jQuery +// to add additional NPM packages call run yarn add package-name@version +// then add the require here. + diff --git a/ruby/hyper-component/spec/test_app/config/application.rb b/ruby/hyper-component/spec/test_app/config/application.rb index f758a42c4..1b198059e 100644 --- a/ruby/hyper-component/spec/test_app/config/application.rb +++ b/ruby/hyper-component/spec/test_app/config/application.rb @@ -6,31 +6,31 @@ # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups(assets: %w(development test))) require 'jquery-rails' -require 'opal' -require 'opal-jquery' -require 'opal-browser' -require 'opal-rails' -require 'react-rails' -require 'hyper-store' -require 'hyper-component' -require 'hyper-spec' +# require 'opal' +# require 'opal-jquery' +# require 'opal-browser' +# require 'opal-rails' +# require 'react-rails' +# require 'hyper-store' +# require 'hyper-component' +# require 'hyper-spec' module TestApp class Application < Rails::Application config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check = false + config.opal.arity_check_enabled = true config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true config.opal.spec_location = 'spec-opal' - config.hyperstack.auto_config = false + # config.hyperstack.auto_config = false config.assets.cache_store = :null_store - config.react.server_renderer_options = { - files: ["server_rendering.js"] - } + # config.react.server_renderer_options = { + # files: ["server_rendering.js"] + # } config.react.server_renderer_directories = ["/app/assets/javascripts"] # Initialize configuration defaults for originally generated Rails version. diff --git a/ruby/hyper-component/spec/test_app/config/initializers/assets.rb b/ruby/hyper-component/spec/test_app/config/initializers/assets.rb index 49a078034..6bc33bcf4 100644 --- a/ruby/hyper-component/spec/test_app/config/initializers/assets.rb +++ b/ruby/hyper-component/spec/test_app/config/initializers/assets.rb @@ -6,7 +6,7 @@ # 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') +Rails.application.config.assets.paths << Rails.root.join('node_modules', 'app', 'views') # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets diff --git a/ruby/hyper-component/spec/test_app/config/initializers/hyperstack.rb b/ruby/hyper-component/spec/test_app/config/initializers/hyperstack.rb new file mode 100644 index 000000000..e2d0f5642 --- /dev/null +++ b/ruby/hyper-component/spec/test_app/config/initializers/hyperstack.rb @@ -0,0 +1,11 @@ +%w[ + hyper-state + hyperstack/autoloader + hyperstack/autoloader_starter + config/initializers/inflections.rb +].each { |r| Hyperstack.cancel_import r } + +Hyperstack.import 'jquery', js_import: true, at_head: true, client_only: true +Hyperstack.import 'react-server', js_import: true, at_head: true, client_only: true +Hyperstack.import 'hyperstack/component/jquery', client_only: true +Hyperstack.import 'hyperstack/component/server' diff --git a/ruby/hyper-component/spec/test_app/config/routes.rb b/ruby/hyper-component/spec/test_app/config/routes.rb index 787824f88..1cc57cb61 100644 --- a/ruby/hyper-component/spec/test_app/config/routes.rb +++ b/ruby/hyper-component/spec/test_app/config/routes.rb @@ -1,3 +1,5 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + + root to: 'home#index' end diff --git a/ruby/hyper-console/hyper-console.gemspec b/ruby/hyper-console/hyper-console.gemspec index 78382b73a..1169dc908 100644 --- a/ruby/hyper-console/hyper-console.gemspec +++ b/ruby/hyper-console/hyper-console.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-operation', Hyperloop::Console::VERSION spec.add_dependency 'hyper-store', Hyperloop::Console::VERSION - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-component', Hyperloop::Console::VERSION spec.add_development_dependency 'hyper-operation', Hyperloop::Console::VERSION diff --git a/ruby/hyper-console/lib/hyperloop/console/version.rb b/ruby/hyper-console/lib/hyperloop/console/version.rb index 855ca1ff7..7d7cbeb0f 100644 --- a/ruby/hyper-console/lib/hyperloop/console/version.rb +++ b/ruby/hyper-console/lib/hyperloop/console/version.rb @@ -1,5 +1,5 @@ module Hyperloop module Console - VERSION = '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' end end diff --git a/ruby/hyper-i18n/hyper-i18n.gemspec b/ruby/hyper-i18n/hyper-i18n.gemspec index 1f440282d..304b20aa0 100644 --- a/ruby/hyper-i18n/hyper-i18n.gemspec +++ b/ruby/hyper-i18n/hyper-i18n.gemspec @@ -11,7 +11,8 @@ Gem::Specification.new do |spec| spec.email = ['adamgeorge.31@gmail.com'] spec.summary = 'HyperI18n seamlessly brings Rails I18n into your Hyperstack application.' - spec.homepage = 'https://www.github.com/ruby-hyperstack/hyper-i18n' + spec.homepage = 'http://ruby-hyperstack.org' + spec.metadata = { 'documentation_uri' => 'https://docs.hyperstack.org/' } spec.license = 'MIT' spec.files = `git ls-files`.split("\n") @@ -22,15 +23,17 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-operation', Hyperstack::I18n::VERSION spec.add_dependency 'i18n' - spec.add_development_dependency 'bundler' #, '~> 1.16' + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-model', Hyperstack::I18n::VERSION spec.add_development_dependency 'hyper-spec', Hyperstack::I18n::VERSION - spec.add_development_dependency 'opal-rails', '~> 0.9.4' + spec.add_development_dependency 'mini_racer', '< 0.4.0' # something is busted with 0.4.0 and its libv8-node dependency + spec.add_development_dependency 'opal-rails' spec.add_development_dependency 'pry' - spec.add_development_dependency 'puma' + spec.add_development_dependency 'puma', '<= 5.4.0' spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rspec' - spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'rspec-rails' + spec.add_development_dependency 'rubocop' #, '~> 0.51.0' + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 end diff --git a/ruby/hyper-i18n/lib/hyper-i18n.rb b/ruby/hyper-i18n/lib/hyper-i18n.rb index 7bf20cf57..d3db3ddab 100644 --- a/ruby/hyper-i18n/lib/hyper-i18n.rb +++ b/ruby/hyper-i18n/lib/hyper-i18n.rb @@ -3,7 +3,6 @@ require 'hyper-component' require 'hyper-operation' -require 'hyper-state' require 'hyperstack/internal/i18n/helper_methods' require 'hyperstack/internal/i18n/localize' diff --git a/ruby/hyper-i18n/lib/hyper-i18n/version.rb b/ruby/hyper-i18n/lib/hyper-i18n/version.rb index b718c956e..23b0d5f44 100644 --- a/ruby/hyper-i18n/lib/hyper-i18n/version.rb +++ b/ruby/hyper-i18n/lib/hyper-i18n/version.rb @@ -1,3 +1,3 @@ module HyperI18n - VERSION = '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' end diff --git a/ruby/hyper-i18n/lib/hyperstack/i18n/version.rb b/ruby/hyper-i18n/lib/hyperstack/i18n/version.rb index 87c466fff..414e46f90 100644 --- a/ruby/hyper-i18n/lib/hyperstack/i18n/version.rb +++ b/ruby/hyper-i18n/lib/hyperstack/i18n/version.rb @@ -1,5 +1,5 @@ module Hyperstack module I18n - VERSION = '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' end end diff --git a/ruby/hyper-i18n/spec/hyper_i18n_spec.rb b/ruby/hyper-i18n/spec/hyper_i18n_spec.rb index c74e7e4fe..6a85d00c4 100644 --- a/ruby/hyper-i18n/spec/hyper_i18n_spec.rb +++ b/ruby/hyper-i18n/spec/hyper_i18n_spec.rb @@ -22,7 +22,7 @@ class TestComponent end end [['component rendering', :client_only], ['prerendering', :server_only]].each do |mode, flag| - it "will translate during #{mode}" do + it "will translate during #{mode}", prerendering_on: flag == :server_only do mount 'Components::TestComponent', {}, render_on: flag expect(find('#tp1')).to have_content('I am a key') expect(find('#tp2')).to have_content('Hello world') diff --git a/ruby/hyper-i18n/spec/spec_helper.rb b/ruby/hyper-i18n/spec/spec_helper.rb index 9b512f7fa..a5a678924 100644 --- a/ruby/hyper-i18n/spec/spec_helper.rb +++ b/ruby/hyper-i18n/spec/spec_helper.rb @@ -9,9 +9,24 @@ require 'hyper-i18n' RSpec.configure do |config| + + config.before :suite do + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end + + config.around(:each, :prerendering_on) do |example| + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) + end + config.color = true config.formatter = :documentation config.before(:all) do `rm -rf spec/test_app/tmp/cache/` end + + # Use legacy hyper-spec on_client behavior + HyperSpec::Helpers.alias_method :on_client, :before_mount end diff --git a/ruby/hyper-i18n/spec/test_app/config/application.rb b/ruby/hyper-i18n/spec/test_app/config/application.rb index 9c6e80a28..525527648 100644 --- a/ruby/hyper-i18n/spec/test_app/config/application.rb +++ b/ruby/hyper-i18n/spec/test_app/config/application.rb @@ -6,6 +6,7 @@ Bundler.require(*Rails.groups(assets: %w(development test))) module TestApp class Application < Rails::Application + config.opal.arity_check_enabled = true # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. diff --git a/ruby/hyper-model/.rspec b/ruby/hyper-model/.rspec index 8c18f1abd..4e1e0d2f7 100644 --- a/ruby/hyper-model/.rspec +++ b/ruby/hyper-model/.rspec @@ -1,2 +1 @@ ---format documentation --color diff --git a/ruby/hyper-model/Gemfile b/ruby/hyper-model/Gemfile index 162da81cd..5eb536166 100644 --- a/ruby/hyper-model/Gemfile +++ b/ruby/hyper-model/Gemfile @@ -1,10 +1,11 @@ source 'https://rubygems.org' -#gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master" -# hyper-model is still using an ancient inlined version of hyper-spec -#gem 'hyper-spec', path: '../hyper-spec' -gem 'hyperstack-config', path: '../hyperstack-config' gem 'hyper-state', path: '../hyper-state' gem 'hyper-component', path: '../hyper-component' gem 'hyper-operation', path: '../hyper-operation' - +gem 'hyper-spec', path: '../hyper-spec' +gem 'hyper-trace', path: '../hyper-trace' +gem 'hyperstack-config', path: '../hyperstack-config' +unless ENV['OPAL_VERSION']&.match("0.11") + gem 'opal-browser', git: 'https://github.com/opal/opal-browser' +end gemspec diff --git a/ruby/hyper-model/Rakefile b/ruby/hyper-model/Rakefile index 1f5cc17e0..eeb360644 100644 --- a/ruby/hyper-model/Rakefile +++ b/ruby/hyper-model/Rakefile @@ -32,7 +32,7 @@ end namespace :spec do task :prepare do - sh %(cd spec/test_app; bundle exec rails db:setup) + sh %(cd spec/test_app; rm db/schema.rb; RAILS_ENV=test bundle exec rails db:setup; RAILS_ENV=test bundle exec rails db:migrate) end (1..7).each do |batch| RSpec::Core::RakeTask.new(:"batch#{batch}") do |t| diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index 8f8574817..e20545e48 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -14,12 +14,8 @@ Gem::Specification.new do |spec| 'possible technologies) so changes to records on the server are '\ 'dynamically updated on all authorised clients.' spec.homepage = 'http://ruby-hyperstack.org' + spec.metadata = { 'documentation_uri' => 'https://docs.hyperstack.org/' } spec.license = 'MIT' - # spec.metadata = { - # "homepage_uri" => 'http://ruby-hyperstack.org', - # "source_code_uri" => 'https://github.com/ruby-hyperstack/hyper-component' - # } - spec.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(examples|gemfiles|pkg|reactive_record_test_app|spec)/}) } # spec.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } spec.test_files = `git ls-files -- {spec}/*`.split("\n") @@ -27,31 +23,24 @@ Gem::Specification.new do |spec| spec.add_dependency 'activemodel' spec.add_dependency 'activerecord', '>= 4.0.0' - spec.add_dependency 'hyper-component', HyperModel::VERSION spec.add_dependency 'hyper-operation', HyperModel::VERSION - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] - spec.add_development_dependency 'capybara' - spec.add_development_dependency 'chromedriver-helper', '1.2.0' - spec.add_development_dependency 'libv8', '~> 7.3.492.27.1' - spec.add_development_dependency 'mini_racer', '~> 0.2.6' - spec.add_development_dependency 'selenium-webdriver' + + spec.add_development_dependency 'bundler' spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'factory_bot_rails' - #spec.add_development_dependency 'hyper-spec', HyperModel::VERSION - spec.add_development_dependency 'mysql2' - spec.add_development_dependency 'opal-activesupport', '~> 0.3.1' - spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' - spec.add_development_dependency 'parser' - spec.add_development_dependency 'pry' + spec.add_development_dependency 'hyper-spec', HyperModel::VERSION + spec.add_development_dependency 'hyper-trace', HyperModel::VERSION + spec.add_development_dependency 'mini_racer', '< 0.4.0' # something is busted with 0.4.0 and its libv8-node dependency + spec.add_development_dependency 'pg' + spec.add_development_dependency 'opal-rails' spec.add_development_dependency 'pry-rescue' - spec.add_development_dependency 'puma' + spec.add_development_dependency 'pry-stack_explorer' + spec.add_development_dependency 'puma', '<= 5.4.0' spec.add_development_dependency 'pusher' spec.add_development_dependency 'pusher-fake' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' - spec.add_development_dependency 'reactrb-rails-generator' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' spec.add_development_dependency 'rspec-its' @@ -59,11 +48,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' spec.add_development_dependency 'rspec-wait' - spec.add_development_dependency 'rubocop', '~> 0.51.0' + spec.add_development_dependency 'rubocop' #, '~> 0.51.0' spec.add_development_dependency 'shoulda' spec.add_development_dependency 'shoulda-matchers' - spec.add_development_dependency 'spring-commands-rspec' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153, '~> 1.3.6' + spec.add_development_dependency 'spring-commands-rspec', '~> 1.0.4' + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153, '~> 1.3.6' spec.add_development_dependency 'timecop', '~> 0.8.1' - spec.add_development_dependency 'unparser' end diff --git a/ruby/hyper-model/lib/active_record_base.rb b/ruby/hyper-model/lib/active_record_base.rb index f797f878f..def9de439 100644 --- a/ruby/hyper-model/lib/active_record_base.rb +++ b/ruby/hyper-model/lib/active_record_base.rb @@ -5,17 +5,46 @@ module ActiveRecord # processes these arguments, and the will always leave the true server side scoping # proc in the `:server` opts. This method is common to client and server. class Base - def self._synchromesh_scope_args_check(args) - opts = if args.count == 2 && args[1].is_a?(Hash) - args[1].merge(server: args[0]) - elsif args[0].is_a? Hash - args[0] - else - { server: args[0] } - end - return opts if opts && opts[:server].respond_to?(:call) - raise 'must provide either a proc as the first arg or by the '\ - '`:server` option to scope and default_scope methods' + class << self + def _synchromesh_scope_args_check(args) + opts = if args.count == 2 && args[1].is_a?(Hash) + args[1].merge(server: args[0]) + elsif args[0].is_a? Hash + args[0] + else + { server: args[0] } + end + return opts if opts[:server].respond_to?(:call) || RUBY_ENGINE == 'opal' + raise 'must provide either a proc as the first arg or by the '\ + '`:server` option to scope and default_scope methods' + end + + alias pre_hyperstack_has_and_belongs_to_many has_and_belongs_to_many unless RUBY_ENGINE == 'opal' + + def has_and_belongs_to_many(other, opts = {}, &block) + join_table_name = [other.to_s, table_name].sort.join('_') + join_model_name = "HyperstackInternalHabtm#{join_table_name.singularize.camelize}" + join_model = + if Object.const_defined? join_model_name + Object.const_get(join_model_name) + else + Object.const_set(join_model_name, Class.new(ActiveRecord::Base)) + end + + join_model.class_eval { belongs_to other.to_s.singularize.to_sym } + + has_many join_model_name.underscore.pluralize.to_sym + + if RUBY_ENGINE == 'opal' + Object.const_set("HABTM_#{other.to_s.camelize}", join_model) + join_model.inheritance_column = nil + has_many other, through: join_model_name.underscore.pluralize.to_sym + else + join_model.table_name = join_table_name + join_model.belongs_to other + pre_hyperstack_has_and_belongs_to_many(other, opts, &block) + end + end end end if RUBY_ENGINE != 'opal' @@ -270,6 +299,12 @@ def denied! Hyperstack::InternalPolicy.raise_operation_access_violation(:scoped_denied, "#{self.class} regulation denies scope access. Called from #{caller_locations(1)}") end + unless method_defined? :saved_changes # for backwards compatibility to Rails < 5.1.7 + def saved_changes + previous_changes + end + end + # call do_not_synchronize to block synchronization of a model def self.do_not_synchronize @@ -286,6 +321,16 @@ def do_not_synchronize? self.class.do_not_synchronize? end + before_create :synchromesh_mark_update_time + before_update :synchromesh_mark_update_time + before_destroy :synchromesh_mark_update_time + + attr_reader :__synchromesh_update_time + + def synchromesh_mark_update_time + @__synchromesh_update_time = Time.now.to_f + end + after_commit :synchromesh_after_create, on: [:create] after_commit :synchromesh_after_change, on: [:update] after_commit :synchromesh_after_destroy, on: [:destroy] @@ -297,7 +342,7 @@ def synchromesh_after_create end def synchromesh_after_change - return if do_not_synchronize? || previous_changes.empty? + return if do_not_synchronize? || saved_changes.empty? ReactiveRecord::Broadcast.after_commit :change, self end @@ -347,6 +392,10 @@ def __hyperstack_secure_attributes(acting_user) nil end end + + scope :__hyperstack_internal_where_hash_scope, ->(*args) { where(*args) } + + scope :__hyperstack_internal_where_sql_scope, ->(*args) { where(*args) } end end diff --git a/ruby/hyper-model/lib/enumerable/pluck.rb b/ruby/hyper-model/lib/enumerable/pluck.rb index 9211a6b02..480b7198c 100644 --- a/ruby/hyper-model/lib/enumerable/pluck.rb +++ b/ruby/hyper-model/lib/enumerable/pluck.rb @@ -1,6 +1,7 @@ # Add pluck to enumerable... its already done for us in rails 5+ module Enumerable - def pluck(key) - map { |element| element[key] } + def pluck(*keys) + map { |element| keys.map { |key| element[key] } } + .flatten(keys.count > 1 ? 0 : 1) end end unless Enumerable.method_defined? :pluck diff --git a/ruby/hyper-model/lib/hyper-model.rb b/ruby/hyper-model/lib/hyper-model.rb index a8fef3628..93b5e51d0 100644 --- a/ruby/hyper-model/lib/hyper-model.rb +++ b/ruby/hyper-model/lib/hyper-model.rb @@ -1,6 +1,8 @@ require 'set' require 'hyperstack-config' -require 'hyper-component' + +Hyperstack.import 'hyper-model' + if RUBY_ENGINE == 'opal' require 'hyper-operation' require 'active_support' diff --git a/ruby/hyper-model/lib/hyper_model/version.rb b/ruby/hyper-model/lib/hyper_model/version.rb index 40c67ccbe..a5881f945 100644 --- a/ruby/hyper-model/lib/hyper_model/version.rb +++ b/ruby/hyper-model/lib/hyper_model/version.rb @@ -1,3 +1,3 @@ module HyperModel - VERSION = '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/associations.rb b/ruby/hyper-model/lib/reactive_record/active_record/associations.rb index 35a32a722..1ec9a664f 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/associations.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/associations.rb @@ -32,7 +32,7 @@ def self.reflection_finder(&block) module Associations class AssociationReflection - + attr_reader :klass_name attr_reader :association_foreign_key attr_reader :attribute attr_reader :macro @@ -79,6 +79,10 @@ def singular? @macro != :has_many end + def habtm? + through_association&.klass_name =~ /^HyperstackInternalHabtm/ + end + def through_association return unless @options[:through] @through_association ||= @owner_class.reflect_on_all_associations.detect do |association| diff --git a/ruby/hyper-model/lib/reactive_record/active_record/base.rb b/ruby/hyper-model/lib/reactive_record/active_record/base.rb index ae09951ff..df6393b25 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/base.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/base.rb @@ -10,13 +10,28 @@ class Base finder_method :__hyperstack_internal_scoped_last scope :__hyperstack_internal_scoped_last_n, ->(n) { last(n) } + def self.where(*args) + if args[0].is_a? Hash + # we can compute membership in the scope when the arg is a hash + __hyperstack_internal_where_hash_scope(args[0]) + else + # otherwise the scope has to always be computed on the server + __hyperstack_internal_where_sql_scope(*args) + end + end + + scope :__hyperstack_internal_where_hash_scope, + client: ->(attrs) { !attrs.detect { |k, v| self[k] != v } } + + scope :__hyperstack_internal_where_sql_scope + ReactiveRecord::ScopeDescription.new( self, :___hyperstack_internal_scoped_find_by, client: ->(attrs) { !attrs.detect { |attr, value| attributes[attr] != value } } ) def self.__hyperstack_internal_scoped_find_by(attrs) - collection = all.apply_scope(:___hyperstack_internal_scoped_find_by, attrs) + collection = all.apply_scope(:___hyperstack_internal_scoped_find_by, attrs).observed if !collection.collection collection._find_by_initializer(self, attrs) else diff --git a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb index 86877f7b9..e4977b83e 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb @@ -1,11 +1,20 @@ module ActiveRecord - module ClassMethods - - alias _new_without_sti_type_cast new - - def new(*args, &block) - _new_without_sti_type_cast(*args, &block).cast_to_current_sti_type + begin + # Opal 0.11 super did not work with new, but new was defined + alias _new_without_sti_type_cast new + def new(*args, &block) + _new_without_sti_type_cast(*args, &block).cast_to_current_sti_type + end + rescue NameError + def self.extended(base) + base.singleton_class.class_eval do + alias_method :_new_without_sti_type_cast, :new + define_method :new do |*args, &block| + _new_without_sti_type_cast(*args, &block).cast_to_current_sti_type + end + end + end end def base_class @@ -188,14 +197,18 @@ def alias_attribute(new_name, old_name) ] def method_missing(name, *args, &block) - if name == 'human_attribute_name' + # In MRI Ruby we would never get to this point with a nil name argument, + # but currently in Opal we do, so we will mimic MRI Ruby and throw a TypeError. + raise TypeError, "nil is not a symbol nor a string" if name.nil? + + if name == "human_attribute_name" opts = args[1] || {} opts[:default] || args[0] elsif args.count == 1 && name.start_with?("find_by_") && !block - find_by(name.sub(/^find_by_/, '') => args[0]) + find_by(name.sub(/^find_by_/, "") => args[0]) elsif [].respond_to?(name) all.send(name, *args, &block) - elsif name.end_with?('!') + elsif name.end_with?("!") send(name.chop, *args, &block).send(:reload_from_db) rescue nil elsif !SERVER_METHODS.include?(name) raise "#{self.name}.#{name}(#{args}) (called class method missing)" @@ -308,6 +321,14 @@ def abstract_class=(val) end end + def table_name + @table_name || name.downcase.pluralize + end + + def table_name=(name) + @table_name = name + end + def composed_of(name, opts = {}) reflection = Aggregations::AggregationReflection.new(base_class, :composed_of, name, opts) if reflection.klass < ActiveRecord::Base @@ -355,7 +376,7 @@ def server_method(name, default: nil) def define_attribute_methods columns_hash.each do |name, column_hash| - next if name == primary_key + next if name == :id # only add serialized key if its serialized. This just makes testing a bit # easier by keeping the columns_hash the same if there are no seralized strings # see rspec ./spec/batch1/column_types/column_type_spec.rb:100 @@ -445,7 +466,7 @@ def _react_param_conversion(param, opt = nil) [assoc.attribute, { id: [value]}] end else - [key, [value]] + [*key, [value]] end end.compact ReactiveRecord::Base.load_data do diff --git a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb index 0eccace04..d2c03a3ab 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb @@ -14,7 +14,21 @@ module InstanceMethods # as well as for belongs_to relationships, server_methods, and the special # type and model_name methods. See the ClassMethods module for details. + # meanwhile in Opal 1.0 there is currently an issue where the name of the method + # does not get passed to method_missing from super. + # https://github.com/opal/opal/issues/2165 + # So the following hack works around that issue until its fixed. + + %x{ + Opal.orig_find_super_dispatcher = Opal.find_super_dispatcher + Opal.find_super_dispatcher = function(obj, mid, current_func, defcheck, allow_stubs) { + Opal.__name_of_super = mid; + return Opal.orig_find_super_dispatcher(obj, mid, current_func, defcheck, allow_stubs) + } + } + def method_missing(missing, *args, &block) + missing ||= `Opal.__name_of_super` column = self.class.columns_hash.detect { |name, *| missing =~ /^#{name}/ } if column name = column[0] @@ -30,9 +44,6 @@ def method_missing(missing, *args, &block) end end - # ignore load_from_json when it calls _hyperstack_internal_setter_id - def _hyperstack_internal_setter_id(*); end - # the system assumes that there is "virtual" model_name and type attribute so # we define the internal setter here. If the user defines some other attributes # or uses these names no harm is done since the exact same method would have been @@ -81,7 +92,7 @@ def initialize(hash = {}) end self.class.load_data do h.each do |attribute, value| - next if attribute == primary_key + next if attribute == :id @ar_instance[attribute] = value changed_attributes << attribute end @@ -115,8 +126,8 @@ def revert @backing_record.revert end - def changed? - @backing_record.changed? + def changed?(attr = nil) + @backing_record.changed?(*attr) end def dup @@ -151,6 +162,14 @@ def itself # end end + def increment!(attr) + load(attr).then { |current_value| update(attr => current_value + 1) } + end + + def decrement!(attr) + load(attr).then { |current_value| update(attr => current_value - 1) } + end + def load(*attributes, &block) first_time = true ReactiveRecord.load do diff --git a/ruby/hyper-model/lib/reactive_record/active_record/public_columns_hash.rb b/ruby/hyper-model/lib/reactive_record/active_record/public_columns_hash.rb index 5240d9165..ded9014f2 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/public_columns_hash.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/public_columns_hash.rb @@ -6,39 +6,38 @@ module ActiveRecord # adds method to get the HyperMesh public column types # this works because the public folder is currently required to be eager loaded. class Base + @@hyper_stack_public_columns_hash_mutex = Mutex.new def self.public_columns_hash - return @public_columns_hash if @public_columns_hash && Rails.env.production? - files = [] - Hyperstack.public_model_directories.each do |dir| - dir_length = Rails.root.join(dir).to_s.length + 1 - Dir.glob(Rails.root.join(dir, '**', '*.rb')).each do |file| - require_dependency(file) # still the file is loaded to make sure for development and test env - files << file[dir_length..-4] + @@hyper_stack_public_columns_hash_mutex.synchronize do + return @public_columns_hash if @public_columns_hash && Rails.env.production? + files = [] + Hyperstack.public_model_directories.each do |dir| + dir_length = Rails.root.join(dir).to_s.length + 1 + Dir.glob(Rails.root.join(dir, '**', '*.rb')).each do |file| + require_dependency(file) # still the file is loaded to make sure for development and test env + files << file[dir_length..-4] + end end - end - @public_columns_hash = {} - # descendants only works for already loaded models! - descendants.each do |model| - if files.include?(model.name.underscore) && model.name.underscore != 'application_record' - @public_columns_hash[model.name] = model.columns_hash rescue nil # why rescue? + @public_columns_hash = {} + # descendants only works for already loaded models! + descendants.each do |model| + if files.include?(model.name.underscore) && model.name.underscore != 'application_record' + @public_columns_hash[model.name] = model.columns_hash rescue nil # why rescue? + end end - # begin - # @public_columns_hash[model.name] = model.columns_hash if model.table_name - # rescue Exception => e - # binding.pry - # @public_columns_hash = nil - # raise $!, "Could not read 'columns_hash' for #{model}: #{$!}", $!.backtrace - # end if files.include?(model.name.underscore) && model.name.underscore != 'application_record' + @public_columns_hash end - @public_columns_hash end + @@hyper_stack_public_columns_hash_as_json_mutex = Mutex.new def self.public_columns_hash_as_json - return @public_columns_hash_json if @public_columns_hash_json && Rails.env.production? - pch = public_columns_hash - return @public_columns_hash_json if @prev_public_columns_hash == pch - @prev_public_columns_hash = pch - @public_columns_hash_json = pch.to_json + @@hyper_stack_public_columns_hash_as_json_mutex.synchronize do + return @public_columns_hash_json if @public_columns_hash_json && Rails.env.production? + pch = public_columns_hash + return @public_columns_hash_json if @prev_public_columns_hash == pch + @prev_public_columns_hash = pch + @public_columns_hash_json = pch.to_json + end end end end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb index 5645b687d..4cf628f3c 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb @@ -38,6 +38,7 @@ class Base attr_accessor :aggregate_owner attr_accessor :aggregate_attribute attr_accessor :destroyed + attr_accessor :being_destroyed attr_accessor :updated_during attr_accessor :synced_attributes attr_accessor :virgin @@ -310,7 +311,7 @@ def saving! @saving = true end - def errors!(hash, saving) + def errors!(hash, saving = false) @errors_at_last_sync = hash if saving notify_waiting_for_save errors.clear && return unless hash @@ -323,7 +324,6 @@ def errors!(hash, saving) end def revert_errors! - puts "#{inspect}.revert_errors! @errors_at_last_sync: #{@errors_at_last_sync}" errors!(@errors_at_last_sync) end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb index 2188a4286..7ba7ed714 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb @@ -141,7 +141,10 @@ class TestModel < ApplicationRecord =end + attr_accessor :broadcast_updated_at + def sync_scopes(broadcast) + self.broadcast_updated_at = broadcast.updated_at # record_with_current_values will return nil if data between # the broadcast record and the value on the client is out of sync # not running set_pre_sync_related_records will cause sync scopes @@ -159,6 +162,8 @@ def sync_scopes(broadcast) ) record.backing_record.sync_unscoped_collection! if record.destroyed? || broadcast.new? end + ensure + self.broadcast_updated_at = nil end def apply_to_all_collections(method, record, dont_gather) @@ -336,26 +341,26 @@ def reload_from_db(force = nil) end def observed - return if @observing || ReactiveRecord::Base.data_loading? + return self if @observing || ReactiveRecord::Base.data_loading? begin @observing = true link_to_parent reload_from_db(true) if @out_of_date Hyperstack::Internal::State::Variable.get(self, :collection) + self ensure @observing = false end end - def set_count_state(val) + def count_state=(val) unless ReactiveRecord::WhileLoading.observed? Hyperstack::Internal::State::Variable.set(self, :collection, collection, true) end + @count_updated_at = ReactiveRecord::Operations::Base.last_response_sent_at @count = val end - - def _count_internal(load_from_client) # when count is called on a leaf, count_internal is called for each # ancestor. Only the outermost count has load_from_client == true @@ -462,16 +467,18 @@ def push(item) alias << push def _internal_push(item) - item.itself # force get of at least the id - if collection - self.force_push item - else - unsaved_children << item - update_child(item) - @owner.backing_record.sync_has_many(@association.attribute) if @owner && @association - if !@count.nil? - @count += item.destroyed? ? -1 : 1 - notify_of_change self + insure_sync do + item.itself # force get of at least the id + if collection + self.force_push item + else + unsaved_children << item + update_child(item) + @owner.backing_record.sync_has_many(@association.attribute) if @owner && @association + if !@count.nil? + @count += (item.destroyed? ? -1 : 1) + notify_of_change self + end end end self @@ -557,7 +564,7 @@ def internal_replace(new_array) notify_of_change new_array end - def delete(item) + def destroy_non_habtm(item) Hyperstack::Internal::State::Mapper.bulk_update do unsaved_children.delete(item) if @owner && @association @@ -573,13 +580,40 @@ def delete(item) end end + def destroy(item) + return destroy_non_habtm(item) unless @association&.habtm? + + ta = @association.through_association + item_foreign_key = @association.source_belongs_to_association.association_foreign_key + join_record = ta.klass.find_by( + ta.association_foreign_key => @owner.id, + item_foreign_key => item.id + ) + return destroy_non_habtm(item) if join_record.nil? || + join_record.backing_record.being_destroyed + + join_record&.destroy + end + + def insure_sync + if Collection.broadcast_updated_at && @count_updated_at && Collection.broadcast_updated_at < @count_updated_at + reload_from_db + else + yield + end + end + + alias delete destroy + def delete_internal(item) - if collection - all.delete(item) - elsif !@count.nil? - @count -= 1 + insure_sync do + if collection + all.delete(item) + elsif !@count.nil? + @count -= 1 + end + yield if block_given? # was yield item, but item is not used end - yield if block_given? # was yield item, but item is not used item end @@ -643,10 +677,19 @@ def method_missing(method, *args, &block) all.send(method, *args, &block) elsif ScopeDescription.find(@target_klass, method) apply_scope(method, *args) - elsif @target_klass.respond_to?(method) && ScopeDescription.find(@target_klass, "_#{method}") + elsif !@target_klass.respond_to?(method) + super + elsif ScopeDescription.find(@target_klass, "_#{method}") apply_scope("_#{method}", *args).first else - super + # create a subclass of the original target class that responds to all + # by returning our collection back + fake_class = Class.new(@target_klass) + fake_class.instance_variable_set("@all", self) + # Opal 0.11 does not handle overridding the original @target_klass + # with an accessor, so we define the accessor as a method. + fake_class.define_singleton_method(:all) { @all } + fake_class.send(method, *args, &block) end end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb index e2c9b3077..b56d34812 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb @@ -1,4 +1,4 @@ -# add mehods to Object to determine if this is a dummy object or not +# add methods to Object to determine if this is a dummy object or not class Object def loaded? !loading? @@ -7,10 +7,6 @@ def loaded? def loading? false end - - def present? - !!self - end end module ReactiveRecord @@ -36,6 +32,12 @@ def build_default_value_for_nil @column_hash[:default] || nil end + def build_default_value_for_json + ::JSON.parse(@column_hash[:default]) if @column_hash[:default] + end + + alias build_default_value_for_jsonb build_default_value_for_json + def build_default_value_for_datetime if @column_hash[:default] ::Time.parse(@column_hash[:default].gsub(' ','T')+'+00:00') @@ -55,18 +57,20 @@ def build_default_value_for_date end end + FALSY_VALUES = [false, nil, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"] + def build_default_value_for_boolean - @column_hash[:default] || false + !FALSY_VALUES.include?(@column_hash[:default]) end def build_default_value_for_float - @column_hash[:default] || Float(0.0) + @column_hash[:default]&.to_f || Float(0.0) end alias build_default_value_for_decimal build_default_value_for_float def build_default_value_for_integer - @column_hash[:default] || Integer(0) + @column_hash[:default]&.to_i || Integer(0) end alias build_default_value_for_bigint build_default_value_for_integer @@ -92,10 +96,6 @@ def loaded? false end - def present? - false - end - def nil? true end @@ -104,6 +104,11 @@ def ! true end + def class + notify + @object.class + end + def method_missing(method, *args, &block) if method.start_with?("build_default_value_for_") nil @@ -158,7 +163,13 @@ def tap alias inspect to_s - `#{self}.$$proto.toString = Opal.Object.$$proto.toString` + %x{ + if (Opal.Object.$$proto) { + #{self}.$$proto.toString = Opal.Object.$$proto.toString + } else { + #{self}.$$prototype.toString = Opal.Object.$$prototype.toString + } + } def to_f notify @@ -216,10 +227,10 @@ def acts_as_string? # to convert it to a string, for rendering # advantage over a try(:method) is, that it doesnt raise und thus is faster # which is important during render - def respond_to?(method) + def respond_to?(method, all = false) return true if method == :acts_as_string? return true if %i[inspect to_date to_f to_i to_numeric to_number to_s to_time].include? method - return @object.respond_to? if @object + return @object.respond_to?(method, all) if @object false end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb index b60cba33e..9a0a80cbc 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb @@ -37,7 +37,8 @@ def get_primary_key_value def get_server_method(attr, reload = nil) non_relationship_getter_common(attr, reload) do |has_key| - sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key + # SPLAT BUG: was sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key + sync_ignore_dummy attr, Base.load_from_db(self, *(vector || [nil]), *attr), has_key end end @@ -79,7 +80,8 @@ def non_relationship_getter_common(attr, reload, &block) if new? yield has_key if block elsif on_opal_client? - sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key + # SPLAT BUG: was sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key + sync_ignore_dummy attr, Base.load_from_db(self, *(vector || [nil]), *attr), has_key elsif id.present? sync_attribute attr, fetch_by_id(attr) else diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb index c841192a9..f1ada7b4d 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb @@ -1,5 +1,3 @@ -require 'json' - module ReactiveRecord class Base @@ -457,7 +455,7 @@ def self.save_records(models, associations, acting_user, validate, save) parent.send("#{association[:attribute]}=", aggregate) #puts "updated is frozen? #{aggregate.frozen?}, parent attributes = #{parent.send(association[:attribute]).attributes}" elsif parent.class.reflect_on_association(association[:attribute].to_sym).nil? - raise "Missing association :#{association[:attribute]} for #{parent.class.name}. Was association defined on opal side only?" + raise "Missing association :#{association[:attribute]} for #{parent.class.name}. Was association defined on opal side only?" elsif parent.class.reflect_on_association(association[:attribute].to_sym).collection? #puts ">>>>>>>>>> #{parent.class.name}.send('#{association[:attribute]}') << #{reactive_records[association[:child_id]]})" dont_save_list.delete(parent) @@ -549,25 +547,31 @@ def self.save_records(models, associations, acting_user, validate, save) if RUBY_ENGINE == 'opal' def destroy(&block) + return if @destroyed || @being_destroyed - return if @destroyed - - #destroy_associations + # destroy_associations promise = Promise.new - if !data_loading? && (id || vector) Operations::Destroy.run(model: ar_instance.model_name.to_s, id: id, vector: vector) .then do |response| - Broadcast.to_self ar_instance + #[reactive_record_id, model.class.name, attributes, messages] model.errors.messages + + if response[:success] + @destroyed = true + Broadcast.to_self ar_instance + else + errors! response[:messages] + end yield response[:success], response[:message] if block promise.resolve response end else destroy_associations # sync_unscoped_collection! # ? should we do this here was NOT being done before hypermesh integration + @destroyed = true yield true, nil if block - promise.resolve({success: true}) + promise.resolve(success: true) end # DO NOT CLEAR ATTRIBUTES. Records that are not found, are destroyed, and if they are searched for again, we want to make @@ -577,8 +581,6 @@ def destroy(&block) # sync! # and modify server_data_cache so that it does NOT call destroy - @destroyed = true - promise end @@ -587,18 +589,15 @@ def destroy(&block) def self.destroy_record(model, id, vector, acting_user) model = Object.const_get(model) record = if id - model.find(id) - else - ServerDataCache.new(acting_user, {})[*vector] - end - - - record.check_permission_with_acting_user(acting_user, :destroy_permitted?).destroy - {success: true, attributes: {}} + model.find(id) + else + ServerDataCache.new(acting_user, {})[*vector].value + end + success = record.check_permission_with_acting_user(acting_user, :destroy_permitted?).destroy + { success: success, attributes: {}, messages: record.errors.messages } rescue Exception => e - #ReactiveRecord::Pry.rescued(e) - {success: false, record: record, message: e} + { success: false, record: record, message: e } end end end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb index a141bf7b3..65b7072ab 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb @@ -12,6 +12,10 @@ class Base < Hyperstack::ControllerOp FORMAT = '0x%x' + class << self + attr_accessor :last_response_sent_at + end + def self.serialize_params(hash) hash['associations'].each do |assoc| assoc['parent_id'] = FORMAT % assoc['parent_id'] @@ -38,6 +42,7 @@ def self.serialize_response(response) response[:saved_models].each do |saved_model| saved_model[0] = FORMAT % saved_model[0] end if response.is_a?(Hash) && response[:saved_models] + response[:sent_at] = Time.now.to_f response end @@ -45,6 +50,7 @@ def self.deserialize_response(response) response[:saved_models].each do |saved_model| saved_model[0] = saved_model[0].to_i(16) end if response.is_a?(Hash) && response[:saved_models] + Base.last_response_sent_at = response.delete(:sent_at) response end end @@ -93,7 +99,7 @@ class Save < Base class Destroy < Base param :acting_user, nils: true param :model - param :id + param :id, nils: true param :vector step do ReactiveRecord::Base.destroy_record( diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb index a3458e12b..52fecd72c 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb @@ -94,7 +94,7 @@ def set_common(attr, value) end def set_attribute_change_status_and_notify(attr, changed, new_value) - if @virgin + if @virgin || @being_destroyed @attributes[attr] = new_value else change_status_and_notify_helper(attr, changed) do |had_key, current_value| @@ -108,14 +108,13 @@ def set_attribute_change_status_and_notify(attr, changed, new_value) end def set_change_status_and_notify_only(attr, changed) - return if @virgin + return if @virgin || @being_destroyed change_status_and_notify_helper(attr, changed) do Hyperstack::Internal::State::Variable.set(self, attr, nil) unless data_loading? end end def change_status_and_notify_helper(attr, changed) - return if @being_destroyed empty_before = changed_attributes.empty? if !changed || data_loading? changed_attributes.delete(attr) diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/while_loading.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/while_loading.rb index 96c6a3dca..76d54fcf5 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/while_loading.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/while_loading.rb @@ -353,7 +353,7 @@ def add_style_sheet end - def after_mount_and_update + def after_mount_and_update(*) @waiting_on_resources = @Loading node = dom_node %x{ diff --git a/ruby/hyper-model/lib/reactive_record/broadcast.rb b/ruby/hyper-model/lib/reactive_record/broadcast.rb index 7fff8a1fb..b9b03c603 100644 --- a/ruby/hyper-model/lib/reactive_record/broadcast.rb +++ b/ruby/hyper-model/lib/reactive_record/broadcast.rb @@ -9,16 +9,16 @@ def self.after_commit(operation, model) puts "Broadcast aftercommit hook: #{data}" if Hyperstack::Connection.show_diagnostics if !Hyperstack.on_server? && Hyperstack::Connection.root_path - send_to_server(operation, data) rescue nil # fails if server no longer running so ignore + send_to_server(operation, data, model.__synchromesh_update_time) rescue nil # fails if server no longer running so ignore else - SendPacket.run(data, operation: operation) + SendPacket.run(data, operation: operation, updated_at: model.__synchromesh_update_time) end end rescue ActiveRecord::StatementInvalid => e raise e unless e.message == "Could not find table 'hyperstack_connections'" end unless RUBY_ENGINE == 'opal' - def self.send_to_server(operation, data) + def self.send_to_server(operation, data, updated_at) salt = SecureRandom.hex authorization = Hyperstack.authorization(salt, data[:channel], data[:broadcast_id]) raise 'no server running' unless Hyperstack::Connection.root_path @@ -27,6 +27,7 @@ def self.send_to_server(operation, data) Hyperstack::Connection.root_path, data, operation: operation, + updated_at: updated_at, salt: salt, authorization: authorization ).tap { |p| raise p.error if p.rejected? } @@ -47,6 +48,7 @@ class SendPacket < Hyperstack::ServerOp param :record param :operation param :previous_changes + param :updated_at unless RUBY_ENGINE == 'opal' validate do @@ -88,7 +90,7 @@ def self.to_self(record, data = {}) def record_with_current_values ReactiveRecord::Base.load_data do - backing_record = @backing_record || klass.find(record[:id]).backing_record + backing_record = @backing_record || klass.find(record[klass.primary_key]).backing_record if destroyed? backing_record.ar_instance else @@ -115,6 +117,10 @@ def destroyed? @destroyed end + def local? + @is_local + end + def klass Object.const_get(@klass) end @@ -126,6 +132,7 @@ def to_s # private attr_reader :record + attr_reader :updated_at def self.open_channels @open_channels ||= Set.new @@ -135,7 +142,7 @@ def self.in_transit @in_transit ||= Hash.new { |h, k| h[k] = new(k) } end - def initialize(id) + def initialize(id = nil) @id = id @received = Set.new @record = {} @@ -144,11 +151,12 @@ def initialize(id) def local(operation, record, data) @destroyed = operation == :destroy + @is_local = true @is_new = operation == :create @klass = record.class.name @record = data record.backing_record.destroyed = false - @record[:id] = record.id if record.id + @record[record.primary_key] = record.id if record.id record.backing_record.destroyed = @destroyed @backing_record = record.backing_record @previous_changes = record.changes @@ -162,11 +170,12 @@ def receive(params) @klass ||= params.klass @record.merge! params.record @previous_changes.merge! params.previous_changes + @updated_at = params.updated_at ReactiveRecord::Base.when_not_saving(klass) do - @backing_record = ReactiveRecord::Base.exists?(klass, params.record[:id]) + @backing_record = ReactiveRecord::Base.exists?(klass, params.record[klass.primary_key]) # first check to see if we already destroyed it and if so exit the block - return if @backing_record&.destroyed + break if @backing_record&.destroyed # We ignore whether the record is being created or not, and just check and see if in our # local copy we have ever loaded this id before. If we have then its not new to us. @@ -180,7 +189,7 @@ def receive(params) # it is possible that we are recieving data on a record for which we are also waiting # on an an inital data load in which case we have not yet set the loaded id, so we # set if now. - @backing_record&.loaded_id = params.record[:id] + @backing_record&.loaded_id = params.record[klass.primary_key] # once we have received all the data from all the channels (applies to create and update only) # we yield and process the record @@ -234,7 +243,7 @@ def process_previous_changes def merge_current_values(br) current_values = Hash[*@previous_changes.collect do |attr, values| - value = attr == :id ? record[:id] : values.first + value = attr == klass.primary_key ? record[klass.primary_key] : values.first if br.attributes.key?(attr) && br.attributes[attr] != br.convert(attr, value) && br.attributes[attr] != br.convert(attr, values.last) diff --git a/ruby/hyper-model/lib/reactive_record/server_data_cache.rb b/ruby/hyper-model/lib/reactive_record/server_data_cache.rb index 40b460098..bddc325b7 100644 --- a/ruby/hyper-model/lib/reactive_record/server_data_cache.rb +++ b/ruby/hyper-model/lib/reactive_record/server_data_cache.rb @@ -474,7 +474,7 @@ def self.load_from_json(tree, target = nil) end end - if id_value = tree["id"] and id_value.is_a? Array + if (id_value = tree[target.class.try(:primary_key)] || tree[:id]) && id_value.is_a?(Array) target.id = id_value.first end tree.each do |method, value| @@ -486,7 +486,7 @@ def self.load_from_json(tree, target = nil) elsif !target load_from_json(value, Object.const_get(method)) elsif method == "*count" - target.set_count_state(value.first) + target.count_state = value.first elsif method.is_a? Integer or method =~ /^[0-9]+$/ new_target = target.push_and_update_belongs_to(method) elsif method.is_a? Array @@ -506,7 +506,7 @@ def self.load_from_json(tree, target = nil) target.send "#{method}=", value.first elsif value.is_a? Array - target.send("_hyperstack_internal_setter_#{method}", value.first) + target.send("_hyperstack_internal_setter_#{method}", value.first) unless [target.class.primary_key, :id].include? method elsif value.is_a?(Hash) && value[:id] && value[:id].first && (association = target.class.reflect_on_association(method)) # not sure if its necessary to check the id above... is it possible to for the method to be an association but not have an id? klass = value[:model_name] ? Object.const_get(value[:model_name].first) : association.klass diff --git a/ruby/hyper-model/spec/batch1/active_record/class_methods_spec.rb b/ruby/hyper-model/spec/batch1/active_record/class_methods_spec.rb new file mode 100644 index 000000000..a33a52b85 --- /dev/null +++ b/ruby/hyper-model/spec/batch1/active_record/class_methods_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "ActiveRecord::ClassMethods", js: true do + context "method_missing" do + it "should return a TypeError if name is nil" do + expect_evaluate_ruby do + error = nil + + begin + User.send(nil) + rescue StandardError => e + error = e + end + + error + end.to eq("nil is not a symbol nor a string") + end + end +end diff --git a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb index fc28bf65d..cdf43c9d8 100644 --- a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb +++ b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb @@ -27,7 +27,7 @@ def method_missing(m, *args, &b) end def as_json - strftime("%Y-%m-%dT%H:%M:%S%z").gsub(/\+0000$/, '-0000') + strftime("%Y-%m-%dT%H:%M:%S.%3N%z").gsub(/\+0000$/, '-0000') end attr_reader :time @@ -35,7 +35,7 @@ def as_json def time_only utc_time = Timex.new(self).utc start_time = Time.parse('2000-01-01T00:00:00.000-00:00').utc - Timex.new (start_time+(utc_time-utc_time.beginning_of_day.to_i).to_i).localtime + Timex.new(start_time+(utc_time-utc_time.beginning_of_day.to_i).to_i).localtime end class << self @@ -61,7 +61,7 @@ def parse(s) Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end TypeTest.build_tables #rescue nil @@ -131,15 +131,17 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t, - timestamp: t + timestamp: t, + json: {kind: :json}, + jsonb: {kind: :jsonb} ) - expect_evaluate_ruby do + expect do TypeTest.columns_hash.collect do |attr, _info| TypeTest.find(1).send(attr).class end - end.to eq([ + end.to_on_client eq([ 'Number', 'NilClass', 'Boolean', 'Date', 'Time', 'Number', 'Number', 'Number', - 'Number', 'String', 'String', 'Time', 'Time' + 'Number', 'String', 'String', 'Time', 'Time', 'NilClass', 'NilClass' ]) check_errors end @@ -149,8 +151,10 @@ class DefaultTest < ActiveRecord::Base TypeTest.serialize :string TypeTest.serialize :text end - [:string, :text].each do |attr| - expect_evaluate_ruby("TypeTest.find(1).#{attr}.class").to eq('NilClass') + %i[string text].each_with_index do |attr, i| + # find a different record for each iteration to prevent finding a model + # which is loaded + expect { TypeTest.find(i + 1)[attr].class }.on_client_to eq('NilClass') end end @@ -167,15 +171,15 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t, - timestamp: t + timestamp: t # see default tests below for json and jsonb ) - expect_evaluate_ruby do + expect do t = TypeTest.find(1) [ !t.boolean, t.date+1, t.datetime+2.days, t.decimal + 5, t.float + 6, t.integer + 7, t.bigint + 8, t.string.length, t.text.length, t.time+3.days, t.timestamp+4.days ] - end.to eq([ + end.on_client_to eq([ true, "2001-01-02", (Timex.sqlmin+2.days).as_json, 5, 6, 7, 8, 0, 0, (Timex.sqlmin+3.days).as_json, (Timex.sqlmin+4.days).as_json ]) @@ -184,7 +188,7 @@ class DefaultTest < ActiveRecord::Base it 'loads and converts the value' do # randomly generates an error, but the exactual spec passed... perhaps move it up or down? (tried moving down one step) t = Timex.parse('1/2/2003') - r = TypeTest.create( + TypeTest.create( boolean: true, date: t.time, datetime: t.time, @@ -195,15 +199,17 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t.time, - timestamp: t.time + timestamp: t.time, + json: {kind: :json}, + jsonb: {kind: :jsonb} ) - expect_promise do - ReactiveRecord.load do + expect do + Hyperstack::Model.load do TypeTest.columns_hash.collect do |attr, _info| [TypeTest.find(1).send(attr).class, TypeTest.find(1).send(attr)] end.flatten end - end.to eq([ + end.to_then eq([ 'Number', 1, 'NilClass', nil, 'Boolean', true, @@ -216,7 +222,9 @@ class DefaultTest < ActiveRecord::Base 'String', 'hello', 'String', 'goodby', 'Time', t.time_only.as_json, # date is indeterminate for active record time - 'Time', t.as_json + 'Time', t.as_json, + 'Hash', {'kind' => 'json'}, + 'Hash', {'kind' => 'jsonb'} ]) check_errors end @@ -243,7 +251,7 @@ class DefaultTest < ActiveRecord::Base test = TypeTest.new test.datetime = 12.2 test.datetime + 60.9 - end.to eq((Timex.at(12.2)+60.9).as_json) + end.to eq(Timex.at(73.1001).as_json) # extra 1 digit forces round up check_errors end @@ -297,8 +305,18 @@ class DefaultTest < ActiveRecord::Base r = DefaultTest.create(string: "no no no") expect_evaluate_ruby do t = DefaultTest.find(1) - [t.string, t.date, t.datetime] - end.to eq(["I'm a string!", r.date.as_json, Timex.new(r.datetime.localtime).as_json]) + [ + t.string, t.date, t.datetime, t.integer_from_string, t.integer_from_int, + t.float_from_string, t.float_from_float, + t.boolean_from_falsy_string, t.boolean_from_truthy_string, t.boolean_from_falsy_value, + t.json[:kind], t.jsonb[:kind] # the default for json and jsonb is nil so we will test dummy operations here + ] + end.to eq([ + "I'm a string!", r.date.as_json, Timex.new(r.datetime.localtime).as_json, 99, 98, + 0.02, 0.01, + false, true, false, + 'json', 'jsonb' + ]) check_errors end @@ -341,4 +359,9 @@ class TypeTest < ActiveRecord::Base end.to eq(r.text) end + it "will update time values with millisecond accuracy" do + sample_time = 12_345.123 + evaluate_ruby { TypeTest.create(datetime: Time.at(sample_time)) } + expect(TypeTest.last.datetime.to_f).to eq sample_time + end end diff --git a/ruby/hyper-model/spec/batch1/misc/self_referencing_belongs_to_spec.rb b/ruby/hyper-model/spec/batch1/misc/self_referencing_belongs_to_spec.rb index c911b7e3b..b0e923daa 100644 --- a/ruby/hyper-model/spec/batch1/misc/self_referencing_belongs_to_spec.rb +++ b/ruby/hyper-model/spec/batch1/misc/self_referencing_belongs_to_spec.rb @@ -12,7 +12,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-model/spec/batch1/misc/where_and_class_method_delegation_spec.rb b/ruby/hyper-model/spec/batch1/misc/where_and_class_method_delegation_spec.rb new file mode 100644 index 000000000..3fc18141c --- /dev/null +++ b/ruby/hyper-model/spec/batch1/misc/where_and_class_method_delegation_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' +require 'rspec-steps' + +RSpec::Steps.steps 'the where method and class delegation', js: true do + + before(:each) do + require 'pusher' + require 'pusher-fake' + Pusher.app_id = "MY_TEST_ID" + Pusher.key = "MY_TEST_KEY" + Pusher.secret = "MY_TEST_SECRET" + require "pusher-fake/support/base" + + Hyperstack.configuration do |config| + config.transport = :pusher + config.channel_prefix = "synchromesh" + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) + end + end + + before(:step) do + stub_const 'TestApplicationPolicy', Class.new + TestApplicationPolicy.class_eval do + always_allow_connection + regulate_all_broadcasts { |policy| policy.send_all } + allow_change(to: :all, on: [:create, :update, :destroy]) { true } + end + ApplicationController.acting_user = nil + isomorphic do + User.alias_attribute :surname, :last_name + User.class_eval do + def self.with_size(attr, size) + where("LENGTH(#{attr}) = ?", size) + end + end + end + + @user1 = User.create(first_name: "Mitch", last_name: "VanDuyn") + User.create(first_name: "Joe", last_name: "Blow") + @user2 = User.create(first_name: "Jan", last_name: "VanDuyn") + User.create(first_name: "Ralph", last_name: "HooBo") + end + + it "can take a hash like value" do + client_option raise_on_js_errors: :debug + expect do + ReactiveRecord.load { User.where(surname: "VanDuyn").pluck(:id, :first_name) } + end.on_client_to eq User.where(surname: "VanDuyn").pluck(:id, :first_name) + end + + it "and will update the collection on the client " do + User.create(first_name: "Paul", last_name: "VanDuyn") + expect do + User.where(surname: "VanDuyn").pluck(:id, :first_name) + end.on_client_to eq User.where(surname: "VanDuyn").pluck(:id, :first_name) + end + + it "or it can take SQL plus params" do + expect do + Hyperstack::Model.load { User.where("first_name LIKE ?", "J%").pluck(:first_name, :surname) } + end.on_client_to eq User.where("first_name LIKE ?", "J%").pluck(:first_name, :surname) + end + + it "class methods will be called from collections" do + expect do + Hyperstack::Model.load { User.where(last_name: 'VanDuyn').with_size(:first_name, 3).pluck('first_name') } + end.on_client_to eq User.where(last_name: 'VanDuyn').with_size(:first_name, 3).pluck('first_name') + end + + it "where-s can be chained (cause they are just class level methods after all)" do + expect do + Hyperstack::Model.load { User.where(last_name: 'VanDuyn').where(first_name: 'Jan').pluck(:id) } + end.on_client_to eq User.where(last_name: 'VanDuyn', first_name: 'Jan').pluck(:id) + end + +end diff --git a/ruby/hyper-model/spec/batch1/policies/regulate_all_broadcasts_spec.rb b/ruby/hyper-model/spec/batch1/policies/regulate_all_broadcasts_spec.rb index 01d06d3d9..2aba5ab49 100644 --- a/ruby/hyper-model/spec/batch1/policies/regulate_all_broadcasts_spec.rb +++ b/ruby/hyper-model/spec/batch1/policies/regulate_all_broadcasts_spec.rb @@ -9,7 +9,7 @@ def react_serializer as_json # does not include type: xxx as per reactive-record end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end def attribute_names @@ -23,7 +23,7 @@ def attribute_names def react_serializer as_json # does not include type: xxx as per reactive-record end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end def attribute_names diff --git a/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb b/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb index 4deae58c8..fc9e626a3 100644 --- a/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb +++ b/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb @@ -13,7 +13,7 @@ def react_serializer def attribute_names [:id, :attr1, :attr2, :attr3, :attr4, :attr5] end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end attr_accessor :id, :attr1, :attr2, :attr3, :attr4, :attr5 @@ -27,7 +27,7 @@ def react_serializer def attribute_names [:id, :attrA, :attrB, :attrC, :attrD, :attrE] end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end attr_accessor :id, :attrA, :attrB, :attrC, :attrD, :attrE @@ -56,7 +56,7 @@ def previous_changes ) end - it "will raise an error if the policy is not sent" do + it "will raise an error if the to method is not used" do stub_const "TestModel1Policy", Class.new TestModel1Policy.class_eval do regulate_broadcast do | policy | @@ -68,6 +68,19 @@ def previous_changes to raise_error("TestModel1 instance broadcast policy not sent to any channel") end + it "will not raise an error if sending to the empty set" do + stub_const "TestModel1Policy", Class.new + TestModel1Policy.class_eval do + regulate_broadcast do | policy | + policy.send_all.to + end + end + model = TestModel1.new(id: 1, attr1: 1, attr2: 2, attr3: 3, attr4: 4, attr5: 5) + expect { |b| Hyperstack::InternalPolicy.regulate_broadcast(model, &b) }. + not_to raise_error("TestModel1 instance broadcast policy not sent to any channel") + end + + it "will intersect all policies for the same channel" do stub_const "TestModel1Policy", Class.new TestModel1Policy.class_eval do diff --git a/ruby/hyper-model/spec/batch1/policies/send_access_xspec.rb b/ruby/hyper-model/spec/batch1/policies/send_access_xspec.rb index b46137406..dc1887651 100644 --- a/ruby/hyper-model/spec/batch1/policies/send_access_xspec.rb +++ b/ruby/hyper-model/spec/batch1/policies/send_access_xspec.rb @@ -13,7 +13,7 @@ def react_serializer def attribute_names [:id, :attr1, :attr2, :attr3, :attr4, :attr5] end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end attr_accessor :id, :attr1, :attr2, :attr3, :attr4, :attr5 @@ -27,7 +27,7 @@ def react_serializer def attribute_names [:id, :attrA, :attrB, :attrC, :attrD, :attrE] end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end attr_accessor :id, :attrA, :attrB, :attrC, :attrD, :attrE diff --git a/ruby/hyper-model/spec/batch2/alias_attribute_spec.rb b/ruby/hyper-model/spec/batch2/alias_attribute_spec.rb index 1d380eee8..f5dc0b382 100644 --- a/ruby/hyper-model/spec/batch2/alias_attribute_spec.rb +++ b/ruby/hyper-model/spec/batch2/alias_attribute_spec.rb @@ -14,7 +14,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-model/spec/batch2/collection_aggregate_methods_spec.rb b/ruby/hyper-model/spec/batch2/collection_aggregate_methods_spec.rb index dfc08b7d6..511e6a1a9 100644 --- a/ruby/hyper-model/spec/batch2/collection_aggregate_methods_spec.rb +++ b/ruby/hyper-model/spec/batch2/collection_aggregate_methods_spec.rb @@ -15,7 +15,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end @@ -36,57 +36,51 @@ it "will not retrieve the entire collection when using #{method}" do FactoryBot.create(:test_model) - expect_promise( - <<~RUBY - Hyperstack::Model - .load { TestModel.#{method} } - .then do |val| - if TestModel.all.instance_variable_get('@collection') - "unnecessary fetch of all" - else - val - end + expect do + Hyperstack::Model + .load { TestModel.send(method) } + .then do |val| + if TestModel.all.instance_variable_get('@collection') + "unnecessary fetch of all" + else + val end - RUBY - ).to eq(TestModel.all.send(method)) + end + end.on_client_to eq(TestModel.all.send(method)) end end %i[any? none?].each do |method| - it 'will retrieve the entire collection when using any? if an arg is passed in' do + it "will retrieve the entire collection when using #{method} if an arg is passed in" do FactoryBot.create(:test_model) - expect_promise( - <<~RUBY - Hyperstack::Model.load do - TestModel.#{method}(TestModel) - end.then do |val| - if TestModel.all.instance_variable_get('@collection') - 'necessary fetch of all' - else - val - end + expect do + Hyperstack::Model.load do + TestModel.send(method, TestModel) + end.then do |val| + if TestModel.all.instance_variable_get('@collection') + 'necessary fetch of all' + else + val end - RUBY - ).to eq('necessary fetch of all') - end + end + end.on_client_to eq('necessary fetch of all') + end unless Opal::VERSION.split('.')[0..1] == ['0', '11'] # opal 0.11 didn't support a value passed to any it 'will retrieve the entire collection when using any? if a block is passed in' do FactoryBot.create(:test_model) - expect_promise( - <<~RUBY - Hyperstack::Model.load do - TestModel.#{method} { |test_model| test_model } - end.then do |val| - if TestModel.all.instance_variable_get('@collection') - 'necessary fetch of all' - else - val - end + expect do + Hyperstack::Model.load do + TestModel.send(method) { |test_model| test_model } + end.then do |val| + if TestModel.all.instance_variable_get('@collection') + 'necessary fetch of all' + else + val end - RUBY - ).to eq('necessary fetch of all') + end + end.on_client_to eq('necessary fetch of all') end end end diff --git a/ruby/hyper-model/spec/batch2/default_scope_spec.rb b/ruby/hyper-model/spec/batch2/default_scope_spec.rb index d30b6fafb..f0bd062b8 100644 --- a/ruby/hyper-model/spec/batch2/default_scope_spec.rb +++ b/ruby/hyper-model/spec/batch2/default_scope_spec.rb @@ -16,7 +16,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-model/spec/batch2/enum_xspec.rb b/ruby/hyper-model/spec/batch2/enum_xspec.rb index 93d7c1c53..6fc2f394a 100644 --- a/ruby/hyper-model/spec/batch2/enum_xspec.rb +++ b/ruby/hyper-model/spec/batch2/enum_xspec.rb @@ -15,7 +15,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end before(:step) do diff --git a/ruby/hyper-model/spec/batch2/has_many_through_spec.rb b/ruby/hyper-model/spec/batch2/has_many_through_spec.rb index 46f3ce204..46fb22308 100644 --- a/ruby/hyper-model/spec/batch2/has_many_through_spec.rb +++ b/ruby/hyper-model/spec/batch2/has_many_through_spec.rb @@ -15,7 +15,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end class ActiveRecord::Base diff --git a/ruby/hyper-model/spec/batch2/non_ar_aggregations_tbdspec.rb b/ruby/hyper-model/spec/batch2/non_ar_aggregations_tbdspec.rb index 7b56b1474..38f84099b 100644 --- a/ruby/hyper-model/spec/batch2/non_ar_aggregations_tbdspec.rb +++ b/ruby/hyper-model/spec/batch2/non_ar_aggregations_tbdspec.rb @@ -14,7 +14,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end User.do_not_synchronize @@ -41,7 +41,6 @@ User.find_by_first_name("Data").save end expect(User.find_by_first_name("Data").data.big_string).to eq("hellohellohello") - binding.pry end it "read it" do diff --git a/ruby/hyper-model/spec/batch2/one_to_one_spec.rb b/ruby/hyper-model/spec/batch2/one_to_one_spec.rb index bea3cb66b..de8447289 100644 --- a/ruby/hyper-model/spec/batch2/one_to_one_spec.rb +++ b/ruby/hyper-model/spec/batch2/one_to_one_spec.rb @@ -15,7 +15,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end class ActiveRecord::Base diff --git a/ruby/hyper-model/spec/batch2/relationships_spec.rb b/ruby/hyper-model/spec/batch2/relationships_spec.rb index 35ed1e07c..c7ef06f16 100644 --- a/ruby/hyper-model/spec/batch2/relationships_spec.rb +++ b/ruby/hyper-model/spec/batch2/relationships_spec.rb @@ -14,7 +14,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb b/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb index e23381c5e..8ea2ba89c 100644 --- a/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb +++ b/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb @@ -20,7 +20,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end @@ -45,7 +45,7 @@ end - it "prerenders a belongs to relationship" do + it "prerenders a belongs to relationship", :prerendering_on do # must be first otherwise check for ajax fails because of race condition # with previous test user_item = User.create(name: 'Fred') @@ -86,7 +86,7 @@ class PrerenderTest < HyperComponent end.to eq(1) end - it "fetches data during prerendering" do + it "fetches data during prerendering", :prerendering_on do 5.times do |i| FactoryBot.create(:todo, title: "Todo #{i}") end @@ -106,6 +106,28 @@ class TestComponent77 < HyperComponent end end + it "destroy receives any errors added by the server" do + class Todo < ActiveRecord::Base + before_destroy do + return true unless title == "don't destroy me" + errors.add :base, "Can't destroy me" + throw(:abort) + end + end + id = Todo.create(title: "don't destroy me").id + expect do + Hyperstack::Model.load do + @todo = Todo.find(id) + end.then do |todo| + todo.destroy.then do |response| + todo.errors.messages unless response[:success] + end + end + end.on_client_to eq("base" => ["Can't destroy me"]) + expect { @todo.destroyed?}.on_client_to be_falsy + end + + it "the limit and offset predefined scopes work" do 5.times do |i| FactoryBot.create(:todo, title: "Todo #{i}") @@ -143,6 +165,71 @@ class TestComponent77 < HyperComponent end.to be_nil end + it "will reload scopes when data arrives too late" do + class ActiveRecord::Base + class << self + def public_columns_hash + @public_columns_hash ||= {} + end + end + end + + class BelongsToModel < ActiveRecord::Base + def self.build_tables + connection.create_table :belongs_to_models, force: true do |t| + t.string :name + t.belongs_to :has_many_model + t.timestamps + end + ActiveRecord::Base.public_columns_hash[name] = columns_hash + end + end + + class HasManyModel < ActiveRecord::Base + def self.build_tables + connection.create_table :has_many_models, force: true do |t| + t.string :name + t.timestamps + end + ActiveRecord::Base.public_columns_hash[name] = columns_hash + end + end + + BelongsToModel.build_tables #rescue nil + HasManyModel.build_tables #rescue nil + + isomorphic do + class BelongsToModel < ActiveRecord::Base + belongs_to :has_many_model + end + + class HasManyModel < ActiveRecord::Base + has_many :belongs_to_models + end + end + + class HasManyModel < ActiveRecord::Base + def belongs_to_models + sleep 0.3 if name == "sleepy-time" + super + end + end + + class ActiveRecord::Base + alias orig_synchromesh_after_create synchromesh_after_create + def synchromesh_after_create + sleep 0.4 if try(:name) == "sleepy-time" + orig_synchromesh_after_create + end + end + + has_many1 = HasManyModel.create(name: "has_many1") + 2.times { |i| BelongsToModel.create(name: "belongs_to_#{i}", has_many_model: has_many1) } + expect { HasManyModel.first.belongs_to_models.count }.on_client_to eq(1) + BelongsToModel.create(name: "sleepy-time", has_many_model: has_many1) + expect { Hyperstack::Model.load { HasManyModel.first.belongs_to_models.count } }.on_client_to eq(3) + end + describe 'can use finder methods on scopes' do before(:each) do diff --git a/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb b/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb new file mode 100644 index 000000000..be2fc20e7 --- /dev/null +++ b/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb @@ -0,0 +1,113 @@ +require 'spec_helper' +require 'test_components' + +describe "has_and_belongs_to_many", js: true do + + alias_method :on_client, :evaluate_ruby + + before(:all) do + require 'pusher' + require 'pusher-fake' + Pusher.app_id = "MY_TEST_ID" + Pusher.key = "MY_TEST_KEY" + Pusher.secret = "MY_TEST_SECRET" + require "pusher-fake/support/base" + + Hyperstack.configuration do |config| + config.transport = :pusher + config.channel_prefix = "synchromesh" + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) + end + + class ActiveRecord::Base + class << self + def public_columns_hash + @public_columns_hash ||= {} + end + end + end + + class Physician < ActiveRecord::Base + def self.build_tables + connection.create_table :physicians, force: true do |t| + t.string :name + t.timestamps + end + ActiveRecord::Base.public_columns_hash[name] = columns_hash + end + end + + class Patient < ActiveRecord::Base + def self.build_tables + connection.create_table :patients, force: true do |t| + t.string :name + t.timestamps + end + ActiveRecord::Base.public_columns_hash[name] = columns_hash + end + end + + class PatientsPhysicianStub < ActiveRecord::Base + def self.build_tables + connection.create_table :patients_physicians, force: true do |t| + t.belongs_to :physician, index: true + t.belongs_to :patient, index: true + end + end + end + + Physician.build_tables #rescue nil + PatientsPhysicianStub.build_tables #rescue nil + Patient.build_tables #rescue nil + + isomorphic do + class Physician < ActiveRecord::Base + has_and_belongs_to_many :patients + end + + class Patient < ActiveRecord::Base + has_and_belongs_to_many :physicians + end + end + end + + before(:each) do + stub_const 'ApplicationPolicy', Class.new + ApplicationPolicy.class_eval do + always_allow_connection + regulate_all_broadcasts { |policy| policy.send_all } + allow_change(to: :all, on: [:create, :update, :destroy]) { true } + end + + size_window(:medium) + end + + it 'works' do + mccoy = Physician.create(name: 'Dr. McCoy') + Patient.create(name: 'James T. Kirk').physicians << mccoy + expect { Physician.first.patients.count }.on_client_to eq(1) + + Patient.create(name: 'Spock').physicians << mccoy + sleep 0.2 + expect { Hyperstack::Model.load { Physician.first.patients.count } }.on_client_to eq(2) + + on_client { Patient.create(name: 'Uhuru') } + 2.times do + on_client { Patient.find(3).physicians << Physician.first } + expect { Patient.find(3).physicians.count }.on_client_to eq(1) + wait_for { Patient.find(3).physicians.count }.to eq(1) + expect { Physician.first.patients.count }.on_client_to eq(3) + expect(Physician.first.patients.count).to eq(3) + + on_client { Patient.find(3).physicians.destroy(Physician.first) } + expect { Patient.find(3).physicians.count }.on_client_to eq(0) + wait_for { Patient.find(3).physicians.count }.to eq(0) + expect { Physician.first.patients.count }.on_client_to eq(2) + expect(Physician.first.patients.count).to eq(2) + end + + on_client { Patient.find(3).physicians.delete(Physician.first) } + expect { Patient.find(3).physicians.count }.on_client_to eq(0) + expect(Patient.find(3).physicians.count).to eq(0) + end +end diff --git a/ruby/hyper-model/spec/batch3/instance_methods_spec.rb b/ruby/hyper-model/spec/batch3/instance_methods_spec.rb new file mode 100644 index 000000000..72f1de57d --- /dev/null +++ b/ruby/hyper-model/spec/batch3/instance_methods_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +require 'test_components' + +describe "Misc Instance Methods", :no_reset, js: true do + + before(:each) do + # spec_helper resets the policy system after each test so we have to setup + # before each test + stub_const 'TestApplication', Class.new + stub_const 'TestApplicationPolicy', Class.new + TestApplicationPolicy.class_eval do + always_allow_connection + regulate_all_broadcasts { |policy| policy.send_all } + allow_change(to: :all) { true } + end + end + + it "increment!" do + user = FactoryBot.create(:user, first_name: 'zero', data_times: 0) + user_id = user.id + evaluate_ruby { User.find(user_id).increment!(:data_times) } + expect(user.reload.data_times).to eq(1) + expect { User.find(user_id).data_times }.to_on_client eq(1) + end + + it "decrement!" do + user = FactoryBot.create(:user, first_name: 'one', data_times: 1) + user_id = user.id + evaluate_ruby { User.find(user_id).decrement!(:data_times) } + expect(user.reload.data_times).to eq(0) + expect { User.find(user_id).data_times }.to_on_client eq(0) + end +end diff --git a/ruby/hyper-model/spec/batch4/default_value_spec.rb b/ruby/hyper-model/spec/batch4/default_value_spec.rb index 635d76810..f439d0a65 100644 --- a/ruby/hyper-model/spec/batch4/default_value_spec.rb +++ b/ruby/hyper-model/spec/batch4/default_value_spec.rb @@ -19,7 +19,7 @@ def self.semaphore Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end @@ -210,7 +210,7 @@ class InputTester < HyperComponent expect(find('#controlled-select').value).to eq('another value') expect(find('#controlled-textarea').value).to eq('another value') - find('#uncontrolled-input').set 'I was set by the user' + find('#uncontrolled-input').set 'I was set by the user', clear: :backspace expect(find('#uncontrolled-input').value).to eq('I was set by the user') find('#uncontrolled-checkbox').set(false) diff --git a/ruby/hyper-model/spec/batch4/scope_spec.rb b/ruby/hyper-model/spec/batch4/scope_spec.rb index 68a738a3c..19a9ff055 100644 --- a/ruby/hyper-model/spec/batch4/scope_spec.rb +++ b/ruby/hyper-model/spec/batch4/scope_spec.rb @@ -14,7 +14,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-model/spec/batch4/scoped_todos_spec.rb b/ruby/hyper-model/spec/batch4/scoped_todos_spec.rb index caa6b233a..03cfa8d77 100644 --- a/ruby/hyper-model/spec/batch4/scoped_todos_spec.rb +++ b/ruby/hyper-model/spec/batch4/scoped_todos_spec.rb @@ -14,7 +14,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-model/spec/batch4/synchromesh_spec.rb b/ruby/hyper-model/spec/batch4/synchromesh_spec.rb index aabd225a0..901075c19 100644 --- a/ruby/hyper-model/spec/batch4/synchromesh_spec.rb +++ b/ruby/hyper-model/spec/batch4/synchromesh_spec.rb @@ -14,7 +14,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-model/spec/batch4/zzz_saving_during_commit_spec.rb b/ruby/hyper-model/spec/batch4/zzz_saving_during_commit_spec.rb index 47db31972..bb704c997 100644 --- a/ruby/hyper-model/spec/batch4/zzz_saving_during_commit_spec.rb +++ b/ruby/hyper-model/spec/batch4/zzz_saving_during_commit_spec.rb @@ -15,7 +15,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end class ActiveRecord::Base diff --git a/ruby/hyper-model/spec/batch5/authorization_spec.rb b/ruby/hyper-model/spec/batch5/authorization_spec.rb index e3da7e7de..14eac5d31 100644 --- a/ruby/hyper-model/spec/batch5/authorization_spec.rb +++ b/ruby/hyper-model/spec/batch5/authorization_spec.rb @@ -20,7 +20,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end @@ -82,7 +82,7 @@ def log(*args) end wait_for_ajax ApplicationController.acting_user = User.new(name: 'fred') - page.evaluate_ruby('Hyperstack.connect("TestApplication")') + evaluate_ruby('Hyperstack.connect("TestApplication")') evaluate_ruby do TestModel.all[0].test_attribute end @@ -100,7 +100,7 @@ def create_permitted? mount 'TestComponent2' wait_for_ajax ApplicationController.acting_user = User.new(name: 'fred') - page.evaluate_ruby('Hyperstack.connect("TestApplication")') + evaluate_ruby('Hyperstack.connect("TestApplication")') TestModel.before_save { self.test_attribute ||= 'top secret' } expect_promise do model = TestModel.new(updated_at: 12) @@ -114,10 +114,10 @@ def create_permitted? mount "TestComponent2" model1 = FactoryBot.create(:test_model, test_attribute: "hello") wait_for_ajax - model1.attributes_on_client(page).should eq({id: 1}) + attributes_on_client(model1).should eq({id: 1}) wait_for_ajax ApplicationController.acting_user = User.new(name: "fred") - page.evaluate_ruby('Hyperstack.connect("TestApplication")') + evaluate_ruby('Hyperstack.connect("TestApplication")') wait_for_ajax # sleep a little, to make sure that on fast systems the seconds precision is covered sleep 2 @@ -127,17 +127,17 @@ def create_permitted? # make sure time zone doesn't matter, as it is about time in space # we get only seconds precision, millisecs are dropped in AR adapters here, but they are in the db with pg # compare only with seconds precision - m1_attr_cl1 = model1.attributes_on_client(page) + m1_attr_cl1 = attributes_on_client(model1) m1_attr_cl1[:id].should eq(1) m1_attr_cl1[:created_at].to_time.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z').should eq(model1.created_at.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z')) m1_attr_cl1[:updated_at].to_time.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z').should eq(model1.updated_at.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z')) ApplicationController.acting_user = User.new(name: "george") - page.evaluate_ruby("Hyperstack.connect(['TestModel', #{model1.id}])") + evaluate_ruby("Hyperstack.connect(['TestModel', #{model1.id}])") wait_for_ajax sleep 2 model1.update_attribute(:completed, true) wait_for_ajax - m1_attr_cl2 = model1.attributes_on_client(page) + m1_attr_cl2 = attributes_on_client(model1) m1_attr_cl2[:id].should eq(1) m1_attr_cl2[:test_attribute].should eq("george") m1_attr_cl2[:completed].should eq(true) @@ -149,10 +149,10 @@ def create_permitted? client_option raise_on_js_errors: :off mount "TestComponent2" model1 = FactoryBot.create(:test_model, test_attribute: "hello") - page.evaluate_ruby('Hyperstack.connect("TestApplication")') + evaluate_ruby('Hyperstack.connect("TestApplication")') model1.update_attribute(:test_attribute, 'george') wait_for_ajax - model1.attributes_on_client(page).should eq({id: 1}) + attributes_on_client(model1).should eq({id: 1}) expect_promise('Hyperstack::Model.load { TestModel.find_by_test_attribute("hello") }').to be_nil end @@ -161,10 +161,10 @@ def create_permitted? mount "TestComponent2" model1 = FactoryBot.create(:test_model, test_attribute: "george") ApplicationController.acting_user = User.new(name: "fred") - page.evaluate_ruby("Hyperstack.connect(['TestModel', #{model1.id}])") + evaluate_ruby("Hyperstack.connect(['TestModel', #{model1.id}])") model1.update_attribute(:completed, true) wait_for_ajax - model1.attributes_on_client(page).should eq({id: 1}) + attributes_on_client(model1).should eq({id: 1}) end end diff --git a/ruby/hyper-model/spec/batch5/load_from_json_xspec.rb b/ruby/hyper-model/spec/batch5/load_from_json_xspec.rb index dfca078ce..4066adb2c 100644 --- a/ruby/hyper-model/spec/batch5/load_from_json_xspec.rb +++ b/ruby/hyper-model/spec/batch5/load_from_json_xspec.rb @@ -26,7 +26,6 @@ end it '*all key' do - binding.pry evaluate_ruby do User.all.count end diff --git a/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb b/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb index 62788821f..de2050291 100644 --- a/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb +++ b/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb @@ -23,7 +23,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-model/spec/batch6/aaa_update_associations_spec.rb b/ruby/hyper-model/spec/batch6/aaa_update_associations_spec.rb index 8667dd1da..99b282917 100644 --- a/ruby/hyper-model/spec/batch6/aaa_update_associations_spec.rb +++ b/ruby/hyper-model/spec/batch6/aaa_update_associations_spec.rb @@ -135,16 +135,20 @@ expect(TodoItem.find_by_title("Jon's first todo!").user.first_name).to eq('Jan') end - it "and a model in a belongs_to relationship can be deleted" do - expect_promise do + it "and a model in a belongs_to relationship can be destroyed" do + expect do ReactiveRecord.load do - User.find_by_first_name("Jan").todo_items.collect { |item| item.itself }.first - end.then do | first | - first.destroy.then do |response| + User.find_by_first_name("Jan").todo_items.collect(&:itself).first + end.then do |first| + first.destroy.then do User.find_by_first_name("Jan").todo_items.all - end + end.tap { @was_destroyed_already = first.destroyed? } end - end.to be_empty + end.on_client_to be_empty + # added following to check that issue #119 got fixed. Item is not + # considered destroyed until we get the status back from server + # that it was destroyed + expect { @was_destroyed_already }.on_client_to be_falsy end it "will update the server properly" do diff --git a/ruby/hyper-model/spec/batch6/inspect_spec.rb b/ruby/hyper-model/spec/batch6/inspect_spec.rb index f408266b5..b47b55a80 100644 --- a/ruby/hyper-model/spec/batch6/inspect_spec.rb +++ b/ruby/hyper-model/spec/batch6/inspect_spec.rb @@ -15,7 +15,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end TodoItem.do_not_synchronize end diff --git a/ruby/hyper-model/spec/batch6/server_method_spec.rb b/ruby/hyper-model/spec/batch6/server_method_spec.rb index 78e6d3328..04bdba059 100644 --- a/ruby/hyper-model/spec/batch6/server_method_spec.rb +++ b/ruby/hyper-model/spec/batch6/server_method_spec.rb @@ -14,7 +14,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end #User.do_not_synchronize diff --git a/ruby/hyper-model/spec/batch6/zzz_on_fetch_error_spec.rb b/ruby/hyper-model/spec/batch6/zzz_on_fetch_error_spec.rb index f1caafb9f..0571417fa 100644 --- a/ruby/hyper-model/spec/batch6/zzz_on_fetch_error_spec.rb +++ b/ruby/hyper-model/spec/batch6/zzz_on_fetch_error_spec.rb @@ -14,7 +14,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-model/spec/batch7/aaa-unit_tests/ar_basics_spec.rb b/ruby/hyper-model/spec/batch7/aaa-unit_tests/ar_basics_spec.rb index 50f71e6f1..36012e34a 100644 --- a/ruby/hyper-model/spec/batch7/aaa-unit_tests/ar_basics_spec.rb +++ b/ruby/hyper-model/spec/batch7/aaa-unit_tests/ar_basics_spec.rb @@ -14,7 +14,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = 'synchromesh' - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-model/spec/batch7/equality_spec.rb b/ruby/hyper-model/spec/batch7/equality_spec.rb index c7b5ad3b5..23e1affac 100644 --- a/ruby/hyper-model/spec/batch7/equality_spec.rb +++ b/ruby/hyper-model/spec/batch7/equality_spec.rb @@ -15,7 +15,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end class ApplicationPolicy diff --git a/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb b/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb index 0e416ac77..ba8037ce1 100644 --- a/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb +++ b/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb @@ -14,7 +14,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end class ActiveRecord::Base @@ -240,32 +240,25 @@ def compare_to_server(model, expression, expected_result, load=true) end it 'changing belongs to relationship on client' do - # not working yet compare_to_server @imageable1, 'pictures.collect(&:name)', ['picture11', 'picture12'] compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture21', 'picture22'] - evaluate_promise do + evaluate_ruby do p = Picture.find(1) p.imageable = Product.find(1) p.save end - # wait_for_ajax # so pusher can initialize compare_to_server @imageable1, 'pictures.collect(&:name)', ['picture12'], false - compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture11', 'picture21', 'picture22'], false + compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture21', 'picture22', 'picture11'], false end it 'changing belongs to relationship on server' do - # compare_to_server @picture11, 'imageable.name', 'imageable1' # here for debug assist - # compare_to_server @picture11, 'imageable.ss', '123' # here for debug assist - - # just debugging here... when id doesn't change we don't realize that data is changing compare_to_server @imageable1, 'pictures.collect(&:name)', ['picture11', 'picture12'] compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture21', 'picture22'] p = Picture.find_by_name('picture11') p.imageable = @imageable2 p.save - # wait_for_ajax # so pusher can initialize compare_to_server @imageable1, 'pictures.collect(&:name)', ['picture12'] - compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture11', 'picture21', 'picture22'] + compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture21', 'picture22', 'picture11'] end diff --git a/ruby/hyper-model/spec/batch7/sti_spec.rb b/ruby/hyper-model/spec/batch7/sti_spec.rb index ca8f1d174..aa69d49fb 100644 --- a/ruby/hyper-model/spec/batch7/sti_spec.rb +++ b/ruby/hyper-model/spec/batch7/sti_spec.rb @@ -15,7 +15,7 @@ Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end class ApplicationPolicy diff --git a/ruby/hyper-model/spec/batch7/while_loading_deprecated_spec.rb b/ruby/hyper-model/spec/batch7/while_loading_deprecated_spec.rb index ebe4062b5..852f381a9 100644 --- a/ruby/hyper-model/spec/batch7/while_loading_deprecated_spec.rb +++ b/ruby/hyper-model/spec/batch7/while_loading_deprecated_spec.rb @@ -22,7 +22,7 @@ def self.semaphore Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end before(:each) do diff --git a/ruby/hyper-model/spec/batch7/while_loading_spec.rb b/ruby/hyper-model/spec/batch7/while_loading_spec.rb index e67d47f3d..6370ec52a 100644 --- a/ruby/hyper-model/spec/batch7/while_loading_spec.rb +++ b/ruby/hyper-model/spec/batch7/while_loading_spec.rb @@ -22,7 +22,7 @@ def self.semaphore Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-model/spec/spec_helper.rb b/ruby/hyper-model/spec/spec_helper.rb index 264f9d3ee..28c895a47 100644 --- a/ruby/hyper-model/spec/spec_helper.rb +++ b/ruby/hyper-model/spec/spec_helper.rb @@ -1,493 +1,352 @@ -# spec/spec_helper.rb ENV["RAILS_ENV"] ||= 'test' -require 'opal' +require 'hyper-spec' +require 'pry' +require 'opal-browser' -def opal? - RUBY_ENGINE == 'opal' -end -def ruby? - !opal? +begin + require File.expand_path('../test_app/config/environment', __FILE__) +rescue LoadError + puts 'Could not load test application. Please ensure you have run `bundle exec rake test_app`' end +require 'rspec/rails' +require 'timecop' +require "rspec/wait" -if RUBY_ENGINE == 'opal' - #require 'hyper-react' - require File.expand_path('../support/react/spec_helpers', __FILE__) - - module Opal - module RSpec - module AsyncHelpers - module ClassMethods - def rendering(title, &block) - klass = Class.new do - include HyperComponent - - def self.block - @block - end - - def self.name - "dummy class" - end - - def render - instance_eval &self.class.block - end - - def self.should_generate(opts={}, &block) - sself = self - @self.async(@title, opts) do - expect_component_to_eventually(sself, &block) - end - end - - def self.should_immediately_generate(opts={}, &block) - sself = self - @self.it(@title, opts) do - element = build_element sself, {} - context = block.arity > 0 ? self : element - expect((element and context.instance_exec(element, &block))).to be(true) - end - end - - end - klass.instance_variable_set("@block", block) - klass.instance_variable_set("@self", self) - klass.instance_variable_set("@title", "it can render #{title}") - klass - end - end +Dir["./spec/support/**/*.rb"].sort.each { |f| require f } + +RSpec.configure do |config| + + if config.formatters.empty? + module Hyperstack + def self.log_import(s) + # turn off import logging unless in verbose mode end end end + config.before :suite do + # grab the prerendered .js file, for debugging purposes + class MiniRacer::Context + alias original_eval eval + def eval(str, options = nil) + original_eval str, options + rescue Exception => e + File.write('react_prerendering_src.js', str) rescue nil + raise e + end + end + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end - RSpec.configure do |config| - config.filter_run_including :opal => true + config.around(:each, :prerendering_on) do |example| + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) end -end -if RUBY_ENGINE != 'opal' - require 'pry' - require 'opal-browser' - begin - require File.expand_path('../test_app/config/environment', __FILE__) - rescue LoadError - puts 'Could not load test application. Please ensure you have run `bundle exec rake test_app`' + config.color = true + config.fail_fast = ENV['FAIL_FAST'] || false + config.fixture_path = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures") + config.infer_spec_type_from_file_location! + config.mock_with :rspec + config.raise_errors_for_deprecations! + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, comment the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + config.after :each do + Rails.cache.clear end - require 'rspec/rails' - require 'timecop' - require "rspec/wait" - #require 'pusher-fake/support/base' - - Dir["./spec/support/**/*.rb"].sort.each { |f| require f } - - RSpec.configure do |config| - config.color = true - config.fail_fast = ENV['FAIL_FAST'] || false - config.fixture_path = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures") - config.infer_spec_type_from_file_location! - config.mock_with :rspec - config.raise_errors_for_deprecations! - - # If you're not using ActiveRecord, or you'd prefer not to run each of your - # examples within a transaction, comment the following line or assign false - # instead of true. - config.use_transactional_fixtures = true - - config.after :each do - Rails.cache.clear - end - config.after(:each) do |example| - unless example.exception - #Object.send(:remove_const, :Application) rescue nil - ObjectSpace.each_object(Class).each do |klass| - if klass < Hyperstack::Regulation - klass.instance_variables.each { |v| klass.instance_variable_set(v, nil) } - end + config.after(:each) do |example| + unless example.exception + #Object.send(:remove_const, :Application) rescue nil + ObjectSpace.each_object(Class).each do |klass| + if klass < Hyperstack::Regulation + klass.instance_variables.each { |v| klass.instance_variable_set(v, nil) } end - PusherFake::Channel.reset if defined? PusherFake end + PusherFake::Channel.reset if defined? PusherFake end - - config.filter_run_including focus: true - config.filter_run_excluding opal: true - config.run_all_when_everything_filtered = true end - FACTORY_BOT = false + config.filter_run_including focus: true + config.filter_run_excluding opal: true + config.run_all_when_everything_filtered = true +end - #require 'rails_helper' - require 'rspec' - require 'rspec/expectations' - begin - require 'factory_bot_rails' - rescue LoadError - end - require 'shoulda/matchers' - require 'database_cleaner' - require 'capybara/rspec' - require 'capybara/rails' - require 'support/component_helpers' - require 'selenium-webdriver' - - def policy_allows_all - stub_const 'TestApplication', Class.new - stub_const 'TestApplicationPolicy', Class.new - TestApplicationPolicy.class_eval do - always_allow_connection - regulate_all_broadcasts { |policy| policy.send_all } - allow_change(to: :all, on: [:create, :update, :destroy]) { true } - end +FACTORY_BOT = false + +#require 'rails_helper' +require 'rspec' +require 'rspec/expectations' +begin + require 'factory_bot_rails' +rescue LoadError +end +require 'shoulda/matchers' +require 'database_cleaner' +require 'capybara/rspec' +require 'capybara/rails' +# require 'support/component_helpers' +require 'selenium-webdriver' + +def policy_allows_all + stub_const 'TestApplication', Class.new + stub_const 'TestApplicationPolicy', Class.new + TestApplicationPolicy.class_eval do + always_allow_connection + regulate_all_broadcasts { |policy| policy.send_all } + allow_change(to: :all, on: [:create, :update, :destroy]) { true } end +end - module React - module IsomorphicHelpers - def self.xxxload_context(ctx, controller, name = nil) - @context = Context.new("#{controller.object_id}-#{Time.now.to_i}", ctx, controller, name) - end +module React + module IsomorphicHelpers + def self.xxxload_context(ctx, controller, name = nil) + @context = Context.new("#{controller.object_id}-#{Time.now.to_i}", ctx, controller, name) end end +end - #Capybara.default_max_wait_time = 4.seconds +#Capybara.default_max_wait_time = 4.seconds - Capybara.server = :puma +Capybara.server = :puma - # The following is deprecated and replaced by the above... just make sure it works - # before removing - # Capybara.server { |app, port| - # require 'puma' - # Puma::Server.new(app).tap do |s| - # s.add_tcp_listener Capybara.server_host, port - # end.run.join - # } +# The following is deprecated and replaced by the above... just make sure it works +# before removing +# Capybara.server { |app, port| +# require 'puma' +# Puma::Server.new(app).tap do |s| +# s.add_tcp_listener Capybara.server_host, port +# end.run.join +# } - module WaitForAjax +module WaitForAjax - def wait_for_ajax - Timeout.timeout(Capybara.default_max_wait_time) do - begin - sleep 0.25 - end until finished_all_ajax_requests? - end + def wait_for_ajax + Timeout.timeout(Capybara.default_max_wait_time) do + begin + sleep 0.25 + end until finished_all_ajax_requests? end + end - def running? - jscode = <<-CODE - (function() { - if (typeof Opal !== "undefined" && Opal.Hyperstack !== undefined) { - try { - return Opal.Hyperstack.$const_get("HTTP")["$active?"](); - } catch(err) { - if (typeof jQuery !== "undefined" && jQuery.active !== undefined) { - return jQuery.active > 0; - } + def running? + jscode = <<-CODE + (function() { + if (typeof Opal !== "undefined" && Opal.Hyperstack !== undefined) { + try { + return Opal.Hyperstack.$const_get("HTTP")["$active?"](); + } catch(err) { + if (typeof jQuery !== "undefined" && jQuery.active !== undefined) { + return jQuery.active > 0; } - } else if (typeof jQuery !== "undefined" && jQuery.active !== undefined) { - return jQuery.active > 0; - } else { - return false; } - })(); - CODE - page.evaluate_script(jscode) - rescue Exception => e - puts "wait_for_ajax failed while testing state of jQuery.active: #{e}" - end + } else if (typeof jQuery !== "undefined" && jQuery.active !== undefined) { + return jQuery.active > 0; + } else { + return false; + } + })(); + CODE + page.evaluate_script(jscode) + rescue Exception => e + puts "wait_for_ajax failed while testing state of jQuery.active: #{e}" + end - def finished_all_ajax_requests? - unless running? - sleep 0.25 # this was 1 second, not sure if its necessary to be so long... - !running? - end - rescue Capybara::NotSupportedByDriverError - true - rescue Exception => e - e.message == "jQuery or Hyperstack::HTTP is not defined" + def finished_all_ajax_requests? + unless running? + sleep 0.25 # this was 1 second, not sure if its necessary to be so long... + !running? end - + rescue Capybara::NotSupportedByDriverError + true + rescue Exception => e + e.message == "jQuery or Hyperstack::HTTP is not defined" end - RSpec.configure do |config| - config.include WaitForAjax +end + +module CheckErrors + def check_errors + logs = page.driver.browser.logs.get(:browser) + errors = logs.select { |e| e.level == "SEVERE" && e.message.present? } + .map { |m| m.message.gsub(/\\n/, "\n") }.to_a + puts "WARNING - FOUND UNEXPECTED ERRORS #{errors}" if errors.present? end +end - RSpec.configure do |config| - # rspec-expectations config goes here. You can use an alternate - # assertion/expectation library such as wrong or the stdlib/minitest - # assertions if you prefer. - config.expect_with :rspec do |expectations| - # Enable only the newer, non-monkey-patching expect syntax. - # For more details, see: - # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax - expectations.syntax = [:should, :expect] - end +RSpec.configure do |config| + config.include WaitForAjax + config.include CheckErrors +end - # rspec-mocks config goes here. You can use an alternate test double - # library (such as bogus or mocha) by changing the `mock_with` option here. - config.mock_with :rspec do |mocks| - # Enable only the newer, non-monkey-patching expect syntax. - # For more details, see: - # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ - mocks.syntax = :expect - - # Prevents you from mocking or stubbing a method that does not exist on - # a real object. This is generally recommended. - mocks.verify_partial_doubles = true - end +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # Enable only the newer, non-monkey-patching expect syntax. + # For more details, see: + # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax + expectations.syntax = [:should, :expect] + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Enable only the newer, non-monkey-patching expect syntax. + # For more details, see: + # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + mocks.syntax = :expect + + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended. + mocks.verify_partial_doubles = true + end - config.include FactoryBot::Syntax::Methods if defined? FactoryBot + config.include FactoryBot::Syntax::Methods if defined? FactoryBot - config.use_transactional_fixtures = false + config.use_transactional_fixtures = false - Capybara.default_max_wait_time = 10.seconds + Capybara.default_max_wait_time = 10.seconds - config.before(:suite) do - #DatabaseCleaner.clean_with(:truncation) - Hyperstack.configuration do |config| - config.transport = :simple_poller - end + config.before(:suite) do + #DatabaseCleaner.clean_with(:truncation) + Hyperstack.configuration do |config| + config.transport = :simple_poller end + end - # config.before(:each) do - # DatabaseCleaner.strategy = :transaction - # end + # config.before(:each) do + # DatabaseCleaner.strategy = :transaction + # end - config.before(:each) do |x| - Hyperstack.class_eval do - def self.on_server? - true - end + config.before(:each) do |x| + Hyperstack.class_eval do + def self.on_server? + true end end + end - config.before(:each) do |ex| - class ActiveRecord::Base - regulate_scope :unscoped - end + config.before(:each) do |ex| + class ActiveRecord::Base + regulate_scope :unscoped end + end - config.before(:each, :js => true) do - DatabaseCleaner.strategy = :truncation - end + config.before(:each, js: true) do + DatabaseCleaner.strategy = :truncation + end - config.before(:each, :js => true) do - size_window - end + config.before(:each, :js => true) do + size_window + end - config.before(:each) do - DatabaseCleaner.start - end + config.before(:each) do + DatabaseCleaner.start + end - config.after(:each) do |example| - # I am assuming the unless was there just to aid in debug when using pry.rescue - # perhaps it could be on a switch detecting presence of pry.rescue? - #unless example.exception - # Clear session data - Capybara.reset_sessions! - # Rollback transaction - DatabaseCleaner.clean - #end - end + config.after(:each) do |example| + # I am assuming the unless was there just to aid in debug when using pry.rescue + # perhaps it could be on a switch detecting presence of pry.rescue? + #unless example.exception + # Clear session data + Capybara.reset_sessions! + # Rollback transaction + DatabaseCleaner.clean + #end + end - config.after(:all, :js => true) do - #size_window(:default) - end + config.after(:all, :js => true) do + #size_window(:default) + end - config.before(:all) do - # reset this variable so if any specs are setting up models locally - # the correct hash gets sent to the client. - ActiveRecord::Base.instance_variable_set('@public_columns_hash', nil) - class ActiveRecord::Base - class << self - alias original_public_columns_hash public_columns_hash - end + config.before(:all) do + # reset this variable so if any specs are setting up models locally + # the correct hash gets sent to the client. + ActiveRecord::Base.instance_variable_set('@public_columns_hash', nil) + class ActiveRecord::Base + class << self + alias original_public_columns_hash public_columns_hash end - module Hyperstack - def self.on_error(_operation, _err, _params, formatted_error_message) - ::Rails.logger.debug( - "#{formatted_error_message}\n\n" + - Pastel.new.red( - 'To further investigate you may want to add a debugging '\ - 'breakpoint to the on_error method in config/initializers/hyperstack.rb' - ) + end + module Hyperstack + def self.on_error(_operation, _err, _params, formatted_error_message) + ::Rails.logger.debug( + "#{formatted_error_message}\n\n" + + Pastel.new.red( + 'To further investigate you may want to add a debugging '\ + 'breakpoint to the on_error method in config/initializers/hyperstack.rb' ) - end + ) end end + end - config.after(:all) do - class ActiveRecord::Base - class << self - alias public_columns_hash original_public_columns_hash - end + config.after(:all) do + class ActiveRecord::Base + class << self + alias public_columns_hash original_public_columns_hash end end + end - config.after(:each, :js => true) do - page.instance_variable_set("@hyper_spec_mounted", false) - end + config.after(:each, :js => true) do + page.instance_variable_set("@hyper_spec_mounted", false) + end - # Fail tests on JavaScript errors in Chrome Headless - class JavaScriptError < StandardError; end + # Fail tests on JavaScript errors in Chrome Headless + class JavaScriptError < StandardError; end - config.after(:each, js: true) do |spec| - logs = page.driver.browser.manage.logs.get(:browser) - if spec.exception - all_messages = logs.select { |e| e.message.present? } - .map { |m| m.message.gsub(/\\n/, "\n") }.to_a - puts "Javascript client console messages:\n\n" + - all_messages.join("\n\n") if all_messages.present? - end - errors = logs.select { |e| e.level == "SEVERE" && e.message.present? } - .map { |m| m.message.gsub(/\\n/, "\n") }.to_a - if client_options[:deprecation_warnings] == :on - warnings = logs.select { |e| e.level == "WARNING" && e.message.present? } - .map { |m| m.message.gsub(/\\n/, "\n") }.to_a - puts "\033[0;33;1m\nJavascript client console warnings:\n\n" + warnings.join("\n\n") + "\033[0;30;21m" if warnings.present? - end - if client_options[:raise_on_js_errors] == :show && errors.present? - puts "\033[031m\nJavascript client console errors:\n\n" + errors.join("\n\n") + "\033[0;30;21m" - elsif client_options[:raise_on_js_errors] == :debug && errors.present? - binding.pry - elsif client_options[:raise_on_js_errors] != :off && errors.present? - raise JavaScriptError, errors.join("\n\n") - end - end - - config.include Capybara::DSL - - # Capybara.register_driver :chrome do |app| - # #caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"excludeSwitches" => [ "ignore-certificate-errors" ]}) - # caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"args" => [ "--window-size=200,200" ]}) - # Capybara::Selenium::Driver.new(app, :browser => :chrome, :desired_capabilities => caps) - # end - - Capybara.register_driver :chromez do |app| - options = {} - options.merge!( - args: %w[auto-open-devtools-for-tabs], - prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } - ) unless ENV['NO_DEBUGGER'] - # this does not seem to work properly. Don't document this feature yet. - options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] - capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options) - Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities) + config.after(:each, js: true) do |spec| + logs = page.driver.browser.logs.get(:browser) + if spec.exception + all_messages = logs.select { |e| e.message.present? } + .map { |m| m.message.gsub(/\\n/, "\n") }.to_a + puts "Javascript client console messages:\n\n" + + all_messages.join("\n\n") if all_messages.present? end - - Capybara.register_driver :chrome_headless_docker_travis do |app| - caps = Selenium::WebDriver::Remote::Capabilities.chrome(loggingPrefs:{browser: 'ALL'}) - options = ::Selenium::WebDriver::Chrome::Options.new - options.add_argument('--headless') - options.add_argument('--no-sandbox') - options.add_argument('--disable-dev-shm-usage') - Capybara::Selenium::Driver.new(app, browser: :chrome, :driver_path => "/usr/lib/chromium-browser/chromedriver", options: options, desired_capabilities: caps) + errors = logs.select { |e| e.level == "SEVERE" && e.message.present? } + .map { |m| m.message.gsub(/\\n/, "\n") }.to_a + if client_options[:deprecation_warnings] == :on + warnings = logs.select { |e| e.level == "WARNING" && e.message.present? } + .map { |m| m.message.gsub(/\\n/, "\n") }.to_a + puts "\033[0;33;1m\nJavascript client console warnings:\n\n" + warnings.join("\n\n") + "\033[0;30;21m" if warnings.present? end - - Capybara.register_driver :selenium_chrome_headless_with_logs do |app| - caps = Selenium::WebDriver::Remote::Capabilities.chrome(loggingPrefs:{browser: 'ALL'}) - browser_options = ::Selenium::WebDriver::Chrome::Options.new() - # browser_options.args << '--some_option' # add whatever browser args and other options you need (--headless, etc) - browser_options.add_argument('--headless') - Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options, desired_capabilities: caps) - # - # - # - # options = ::Selenium::WebDriver::Chrome::Options.new - # options.add_argument('--headless') - # options.add_argument('--no-sandbox') - # options.add_argument('--disable-dev-shm-usage') - # Capybara::Selenium::Driver.new(app, browser: :chrome, :driver_path => "/usr/lib/chromium-browser/chromedriver", options: options) - end - - - class Selenium::WebDriver::Firefox::Profile - - def self.firebug_version - @firebug_version ||= '2.0.13-fx' - end - - def self.firebug_version=(version) - @firebug_version = version - end - - def frame_position - @frame_position ||= 'detached' - end - - def frame_position=(position) - @frame_position = ["left", "right", "top", "detached"].detect do |side| - position && position[0].downcase == side[0] - end || "detached" - end - - def enable_firebug(version = nil) - version ||= Selenium::WebDriver::Firefox::Profile.firebug_version - add_extension(File.expand_path("../bin/firebug-#{version}.xpi", __FILE__)) - - # For some reason, Firebug seems to trigger the Firefox plugin check - # (navigating to https://www.mozilla.org/en-US/plugincheck/ at startup). - # This prevents it. See http://code.google.com/p/selenium/issues/detail?id=4619. - self["extensions.blocklist.enabled"] = false - - # Prevent "Welcome!" tab - self["extensions.firebug.showFirstRunPage"] = false - - # Enable for all sites. - self["extensions.firebug.allPagesActivation"] = "on" - - # Enable all features. - ['console', 'net', 'script'].each do |feature| - self["extensions.firebug.#{feature}.enableSites"] = true - end - - # Closed by default, will open detached. - self["extensions.firebug.framePosition"] = frame_position - self["extensions.firebug.previousPlacement"] = 3 - self["extensions.firebug.defaultPanelName"] = "console" - - # Disable native "Inspect Element" menu item. - self["devtools.inspector.enabled"] = false - self["extensions.firebug.hideDefaultInspector"] = true - end + if client_options[:raise_on_js_errors] == :show && errors.present? + puts "\033[031m\nJavascript client console errors:\n\n" + errors.join("\n\n") + "\033[0;30;21m" + elsif client_options[:raise_on_js_errors] == :debug && errors.present? + binding.pry + elsif client_options[:raise_on_js_errors] != :off && errors.present? + raise JavaScriptError, errors.join("\n\n") end + end - Capybara.register_driver :selenium_with_firebug do |app| - profile = Selenium::WebDriver::Firefox::Profile.new - profile.frame_position = ENV['DRIVER'] && ENV['DRIVER'][2] - profile.enable_firebug - Capybara::Selenium::Driver.new(app, :browser => :firefox, :profile => profile) - end + config.include Capybara::DSL - Capybara.register_driver :chrome do |app| - Capybara::Selenium::Driver.new(app, :browser => :chrome) - end + # Capybara.register_driver :chrome do |app| + # #caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"excludeSwitches" => [ "ignore-certificate-errors" ]}) + # caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"args" => [ "--window-size=200,200" ]}) + # Capybara::Selenium::Driver.new(app, :browser => :chrome, :capabilities => caps) + # end - if ENV['DRIVER'] =~ /^ff/ - Capybara.javascript_driver = :selenium_with_firebug - elsif ENV['DRIVER'] == 'chrome' - Capybara.javascript_driver = :chromez - elsif ENV['DRIVER'] == 'headless' - Capybara.javascript_driver = :selenium_chrome_headless_with_logs #:selenium_chrome_headless - elsif ENV['DRIVER'] == 'travis' - Capybara.javascript_driver = :chrome_headless_docker_travis - else - Capybara.javascript_driver = :selenium_chrome_headless_with_logs #:selenium_chrome_headless - end + # Use legacy hyper-spec on_client behavior + HyperSpec::Helpers.alias_method :on_client, :before_mount +end - include ComponentTestHelpers +FactoryBot.define do + sequence :seq_number do |n| + " #{n}" end - FactoryBot.define do - - sequence :seq_number do |n| - " #{n}" - end - - end if defined? FactoryBot - -end +end if defined? FactoryBot diff --git a/ruby/hyper-model/spec/support/component_helpers.rb b/ruby/hyper-model/spec/support/component_helpers.rb deleted file mode 100644 index e9b1c935e..000000000 --- a/ruby/hyper-model/spec/support/component_helpers.rb +++ /dev/null @@ -1,529 +0,0 @@ -# see component_test_helpers_spec.rb for examples - -require 'parser/current' -require 'unparser' - -Parser::Builders::Default.emit_procarg0 = true - -#require 'pry' - -module ComponentTestHelpers - - def self.compile_to_opal(&block) - Opal.compile(block.source.split("\n")[1..-2].join("\n")) - end - - - TOP_LEVEL_COMPONENT_PATCH = lambda { |&block| Opal.compile(block.source.split("\n")[1..-2].join("\n"))}.call do #ComponentTestHelpers.compile_to_opal do - module Hyperstack - module Internal - module Component - class TopLevelRailsComponent - - # original class declares these params: - # param :component_name - # param :controller - # param :render_params - - class << self - attr_accessor :event_history - - def callback_history_for(proc_name) - event_history[proc_name] - end - - def last_callback_for(proc_name) - event_history[proc_name].last - end - - def clear_callback_history_for(proc_name) - event_history[proc_name] = [] - end - - def event_history_for(event_name) - event_history["on_#{event_name}"] - end - - def last_event_for(event_name) - event_history["on_#{event_name}"].last - end - - def clear_event_history_for(event_name) - event_history["on_#{event_name}"] = [] - end - end - - def component - return @component if @component - paths_searched = [] - component = nil - if @ComponentName.start_with?('::') - # if absolute path of component is given, look it up and fail if not found - paths_searched << @ComponentName - component = begin - Object.const_get(@ComponentName) - rescue NameError - nil - end - else - # if relative path is given, look it up like this - # 1) we check each path + controller-name + component-name - # 2) if we can't find it there we check each path + component-name - # if we can't find it we just try const_get - # so (assuming controller name is Home) - # ::Foo::Bar will only resolve to some component named ::Foo::Bar - # but Foo::Bar will check (in this order) ::Home::Foo::Bar, ::Components::Home::Foo::Bar, ::Foo::Bar, ::Components::Foo::Bar - self.class.search_path.each do |scope| - paths_searched << "#{scope.name}::#{@Controller}::#{@ComponentName}" - component = begin - scope.const_get(@Controller, false).const_get(@ComponentName, false) - rescue NameError - nil - end - break if component != nil - end - unless component - self.class.search_path.each do |scope| - paths_searched << "#{scope.name}::#{@ComponentName}" - component = begin - scope.const_get(@ComponentName, false) - rescue NameError - nil - end - break if component != nil - end - end - end - @component = component - return @component if @component && @component.method_defined?(:render) - raise "Could not find component class '#{@ComponentName}' for @Controller '#{@Controller}' in any component directory. Tried [#{paths_searched.join(", ")}]" - end - - before_mount do - TopLevelRailsComponent.event_history = Hash.new { |h, k| h[k] = [] } - component.validator.rules.each do |name, rules| - next unless rules[:type] == Proc - - TopLevelRailsComponent.event_history[name] = [] - @RenderParams[name] = lambda do |*args| - TopLevelRailsComponent.event_history[name] << args - end - end - end - - def render - Hyperstack::Internal::Component::RenderingContext.render(component, @RenderParams) - end - end - end - end - end - - # module React - # class TopLevelRailsComponent # NEEDS TO BE Hyperstack::Internal::Component::TopLevelRailsComponent - # - # class << self - # attr_accessor :event_history - # - # def callback_history_for(proc_name) - # event_history[proc_name] - # end - # - # def last_callback_for(proc_name) - # event_history[proc_name].last - # end - # - # def clear_callback_history_for(proc_name) - # event_history[proc_name] = [] - # end - # - # def event_history_for(event_name) - # event_history["_on#{event_name.event_camelize}"] - # end - # - # def last_event_for(event_name) - # event_history["_on#{event_name.event_camelize}"].last - # end - # - # def clear_event_history_for(event_name) - # event_history["_on#{event_name.event_camelize}"] = [] - # end - # - # end - # - # def component - # return @component if @component - # paths_searched = [] - # component = nil - # if params.component_name.start_with?('::') - # # if absolute path of component is given, look it up and fail if not found - # paths_searched << params.component_name - # component = begin - # Object.const_get(params.component_name) - # rescue NameError - # nil - # end - # else - # # if relative path is given, look it up like this - # # 1) we check each path + controller-name + component-name - # # 2) if we can't find it there we check each path + component-name - # # if we can't find it we just try const_get - # # so (assuming controller name is Home) - # # ::Foo::Bar will only resolve to some component named ::Foo::Bar - # # but Foo::Bar will check (in this order) ::Home::Foo::Bar, ::Components::Home::Foo::Bar, ::Foo::Bar, ::Components::Foo::Bar - # self.class.search_path.each do |scope| - # paths_searched << "#{scope.name}::#{params.controller}::#{params.component_name}" - # component = begin - # scope.const_get(params.controller, false).const_get(params.component_name, false) - # rescue NameError - # nil - # end - # break if component != nil - # end - # unless component - # self.class.search_path.each do |scope| - # paths_searched << "#{scope.name}::#{params.component_name}" - # component = begin - # scope.const_get(params.component_name, false) - # rescue NameError - # nil - # end - # break if component != nil - # end - # end - # end - # @component = component - # return @component if @component && @component.method_defined?(:render) - # raise "Could not find component class '#{params.component_name}' for params.controller '#{params.controller}' in any component directory. Tried [#{paths_searched.join(", ")}]" - # end - # - # before_mount do - # # NEEDS TO BE Hyperstack::Internal::Component::TopLevelRailsComponent - # TopLevelRailsComponent.event_history = Hash.new {|h,k| h[k] = [] } - # component.validator.rules.each do |name, rules| - # if rules[:type] == Proc - # # NEEDS TO BE Hyperstack::Internal::Component::TopLevelRailsComponent - # TopLevelRailsComponent.event_history[name] = [] - # params.render_params[name] = lambda { |*args| TopLevelRailsComponent.event_history[name] << args.collect { |arg| Native(arg).to_n } } - # end - # end - # end - # - # def render - # Hyperstack::Internal::Component::RenderingContext.render(component, params.render_params) - # end - # end - # end - end - - def build_test_url_for(controller) - - unless controller - Object.const_set("ReactTestController", Class.new(ApplicationController)) unless defined?(::ReactTestController) - controller = ::ReactTestController - end - - route_root = controller.name.gsub(/Controller$/,"").underscore - - unless controller.method_defined? :test - controller.class_eval do - define_method(:test) do - route_root = self.class.name.gsub(/Controller$/,"").underscore - test_params = Rails.cache.read("/#{route_root}/#{params[:id]}") - @component_name = test_params[0] - @component_params = test_params[1] - render_params = test_params[2] - render_on = render_params.delete(:render_on) || :client_only - mock_time = render_params.delete(:mock_time) - style_sheet = render_params.delete(:style_sheet) - javascript = render_params.delete(:javascript) - code = render_params.delete(:code) - page = "<%= react_component @component_name, @component_params, { prerender: #{render_on != :client_only} } %>" # false should be: "#{render_on != :client_only} } %>" but its not working in the gem testing harness - unless render_on == :server_only - page = "\n#{page}" - page = "\n"+page if code - end - - #TODO figure out how to auto insert this line???? something like: - #page = "<%= javascript_include_tag 'reactrb-router' %>\n#{page}" - - if (render_on != :server_only && !render_params[:layout]) || javascript - #page = "\n"+page - page = "<%= javascript_include_tag '#{javascript || 'application'}' %>\n"+page - end - if mock_time || (defined?(Timecop) && Timecop.top_stack_item) - puts "********** WARNING LOLEX NOT AVAILABLE TIME ON CLIENT WILL NOT MATCH SERVER **********" - # unix_millis = ((mock_time || Time.now).to_f * 1000.0).to_i - # page = "<%= javascript_include_tag 'spec/libs/lolex' %>\n"+ - # "\n"+page - end - if !render_params[:layout] || style_sheet - page = "<%= stylesheet_link_tag '#{style_sheet || 'application'}' rescue nil %>\n"+page - end - page = "\n#{page}" - title = view_context.escape_javascript(ComponentTestHelpers.current_example.description) - title = "#{title}...continued." if ComponentTestHelpers.description_displayed - page = "\n#{page}" - ComponentTestHelpers.description_displayed = true - render_params[:inline] = page - render render_params - end - end - - # test_routes = Proc.new do - # get "/#{route_root}/:id", to: "#{route_root}#test" - # end - # Rails.application.routes.eval_block(test_routes) - - begin - routes = Rails.application.routes - routes.disable_clear_and_finalize = true - routes.clear! - routes.draw do - get "/#{route_root}/:id", to: "#{route_root}#test" - end - Rails.application.routes_reloader.paths.each{ |path| load(path) } - routes.finalize! - ActiveSupport.on_load(:action_controller) { routes.finalize! } - ensure - routes.disable_clear_and_finalize = false - end - end - - "/#{route_root}/#{@test_id = (@test_id || 0) + 1}" - - end - - def isomorphic(&block) - yield - on_client(&block) - end - - def evaluate_ruby(str="", opts={}, &block) - insure_mount - str = "#{str}\n#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}" if block - js = Opal.compile(str).gsub("// Prepare super implicit arguments\n", "").gsub("\n","").gsub("(Opal);","(Opal)") - JSON.parse(evaluate_script("[#{js}].$to_json()"), opts).first - end - - def expect_evaluate_ruby(str = '', opts = {}, &block) - expect(evaluate_ruby(add_opal_block(str, block), opts)) - end - - def add_opal_block(str, block) - # big assumption here is that we are going to follow this with a .to - # hence .children.first followed by .children.last - # probably should do some kind of "search" to make this work nicely - return str unless block - "#{str}\n"\ - "#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.first.children.last}" - end - - def evaluate_promise(str = '', opts = {}, &block) - insure_mount - str = "#{str}\n#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}" if block - str = "#{str}.then { |args| args = [args]; `window.hyper_spec_promise_result = args` }" - js = Opal.compile(str).gsub("\n","").gsub("(Opal);","(Opal)") - page.evaluate_script("window.hyper_spec_promise_result = false") - page.execute_script(js) - Timeout.timeout(100) do #Capybara.default_max_wait_time) do - loop do - sleep 0.25 - break if page.evaluate_script("!!window.hyper_spec_promise_result") - end - end - JSON.parse(page.evaluate_script("window.hyper_spec_promise_result.$to_json()"), opts).first - end - - def expect_promise(str = '', opts = {}, &block) - insure_mount - expect(evaluate_promise(add_opal_block(str, block), opts)) - end - - def ppr(str) - js = Opal.compile(str).gsub("\n","").gsub("(Opal);","(Opal)") - execute_script("console.log(#{js})") - end - - - def on_client(&block) - @client_code = "#{@client_code}#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}\n" - end - - def debugger - `debugger` - nil - end - - class << self - attr_accessor :current_example - attr_accessor :description_displayed - def display_example_description - "" - end - end - - def insure_mount - # rescue in case page is not defined... - mount unless page.instance_variable_get("@hyper_spec_mounted") rescue nil - end - - def client_option(opts = {}) - @client_options ||= {} - @client_options.merge! opts - end - - alias client_options client_option - - def mount(component_name = nil, params = nil, opts = {}, &block) - unless params - params = opts - opts = {} - end - test_url = build_test_url_for(opts.delete(:controller)) - if block || @client_code || component_name.nil? - block_with_helpers = <<-code - module ComponentHelpers - def self.js_eval(s) - `eval(s)` - end - def self.dasherize(s) - %x{ - return s.replace(/[-_\\s]+/g, '-') - .replace(/([A-Z\\d]+)([A-Z][a-z])/g, '$1-$2') - .replace(/([a-z\\d])([A-Z])/g, '$1-$2') - .toLowerCase() - } - end - def self.add_class(class_name, styles={}) - style = styles.collect { |attr, value| "\#{dasherize(attr)}:\#{value}"}.join("; ") - cs = class_name.to_s - %x{ - var style_el = document.createElement("style"); - var css = "." + cs + " { " + style + " }"; - style_el.type = "text/css"; - if (style_el.styleSheet){ - style_el.styleSheet.cssText = css; - } else { - style_el.appendChild(document.createTextNode(css)); - } - document.head.appendChild(style_el); - } - end - end - class HyperComponent::HyperTestDummy < HyperComponent - render {} - end - #{@client_code} - #{Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last) if block} - code - opts[:code] = Opal.compile(block_with_helpers) - end - component_name ||= 'HyperComponent::HyperTestDummy' - ::Rails.cache.write(test_url, [component_name, params, opts]) - - # this code copied from latest hyper-spec - test_code_key = "hyper_spec_prerender_test_code.js" - #::Rails.configuration.react.server_renderer_options[:files] ||= ['hyperstack-prerender-loader.js'] - @@original_server_render_files ||= ::Rails.configuration.react.server_renderer_options[:files] || [] #'hyperstack-prerender-loader.js'] - if opts[:render_on] == :both || opts[:render_on] == :server_only - unless opts[:code].blank? - ::Rails.cache.write(test_code_key, opts[:code]) - ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + [test_code_key] - ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes - else - ::Rails.cache.delete(test_code_key) - ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files - ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes - end - end - # end of copied code - - visit test_url - wait_for_ajax unless opts[:no_wait] - page.instance_variable_set("@hyper_spec_mounted", true) - end - - [:callback_history_for, :last_callback_for, :clear_callback_history_for, :event_history_for, :last_event_for, :clear_event_history_for].each do |method| - define_method(method) { |event_name| evaluate_script("Opal.React.TopLevelRailsComponent.$#{method}('#{event_name}')") } - end - - def run_on_client(&block) - script = Opal.compile(Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last) - execute_script(script) - end - - def open_in_chrome - if false && ['linux', 'freebsd'].include?(`uname`.downcase) - `google-chrome http://#{page.server.host}:#{page.server.port}#{page.current_path}` - else - `open http://#{page.server.host}:#{page.server.port}#{page.current_path}` - end - while true - sleep 1.hour - end - end - - def pause(message = nil) - if message - puts message - page.evaluate_ruby "puts #{message.inspect}.to_s + ' (type go() to continue)'" - end - page.evaluate_script("window.hyper_spec_waiting_for_go = true") - loop do - sleep 0.25 - break unless page.evaluate_script("window.hyper_spec_waiting_for_go") - end - end - - def size_window(width=nil, height=nil) - width, height = width if width.is_a? Array - portrait = true if height == :portrait - case width - when :small - width, height = [480, 320] - when :mobile - width, height = [640, 480] - when :tablet - width, height = [960, 640] - when :large - width, height = [1920, 6000] - when :default, nil - width, height = [1024, 768] - end - if portrait - width, height = [height, width] - end - if page.driver.browser.respond_to?(:manage) - page.driver.browser.manage.window.resize_to(width, height) - elsif page.driver.respond_to?(:resize) - page.driver.resize(width, height) - end - end - - def check_errors - logs = page.driver.browser.manage.logs.get(:browser) - errors = logs.select { |e| e.level == "SEVERE" && e.message.present? } - .map { |m| m.message.gsub(/\\n/, "\n") }.to_a - puts "WARNING - FOUND UNEXPECTED ERRORS #{errors}" if errors.present? - end - -end - -RSpec.configure do |config| - config.before(:each) do |example| - ComponentTestHelpers.current_example = example - ComponentTestHelpers.description_displayed = false - end - config.before(:all) do - ActiveRecord::Base.class_eval do - def attributes_on_client(page) - page.evaluate_ruby("#{self.class.name}.find(#{id}).attributes", symbolize_names: true) - end - end - end -end diff --git a/ruby/hyper-model/spec/test_app/app/assets/config/manifest.js b/ruby/hyper-model/spec/test_app/app/assets/config/manifest.js new file mode 100644 index 000000000..9c361e667 --- /dev/null +++ b/ruby/hyper-model/spec/test_app/app/assets/config/manifest.js @@ -0,0 +1,2 @@ + //= link_directory ../javascripts .js + //= link_directory ../stylesheets .css \ No newline at end of file diff --git a/ruby/hyper-model/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-model/spec/test_app/app/assets/javascripts/application.js index 4cd60b03a..2f767b8b7 100644 --- a/ruby/hyper-model/spec/test_app/app/assets/javascripts/application.js +++ b/ruby/hyper-model/spec/test_app/app/assets/javascripts/application.js @@ -1,6 +1,6 @@ -//= require 'react' -//= require 'react_ujs' -//= require 'components' +//xx= require 'react' +//xx= require 'react_ujs' +//xx= require 'components' //= require action_cable -//= require 'hyperstack/pusher' -Opal.load('components'); +//xx= require 'hyperstack/pusher' +//= require hyperstack-loader diff --git a/ruby/hyper-model/spec/test_app/app/assets/javascripts/server_rendering.js b/ruby/hyper-model/spec/test_app/app/assets/javascripts/server_rendering.js deleted file mode 100644 index 29f5acdd8..000000000 --- a/ruby/hyper-model/spec/test_app/app/assets/javascripts/server_rendering.js +++ /dev/null @@ -1,4 +0,0 @@ -//= require 'react-server' -//= require 'react_ujs' -//= require 'components' -Opal.load('components') \ No newline at end of file diff --git a/ruby/hyper-model/spec/test_app/app/views/components/base_classes.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/components/base_classes.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/views/components/base_classes.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/components/base_classes.rb diff --git a/ruby/hyper-model/spec/test_app/app/views/components/show.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/components/show.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/views/components/show.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/components/show.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/active_record_patch.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/active_record_patch.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/active_record_patch.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/active_record_patch.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/address.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/address.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/address.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/address.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/application_record.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/application_record.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/application_record.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/application_record.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/bone.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/bone.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/bone.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/bone.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/cat.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/cat.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/cat.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/cat.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/child_model.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/child_model.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/child_model.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/child_model.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/comment.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/comment.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/comment.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/comment.rb diff --git a/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb new file mode 100644 index 000000000..82ce7fbd4 --- /dev/null +++ b/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb @@ -0,0 +1,18 @@ +class DefaultTest < ActiveRecord::Base +def self.build_tables + connection.create_table :default_tests, force: true do |t| + t.string :string, default: "I'm a string!" + t.date :date, default: Date.today + t.datetime :datetime, default: Time.now + t.integer :integer_from_string, default: "99" + t.integer :integer_from_int, default: 98 + t.float :float_from_string, default: "0.02" + t.float :float_from_float, default: 0.01 + t.boolean :boolean_from_falsy_string, default: "OFF" + t.boolean :boolean_from_truthy_string, default: "something-else" + t.boolean :boolean_from_falsy_value, default: false + t.json :json, default: {kind: :json} + t.jsonb :jsonb, default: {kind: :jsonb} + end + end +end diff --git a/ruby/hyper-model/spec/test_app/app/models/public/dog.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/dog.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/dog.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/dog.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/pet.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/pet.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/pet.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/pet.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/scratching_post.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/scratching_post.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/scratching_post.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/scratching_post.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/some_model.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/some_model.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/some_model.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/some_model.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/test_model.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/test_model.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/test_model.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/test_model.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/todo.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/todo.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/todo.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/todo.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/todo_item.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/todo_item.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/todo_item.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/todo_item.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/type_test.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb similarity index 90% rename from ruby/hyper-model/spec/test_app/app/models/public/type_test.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb index 86b319a72..50fc55d53 100644 --- a/ruby/hyper-model/spec/test_app/app/models/public/type_test.rb +++ b/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb @@ -13,6 +13,8 @@ def self.build_tables t.text(:text) t.time(:time) t.timestamp(:timestamp) + t.json(:json) + t.jsonb(:jsonb) end end -end \ No newline at end of file +end diff --git a/ruby/hyper-model/spec/test_app/app/models/public/user.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/user.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/user.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/user.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/_react_public_models.rb b/ruby/hyper-model/spec/test_app/app/models/_react_public_models.rb deleted file mode 100644 index 9e0c5b16f..000000000 --- a/ruby/hyper-model/spec/test_app/app/models/_react_public_models.rb +++ /dev/null @@ -1,2 +0,0 @@ -# app/models/_react_public_models.rb -require_tree './public' if RUBY_ENGINE == 'opal' diff --git a/ruby/hyper-model/spec/test_app/app/models/public/default_test.rb b/ruby/hyper-model/spec/test_app/app/models/public/default_test.rb deleted file mode 100644 index 0c47a8785..000000000 --- a/ruby/hyper-model/spec/test_app/app/models/public/default_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -class DefaultTest < ActiveRecord::Base -def self.build_tables - connection.create_table :default_tests, force: true do |t| - t.string :string, default: "I'm a string!" - t.date :date, default: Date.today - t.datetime :datetime, default: Time.now - end - end -end diff --git a/ruby/hyper-model/spec/test_app/config/application.rb b/ruby/hyper-model/spec/test_app/config/application.rb index 935758929..c3c7f580a 100644 --- a/ruby/hyper-model/spec/test_app/config/application.rb +++ b/ruby/hyper-model/spec/test_app/config/application.rb @@ -12,20 +12,18 @@ module TestApp class Application < Rails::Application config.action_cable.allowed_request_origins = [/http\:\/\/127\.0\.0\.1\:[0-9]*/] - config.eager_load_paths += %W(#{config.root}/app/models/public) - config.autoload_paths += %W(#{config.root}/app/models/public) + # config.eager_load_paths += %W(#{config.root}/app/models/public) + # config.autoload_paths += %W(#{config.root}/app/models/public) config.assets.paths << ::Rails.root.join('app', 'models').to_s - config.hyperstack.auto_config = false config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check = false + config.opal.arity_check_enabled = true config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true config.opal.spec_location = 'spec-opal' config.assets.cache_store = :null_store - config.hyperstack.auto_config = false config.react.server_renderer_options = { files: ['server_rendering.js'] diff --git a/ruby/hyper-model/spec/test_app/config/database.yml b/ruby/hyper-model/spec/test_app/config/database.yml index ffe0fdf4f..ad8c9dd1c 100644 --- a/ruby/hyper-model/spec/test_app/config/database.yml +++ b/ruby/hyper-model/spec/test_app/config/database.yml @@ -4,10 +4,27 @@ # Ensure the SQLite 3 gem is defined in your Gemfile # gem 'sqlite3' # +# default: &default +# adapter: mysql2 +# encoding: utf8 +# username: root +# +# development: +# <<: *default +# database: hyper_mesh_development_db +# +# test: +# <<: *default +# database: hyper_mesh_test_db +# +# production: +# <<: *default +# database: hyper_mesh_production_db + default: &default - adapter: mysql2 - encoding: utf8 - username: root + adapter: postgresql + pool: 5 + timeout: 5000 development: <<: *default @@ -20,23 +37,3 @@ test: production: <<: *default database: hyper_mesh_production_db - -# default: &default -# adapter: sqlite3 -# pool: 5 -# timeout: 10000 -# -# 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/ruby/hyper-model/spec/test_app/config/initializers/hyperstack.rb b/ruby/hyper-model/spec/test_app/config/initializers/hyperstack.rb new file mode 100644 index 000000000..7eca39707 --- /dev/null +++ b/ruby/hyper-model/spec/test_app/config/initializers/hyperstack.rb @@ -0,0 +1,5 @@ +Hyperstack.import 'hyperstack/pusher', client_only: true +Hyperstack.cancel_import 'hyperstack/autoloader' +Hyperstack.cancel_import 'hyperstack/autoloader_starter' +Hyperstack.cancel_import 'config/initializers/inflections.rb' +def (Hyperstack::Connection).build_tables?; false; end diff --git a/ruby/hyper-model/spec/test_app/config/initializers/synchromesh.rb b/ruby/hyper-model/spec/test_app/config/initializers/synchromesh.rb deleted file mode 100644 index 8d6858222..000000000 --- a/ruby/hyper-model/spec/test_app/config/initializers/synchromesh.rb +++ /dev/null @@ -1,20 +0,0 @@ -# require 'pusher' -# Pusher.app_id = "MY_TEST_ID" -# Pusher.key = "MY_TEST_KEY" -# Pusher.secret = "MY_TEST_SECRET" -# require 'pusher-fake' -# -# HyperMesh.configuration do |config| -# config.transport = :pusher -# config.channel_prefix = "synchromesh" -# config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) -# end -class MiniRacer::Context - alias original_eval eval - def eval(str, options=nil) - original_eval str, options - rescue Exception => e - File.write('react_prerendering_src.js', str) rescue nil - raise e - end -end diff --git a/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb b/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb index 86dec7925..03022f4ba 100644 --- a/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb +++ b/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb @@ -49,8 +49,6 @@ def change t.references :author t.integer "user_id" t.integer "todo_item_id" - t.datetime "created_at" - t.datetime "updated_at" end create_table "addresses" do |t| diff --git a/ruby/hyper-model/spec/test_app/db/migrate/20210228200459_add_connection_tables.rb b/ruby/hyper-model/spec/test_app/db/migrate/20210228200459_add_connection_tables.rb new file mode 100644 index 000000000..e2faf1096 --- /dev/null +++ b/ruby/hyper-model/spec/test_app/db/migrate/20210228200459_add_connection_tables.rb @@ -0,0 +1,16 @@ +class AddConnectionTables < ActiveRecord::Migration[5.2] + def change + create_table "hyperstack_connections", force: :cascade do |t| + t.string "channel" + t.string "session" + t.datetime "created_at" + t.datetime "expires_at" + t.datetime "refresh_at" + end + + create_table "hyperstack_queued_messages", force: :cascade do |t| + t.text "data" + t.integer "connection_id" + end + end +end diff --git a/ruby/hyper-model/spec/test_app/db/schema.rb b/ruby/hyper-model/spec/test_app/db/schema.rb deleted file mode 100644 index d5f44084b..000000000 --- a/ruby/hyper-model/spec/test_app/db/schema.rb +++ /dev/null @@ -1,119 +0,0 @@ -# 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. -# -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). -# -# It's strongly recommended that you check this file into your version control system. - -ActiveRecord::Schema.define(version: 2016_07_31_182106) do - - create_table "addresses", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "street" - t.string "city" - t.string "state" - t.string "zip" - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "bones", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.integer "dog_id" - end - - create_table "child_models", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "child_attribute" - t.bigint "test_model_id" - t.index ["test_model_id"], name: "index_child_models_on_test_model_id" - end - - create_table "comments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.text "comment" - t.datetime "created_at" - t.datetime "updated_at" - t.bigint "todo_id" - t.bigint "author_id" - t.integer "user_id" - t.integer "todo_item_id" - t.index ["author_id"], name: "index_comments_on_author_id" - t.index ["todo_id"], name: "index_comments_on_todo_id" - end - - create_table "hyperstack_connections", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "channel" - t.string "session" - t.datetime "created_at" - t.datetime "expires_at" - t.datetime "refresh_at" - end - - create_table "hyperstack_queued_messages", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.text "data" - t.integer "connection_id" - end - - create_table "pets", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.integer "owner_id" - end - - create_table "scratching_posts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.integer "cat_id" - end - - create_table "test_models", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "test_attribute" - t.boolean "completed" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "todo_items", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "title" - t.text "description" - t.boolean "complete" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "user_id" - t.integer "comment_id" - end - - create_table "todos", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "title" - t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "completed", default: false, null: false - t.bigint "created_by_id" - t.bigint "owner_id" - t.index ["created_by_id"], name: "index_todos_on_created_by_id" - t.index ["owner_id"], name: "index_todos_on_owner_id" - end - - create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "role" - t.bigint "manager_id" - t.string "first_name" - t.string "last_name" - t.string "email" - t.datetime "created_at" - t.datetime "updated_at" - t.string "address_street" - t.string "address_city" - t.string "address_state" - t.string "address_zip" - t.integer "address_id" - t.string "address2_street" - t.string "address2_city" - t.string "address2_state" - t.string "address2_zip" - t.string "data_string" - t.integer "data_times" - t.integer "test_enum" - t.index ["manager_id"], name: "index_users_on_manager_id" - end - -end diff --git a/ruby/hyper-model/spec/test_components.rb b/ruby/hyper-model/spec/test_components.rb index 775e16433..133e477cb 100644 --- a/ruby/hyper-model/spec/test_components.rb +++ b/ruby/hyper-model/spec/test_components.rb @@ -1,6 +1,6 @@ RSpec.configure do |config| config.before(:all) do - on_client do + before_mount do class TestComponent < HyperComponent param scope: :all render(DIV) do diff --git a/ruby/hyper-operation/.travis.yml b/ruby/hyper-operation/.travis.yml index 3eebae118..16e36bca3 100644 --- a/ruby/hyper-operation/.travis.yml +++ b/ruby/hyper-operation/.travis.yml @@ -7,6 +7,7 @@ rvm: - ruby-head services: - mysql + - redis-server env: - DRIVER=google-chrome TZ=Europe/Berlin matrix: diff --git a/ruby/hyper-operation/hyper-operation.gemspec b/ruby/hyper-operation/hyper-operation.gemspec index 49b95945f..98358a6d8 100644 --- a/ruby/hyper-operation/hyper-operation.gemspec +++ b/ruby/hyper-operation/hyper-operation.gemspec @@ -10,12 +10,8 @@ Gem::Specification.new do |spec| spec.email = ['mitch@catprint.com', 'jan@kursator.com'] spec.summary = 'HyperOperations are the swiss army knife of the Hyperstack' spec.homepage = 'http://ruby-hyperstack.org' + spec.metadata = { 'documentation_uri' => 'https://docs.hyperstack.org/' } spec.license = 'MIT' - # spec.metadata = { - # "homepage_uri" => 'http://ruby-hyperstack.org', - # "source_code_uri" => 'https://github.com/ruby-hyperstack/hyper-component' - # } - spec.files = `git ls-files -z` .split("\x0") .reject { |f| f.match(%r{^(gemfiles|examples|spec)/}) } @@ -29,24 +25,25 @@ Gem::Specification.new do |spec| spec.add_dependency 'opal-activesupport', '~> 0.3.1' spec.add_dependency 'tty-table' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'hyper-spec', Hyperstack::Operation::VERSION spec.add_development_dependency 'mysql2' - spec.add_development_dependency 'opal', '>= 0.11.0', '< 0.12.0' - spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' + # spec.add_development_dependency 'opal-browser', '~> 0.2.0' + spec.add_development_dependency 'opal-rails' spec.add_development_dependency 'pry-rescue' - spec.add_development_dependency 'puma' + spec.add_development_dependency 'pry-stack_explorer' + spec.add_development_dependency 'puma', '<= 5.4.0' spec.add_development_dependency 'pusher' spec.add_development_dependency 'pusher-fake' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' + spec.add_development_dependency 'redis' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' spec.add_development_dependency 'rspec-wait' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyper-operation/lib/hyper-operation.rb b/ruby/hyper-operation/lib/hyper-operation.rb index e01d34d29..da470051f 100644 --- a/ruby/hyper-operation/lib/hyper-operation.rb +++ b/ruby/hyper-operation/lib/hyper-operation.rb @@ -28,6 +28,7 @@ def titleize require 'hyper-operation/railway/validations' require 'hyper-operation/server_op' require 'hyper-operation/boot' + require 'hyper-operation/async_sleep' else require 'tty-table' require 'hyperstack-config' @@ -45,7 +46,7 @@ def titleize require 'hyper-operation/transport/client_drivers' require 'hyper-operation/transport/acting_user' require 'opal-activesupport' - require 'hyper-operation/delay_and_interval' + require 'hyper-operation/async_sleep' require 'hyper-operation/exception' require 'hyper-operation/promise' require 'hyper-operation/railway' diff --git a/ruby/hyper-operation/lib/hyper-operation/api.rb b/ruby/hyper-operation/lib/hyper-operation/api.rb index 7ba464595..5b11e3d4f 100644 --- a/ruby/hyper-operation/lib/hyper-operation/api.rb +++ b/ruby/hyper-operation/lib/hyper-operation/api.rb @@ -46,8 +46,12 @@ def _run(*args) @_railway.process_params(args) @_railway.process_validations @_railway.run - @_railway.dispatch - @_railway.result + # return the result from dispatch in case there is an error + if (dispatch_result = @_railway.dispatch).rejected? + dispatch_result + else + @_railway.result + end end end diff --git a/ruby/hyper-operation/lib/hyper-operation/async_sleep.rb b/ruby/hyper-operation/lib/hyper-operation/async_sleep.rb new file mode 100644 index 000000000..800b5f20e --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/async_sleep.rb @@ -0,0 +1,23 @@ +module Hyperstack + module AsyncSleep + if RUBY_ENGINE == 'opal' + def self.every(*args, &block) + every(*args, &block) + end + + def self.after(*args, &block) + after(*args, &block) + end + else + extend self + + def every(time, &block) + Thread.new { loop { sleep time; block.call } } + end + + def after(time, &block) + Thread.new { sleep time; block.call } + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/delay_and_interval.rb b/ruby/hyper-operation/lib/hyper-operation/delay_and_interval.rb deleted file mode 100644 index 3f1f11424..000000000 --- a/ruby/hyper-operation/lib/hyper-operation/delay_and_interval.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Kernel - def every(time, &block) - Thread.new { loop { sleep time; block.call }} - end - - def after(time, &block) - Thread.new { sleep time; block.call } - end -end diff --git a/ruby/hyper-operation/lib/hyper-operation/promise.rb b/ruby/hyper-operation/lib/hyper-operation/promise.rb index eb41c9363..297c8ae25 100644 --- a/ruby/hyper-operation/lib/hyper-operation/promise.rb +++ b/ruby/hyper-operation/lib/hyper-operation/promise.rb @@ -123,6 +123,7 @@ def resolve(value = nil) begin if block = @action[:success] || @action[:always] + @realized = :resolve value = block.call(value) end @@ -156,6 +157,9 @@ def reject(value = nil) begin if block = @action[:failure] || @action[:always] + # temporarily set values so always can determine if this + # was a reject or resolve + @realized = :reject value = block.call(value) end diff --git a/ruby/hyper-operation/lib/hyper-operation/railway/dispatcher.rb b/ruby/hyper-operation/lib/hyper-operation/railway/dispatcher.rb index 8e80c5da2..54b0d6809 100644 --- a/ruby/hyper-operation/lib/hyper-operation/railway/dispatcher.rb +++ b/ruby/hyper-operation/lib/hyper-operation/railway/dispatcher.rb @@ -1,7 +1,6 @@ module Hyperstack class Operation class Railway - def receivers self.class.receivers end diff --git a/ruby/hyper-operation/lib/hyper-operation/railway/run.rb b/ruby/hyper-operation/lib/hyper-operation/railway/run.rb index c724843e8..e7359cb2a 100644 --- a/ruby/hyper-operation/lib/hyper-operation/railway/run.rb +++ b/ruby/hyper-operation/lib/hyper-operation/railway/run.rb @@ -4,10 +4,13 @@ class Operation class Exit < StandardError attr_reader :state attr_reader :result - def initialize(state, result) + def initialize(state, result = nil) @state = state @result = result end + def to_s + @state + end end class Railway @@ -61,7 +64,10 @@ def step(opts) def failed(opts) @promise_chain = @promise_chain - .always { |result| apply(result, :failed, opts) } + .always do |result| + @state = :failed if @promise_chain.rejected? && @state != :abort + apply(result, :failed, opts) + end end def async(opts) diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb b/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb index de325c48b..54b185d88 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb @@ -97,7 +97,7 @@ def self.connect_to(channel_name, id = nil) %x{ var channel = #{ClientDrivers.opts[:pusher_api]}.subscribe(#{channel.gsub('::', '==')}); channel.bind('dispatch', #{ClientDrivers.opts[:dispatch]}) - channel.bind('pusher:subscription_succeeded', #{lambda {ClientDrivers.get_queued_data("connect-to-transport", channel_string)}}) + channel.bind('pusher:subscription_succeeded', #{->(*) { ClientDrivers.get_queued_data("connect-to-transport", channel_string)}}) } @pusher_dispatcher_registered = true elsif ClientDrivers.opts[:transport] == :action_cable @@ -169,7 +169,7 @@ def self.sync_dispatch(data) config_hash = { transport: Hyperstack.transport, id: id, - acting_user_id: (controller.acting_user && controller.acting_user.id), + acting_user_id: (controller.acting_user.respond_to?(:id) && controller.acting_user.id), env: ::Rails.env, client_logging: Hyperstack.client_logging, pusher_fake_js: pusher_fake_js, diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb index f3ef3b019..e4b4e7d28 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb @@ -1,176 +1,95 @@ -module Hyperstack - module AutoCreate - def table_exists? - # works with both rails 4 and 5 without deprecation warnings - if connection.respond_to?(:data_sources) - connection.data_sources.include?(table_name) - else - connection.tables.include?(table_name) - end - end - - def needs_init? - Hyperstack.transport != :none && Hyperstack.on_server? && !table_exists? - end - - def create_table(*args, &block) - connection.create_table(table_name, *args, &block) if needs_init? - end - end - - class Connection < ActiveRecord::Base - class QueuedMessage < ActiveRecord::Base - - extend AutoCreate - - self.table_name = 'hyperstack_queued_messages' - - do_not_synchronize - - serialize :data +# frozen_string_literal: true - belongs_to :hyperstack_connection, - class_name: 'Hyperstack::Connection', - foreign_key: 'connection_id' - - scope :for_session, - ->(session) { joins(:hyperstack_connection).where('session = ?', session) } - - # For simplicity we use QueuedMessage with connection_id 0 - # to store the current path which is used by consoles to - # communicate back to the server - - default_scope { where('connection_id IS NULL OR connection_id != 0') } +module Hyperstack + class Connection + class << self + attr_accessor :transport, :connection_adapter, :show_diagnostics + + def adapter + adapter_name = Hyperstack.connection[:adapter].to_s + adapter_path = "hyper-operation/transport/connection_adapter/#{adapter_name}" + + begin + require adapter_path + rescue LoadError => e + if e.path == adapter_path + raise e.class, "Could not load the '#{adapter_name}' adapter. Make sure the adapter is spelled correctly in your Hyperstack config, and the necessary gems are in your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise e.class, "Error loading the '#{adapter_name}' adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end + end - def self.root_path=(path) - unscoped.find_or_create_by(connection_id: 0).update(data: path) + adapter_name = adapter_name.camelize + "Hyperstack::ConnectionAdapter::#{adapter_name}".constantize end - def self.root_path - unscoped.find_or_create_by(connection_id: 0).data + def build_tables + adapter.build_tables end - end - - extend AutoCreate - def self.build_tables - create_table(force: :cascade) do |t| - t.string :channel - t.string :session - t.datetime :created_at - t.datetime :expires_at - t.datetime :refresh_at - end - QueuedMessage.create_table(force: :cascade) do |t| - t.text :data - t.integer :connection_id + def build_tables? + adapter.respond_to?(:build_tables) end - end - - do_not_synchronize - - self.table_name = 'hyperstack_connections' - - has_many :messages, - foreign_key: 'connection_id', - class_name: 'Hyperstack::Connection::QueuedMessage', - dependent: :destroy - scope :expired, - -> { where('expires_at IS NOT NULL AND expires_at < ?', Time.zone.now) } - scope :pending_for, - ->(channel) { where(channel: channel).where('session IS NOT NULL') } - scope :inactive, - -> { where('session IS NULL AND refresh_at < ?', Time.zone.now) } - - def self.needs_refresh? - exists?(['refresh_at IS NOT NULL AND refresh_at < ?', Time.zone.now]) - end - - def transport - self.class.transport - end - - before_create do - if session - self.expires_at = Time.now + transport.expire_new_connection_in - elsif transport.refresh_channels_every != :never - self.refresh_at = Time.now + transport.refresh_channels_every - end - end - - class << self - attr_accessor :transport - attr_accessor :show_diagnostics def active - # if table doesn't exist then we are either calling from within - # a migration or from a console before the server has ever started - # in these cases there are no channels so we return nothing - return [] unless table_exists? - if Hyperstack.on_server? - expired.delete_all - refresh_connections if needs_refresh? - end - all.pluck(:channel).uniq + adapter.active end def open(channel, session = nil, root_path = nil) puts "open(#{channel}, #{session}, #{root_path})" if show_diagnostics - self.root_path = root_path - find_or_create_by(channel: channel, session: session).tap { |c| puts " - open returning #{c}" if show_diagnostics} + + adapter.open(channel, session, root_path).tap do |c| + puts " - open returning #{c}" if show_diagnostics + end end def send_to_channel(channel, data) - pending_for(channel).each do |connection| - QueuedMessage.create(data: data, hyperstack_connection: connection) - end - transport.send_data(channel, data) if exists?(channel: channel, session: nil) + puts "send_to_channel(#{channel}, #{data})" if show_diagnostics + + adapter.send_to_channel(channel, data) end def read(session, root_path) - self.root_path = root_path - where(session: session) - .update_all(expires_at: Time.now + transport.expire_polled_connection_in) - QueuedMessage.for_session(session).destroy_all.pluck(:data) + puts "read(#{session}, #{root_path})" if show_diagnostics + + adapter.read(session, root_path) end def connect_to_transport(channel, session, root_path) puts "connect_to_transport(#{channel}, #{session}, #{root_path})" if show_diagnostics - self.root_path = root_path - if (connection = find_by(channel: channel, session: session)) - messages = connection.messages.pluck(:data) - connection.destroy - else - messages = [] - end - open(channel) - messages + + adapter.connect_to_transport(channel, session, root_path) end def disconnect(channel) - find_by(channel: channel, session: nil).destroy + adapter.disconnect(channel) end def root_path=(path) - QueuedMessage.root_path = path if path + adapter.root_path = path end def root_path - # if the QueuedMessage table doesn't exist then we are either calling from within - # a migration or from a console before the server has ever started - # in these cases there is no root path to the server - QueuedMessage.root_path if QueuedMessage.table_exists? + adapter.root_path end def refresh_connections - refresh_started_at = Time.zone.now - channels = transport.refresh_channels - next_refresh = refresh_started_at + transport.refresh_channels_every - channels.each do |channel| - connection = find_by(channel: channel, session: nil) - connection.update(refresh_at: next_refresh) if connection + adapter.refresh_connections + end + + def method_missing(method_name, *args, &block) + if adapter::Connection.respond_to?(method_name) + adapter::Connection.send(method_name, *args, &block) + else + super end - inactive.delete_all + end + + def respond_to_missing?(method_name, include_private = false) + adapter::Connection.respond_to?(method_name) end end end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb new file mode 100644 index 000000000..f503b0c24 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require_relative 'active_record/connection' +require_relative 'active_record/queued_message' + +module Hyperstack + module ConnectionAdapter + module ActiveRecord + class << self + def build_tables + Connection.create_table(force: :cascade) do |t| + t.string :channel + t.string :session + t.datetime :created_at + t.datetime :expires_at + t.datetime :refresh_at + end + + QueuedMessage.create_table(force: :cascade) do |t| + t.text :data + t.integer :connection_id + end + end + + def transport + Hyperstack::Connection.transport + end + + def active + # if table doesn't exist then we are either calling from within + # a migration or from a console before the server has ever started + # in these cases there are no channels so we return nothing + return [] unless Connection.table_exists? + + if Hyperstack.on_server? + Connection.expired.delete_all + refresh_connections if Connection.needs_refresh? + end + + Connection.all.pluck(:channel).uniq + rescue ::ActiveRecord::StatementInvalid + [] + end + + def open(channel, session = nil, root_path = nil) + self.root_path = root_path + + Connection.find_or_create_by(channel: channel, session: session) + end + + def send_to_channel(channel, data) + Connection.pending_for(channel).each do |connection| + QueuedMessage.create(data: data, hyperstack_connection: connection) + end + + transport.send_data(channel, data) if Connection.exists?(channel: channel, session: nil) + end + + def read(session, root_path) + self.root_path = root_path + + Connection.where(session: session) + .update_all(expires_at: Time.current + transport.expire_polled_connection_in) + + QueuedMessage.for_session(session).destroy_all.pluck(:data) + end + + def connect_to_transport(channel, session, root_path) + self.root_path = root_path + + if (connection = Connection.find_by(channel: channel, session: session)) + messages = connection.messages.pluck(:data) + connection.destroy + else + messages = [] + end + + open(channel) + + messages + end + + def disconnect(channel) + Connection.find_by(channel: channel, session: nil).destroy + end + + def root_path=(path) + QueuedMessage.root_path = path if path + end + + def root_path + # if the QueuedMessage table doesn't exist then we are either calling from within + # a migration or from a console before the server has ever started + # in these cases there is no root path to the server + QueuedMessage.root_path if QueuedMessage.table_exists? + end + + def refresh_connections + refresh_started_at = Time.current + channels = transport.refresh_channels + next_refresh = refresh_started_at + transport.refresh_channels_every + + channels.each do |channel| + connection = Connection.find_by(channel: channel, session: nil) + connection.update(refresh_at: next_refresh) if connection + end + + Connection.inactive.delete_all + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/auto_create.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/auto_create.rb new file mode 100644 index 000000000..604ef62f3 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/auto_create.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Hyperstack + module ConnectionAdapter + module ActiveRecord + module AutoCreate + def table_exists? + # works with both rails 4 and 5 without deprecation warnings + if connection.respond_to?(:data_sources) + connection.data_sources.include?(table_name) + else + connection.tables.include?(table_name) + end + end + + def needs_init? + Hyperstack.transport != :none && Hyperstack.on_server? && !table_exists? + end + + def create_table(*args, &block) + connection.create_table(table_name, *args, &block) if needs_init? + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb new file mode 100644 index 000000000..7c6fbd4e9 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative 'auto_create' + +module Hyperstack + module ConnectionAdapter + module ActiveRecord + class Connection < ::ActiveRecord::Base + extend AutoCreate + + self.table_name = 'hyperstack_connections' + + do_not_synchronize + + has_many :messages, + foreign_key: 'connection_id', + class_name: 'Hyperstack::ConnectionAdapter::ActiveRecord::QueuedMessage', + dependent: :destroy + + scope :expired, + -> { where('expires_at IS NOT NULL AND expires_at < ?', Time.current) } + scope :pending_for, + ->(channel) { where(channel: channel).where('session IS NOT NULL') } + scope :inactive, + -> { where('session IS NULL AND refresh_at < ?', Time.current) } + + before_create do + if session + self.expires_at = Time.current + transport.expire_new_connection_in + elsif transport.refresh_channels_every != :never + self.refresh_at = Time.current + transport.refresh_channels_every + end + end + + class << self + def needs_refresh? + exists?(['refresh_at IS NOT NULL AND refresh_at < ?', Time.current]) + end + end + + def transport + Hyperstack::Connection.transport + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb new file mode 100644 index 000000000..363dc8e11 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require_relative 'auto_create' + +module Hyperstack + module ConnectionAdapter + module ActiveRecord + class QueuedMessage < ::ActiveRecord::Base + extend AutoCreate + + self.table_name = 'hyperstack_queued_messages' + + do_not_synchronize + + serialize :data + + belongs_to :hyperstack_connection, + class_name: 'Hyperstack::ConnectionAdapter::ActiveRecord::Connection', + foreign_key: 'connection_id', + optional: true + + scope :for_session, + ->(session) { joins(:hyperstack_connection).where('session = ?', session) } + + # For simplicity we use QueuedMessage with connection_id 0 + # to store the current path which is used by consoles to + # communicate back to the server. The belongs_to connection + # therefore must be optional. + + default_scope { where('connection_id IS NULL OR connection_id != 0') } + + def self.root_path=(path) + unscoped.find_or_create_by(connection_id: 0).update(data: path) + end + + def self.root_path + unscoped.find_or_create_by(connection_id: 0).data + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb new file mode 100644 index 000000000..766e0c389 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require_relative 'redis/connection' +require_relative 'redis/queued_message' + +module Hyperstack + module ConnectionAdapter + module Redis + class << self + def transport + Hyperstack::Connection.transport + end + + def active + if Hyperstack.on_server? + Connection.expired.each(&:destroy) + refresh_connections if Connection.needs_refresh? + end + + Connection.all.map(&:channel).uniq + end + + def open(channel, session = nil, root_path = nil) + self.root_path = root_path + + Connection.find_or_create_by(channel: channel, session: session) + end + + def send_to_channel(channel, data) + Connection.pending_for(channel).each do |connection| + QueuedMessage.create(connection_id: connection.id, data: data) + end + + transport.send_data(channel, data) if Connection.exists?(channel: channel, session: nil) + end + + def read(session, root_path) + self.root_path = root_path + + Connection.where(session: session).each do |connection| + connection.update(expires_at: Time.current + transport.expire_polled_connection_in) + end + + messages = QueuedMessage.for_session(session) + data = messages.map(&:data) + messages.each(&:destroy) + data + end + + def connect_to_transport(channel, session, root_path) + self.root_path = root_path + + if (connection = Connection.find_by(channel: channel, session: session)) + messages = connection.messages.map(&:data) + connection.destroy + else + messages = [] + end + + open(channel) + + messages + end + + def disconnect(channel) + Connection.find_by(channel: channel, session: nil).destroy + end + + def root_path=(path) + QueuedMessage.root_path = path if path + end + + def root_path + QueuedMessage.root_path + rescue + nil + end + + def refresh_connections + refresh_started_at = Time.current + channels = transport.refresh_channels + next_refresh = refresh_started_at + transport.refresh_channels_every + + channels.each do |channel| + connection = Connection.find_by(channel: channel, session: nil) + connection.update(refresh_at: next_refresh) if connection + end + + Connection.inactive.each(&:destroy) + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb new file mode 100644 index 000000000..d5d3734cc --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require_relative 'redis_record' + +module Hyperstack + module ConnectionAdapter + module Redis + class Connection < RedisRecord::Base + self.table_name = 'hyperstack:connections' + self.column_names = %w[id channel session created_at expires_at refresh_at].freeze + + attr_accessor(*column_names.map(&:to_sym)) + + class << self + def transport + Hyperstack::Connection.transport + end + + def create(opts = {}) + opts.tap do |hash| + if opts[:session] + hash[:expires_at] = (Time.current + transport.expire_new_connection_in) + elsif transport.refresh_channels_every != :never + hash[:refresh_at] = (Time.current + transport.refresh_channels_every) + end + + hash[:created_at] = Time.current + end.to_a.flatten + + super(opts) + end + + def inactive + scope { |id| id if inactive?(id) } + end + + def inactive?(id) + get_dejsonized_attribute(id, :session).blank? && + get_dejsonized_attribute(id, :refresh_at).present? && + Time.zone.parse(get_dejsonized_attribute(id, :refresh_at)) < Time.current + end + + def expired + scope { |id| id if expired?(id) } + end + + def expired?(id) + get_dejsonized_attribute(id, :expires_at).present? && + Time.zone.parse(get_dejsonized_attribute(id, :expires_at)) < Time.current + end + + def pending_for(channel) + scope { |id| id if pending_for?(id, channel) } + end + + def pending_for?(id, channel) + get_dejsonized_attribute(id, :session).present? && + get_dejsonized_attribute(id, :channel) == channel + end + + def needs_refresh? + scope { |id| id if needs_refresh(id) }.any? + end + + def needs_refresh(id) + get_dejsonized_attribute(id, :refresh_at).present? && + Time.zone.parse(get_dejsonized_attribute(id, :refresh_at)) < Time.current + end + end + + def messages + QueuedMessage.where(connection_id: id) + end + + %i[created_at expires_at refresh_at].each do |attr| + define_method(attr) do + value = instance_variable_get(:"@#{attr}") + + value.is_a?(Time) ? value : Time.zone.parse(value) + end + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb new file mode 100644 index 000000000..c23c28f7e --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require_relative 'redis_record' + +module Hyperstack + module ConnectionAdapter + module Redis + class QueuedMessage < RedisRecord::Base + self.table_name = 'hyperstack:queued_messages' + self.column_names = %w[id data connection_id].freeze + + attr_accessor(*column_names.map(&:to_sym)) + + class << self + def for_session(session) + Connection.where(session: session).map(&:messages).flatten + end + + def root_path=(path) + find_or_create_by(connection_id: 0).update(data: path) + end + + def root_path + find_or_create_by(connection_id: 0).data + end + end + + def connection + Connection.find(connection_id) + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb new file mode 100644 index 000000000..c1c8440d3 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +module Hyperstack + module ConnectionAdapter + module Redis + module RedisRecord + class Base + class << self + attr_accessor :table_name, :column_names + + def client + @client ||= ::Redis.new(url: Hyperstack.connection[:redis_url]) + end + + def scope(&block) + ids = client.smembers(table_name) + + ids = ids.map(&block) if block + + ids.compact.map { |id| instantiate(id) } + end + + def all + scope + end + + def first + id = client.smembers(table_name).first + + instantiate(id) + end + + def last + id = client.smembers(table_name).last + + instantiate(id) + end + + def find(id) + return unless client.smembers(table_name).include?(id) + + instantiate(id) + end + + def find_by(opts) + found = nil + + client.smembers(table_name).each do |id| + unless opts.map { |k, v| get_dejsonized_attribute(id, k) == v }.include?(false) + found = instantiate(id) + break + end + end + + found + end + + def find_or_create_by(opts = {}) + if (existing = find_by(opts)) + existing + else + create(opts) + end + end + + def where(opts = {}) + scope do |id| + unless opts.map { |k, v| get_dejsonized_attribute(id, k) == v }.include?(false) + id + end + end + end + + def exists?(opts = {}) + !!client.smembers(table_name).detect do |id| + !opts.map { |k, v| get_dejsonized_attribute(id, k) == v }.include?(false) + end + end + + def create(opts = {}) + record = new({ id: SecureRandom.uuid }.merge(opts)) + + record.save + + record + end + + def destroy_all + all.each(&:destroy) + + true + end + + def jsonize_attributes(attrs) + attrs.map do |attr, value| + [attr, value.to_json] + end.to_h + end + + def dejsonize_attributes(attrs) + attrs.map do |attr, value| + [attr, value && JSON.parse(value)] + end.to_h + end + + protected + + def instantiate(id) + new(dejsonize_attributes(client.hgetall("#{table_name}:#{id}"))) + end + + def get_dejsonized_attribute(id, attr) + value = client.hget("#{table_name}:#{id}", attr) + JSON.parse(value) if value + end + end + + def initialize(opts = {}) + opts.each { |k, v| send(:"#{k}=", v) } + end + + def save + self.class.client.hmset("#{table_name}:#{id}", *self.class.jsonize_attributes(attributes)) + + unless self.class.client.smembers(table_name).include?(id) + self.class.client.sadd(table_name, id) + end + + true + end + + def update(opts = {}) + opts.each { |k, v| send(:"#{k}=", v) } + save + end + + def destroy + self.class.client.srem(table_name, id) + + self.class.client.hdel("#{table_name}:#{id}", attributes.keys) + + true + end + + def attributes + self.class.column_names.map do |column_name| + [column_name, instance_variable_get("@#{column_name}")] + end.to_h + end + + def table_name + self.class.table_name + end + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb b/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb index 1ba9df7b7..31de6917e 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb @@ -16,7 +16,7 @@ def self.reset_operations # config.eager_load_paths += %W(#{config.root}/app/hyperstack/models) # config.autoload_paths += %W(#{config.root}/app/hyperstack/models) # config.assets.paths << ::Rails.root.join('app', 'hyperstack').to_s - config.after_initialize { Connection.build_tables } + config.after_initialize { Connection.build_tables if Connection.build_tables? } end Object.send(:remove_const, :Application) if @fake_application_defined @fake_application_defined = false @@ -66,6 +66,14 @@ def self.reset_operations define_setting :client_logging, true define_setting :connect_session, true + define_setting(:connection, { adapter: :active_record }) do |connection| + if connection[:adapter] == :redis + require 'redis' + + connection[:redis_url] ||= 'redis://127.0.0.1:6379/0' + end + end + def self.app_id opts[:app_id] || Pusher.app_id if transport == :pusher end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack_controller.rb b/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack_controller.rb index 26f7685c5..da95eb103 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack_controller.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack_controller.rb @@ -11,6 +11,10 @@ def acceptable_content_type?(headers) end end if defined? ::WebConsole::Middleware + + # The purpose of this is to prevent massive amounts of logging + # if using simple polling. If polling is on then only actual messages + # with content will be shown, other wise the log message is dropped. module ::Rails module Rack class Logger < ActiveSupport::LogSubscriber diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb b/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb index 05d9debab..ee362d878 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb @@ -328,9 +328,9 @@ def self.broadcast(instance, policy) regulations[instance].regulations.each do |regulation| instance.instance_exec wrap_policy(policy, regulation), ®ulation end - if policy.has_unassigned_sets? - raise "#{instance.class.name} instance broadcast policy not sent to any channel" - end + return if policy.has_to_been_called? + + raise "#{instance.class.name} instance broadcast policy not sent to any channel" end end @@ -425,12 +425,17 @@ def add_unassigned_send_set(send_set) end def send_set_to(send_set, channels) + @to_has_been_called = true channels.flatten(1).each do |channel| merge_set(send_set, channel) if channel_available? channel @unassigned_send_sets.delete(send_set) end end + def has_to_been_called? + !has_unassigned_sets? || @to_has_been_called + end + def merge_set(send_set, channel) return unless channel channel = channel.name if channel.is_a?(Class) && channel.name @@ -467,7 +472,7 @@ def filter(h, attribute_set) def send_message(header, channel, attribute_set, &block) record = filter(@obj.react_serializer, attribute_set) - previous_changes = filter(@obj.previous_changes, attribute_set) + previous_changes = filter(@obj.saved_changes, attribute_set) return if record.empty? && previous_changes.empty? message = header.merge( channel: channel_to_string(channel), diff --git a/ruby/hyper-operation/lib/hyper-operation/version.rb b/ruby/hyper-operation/lib/hyper-operation/version.rb index c65efaf7b..2a0e07d81 100644 --- a/ruby/hyper-operation/lib/hyper-operation/version.rb +++ b/ruby/hyper-operation/lib/hyper-operation/version.rb @@ -1,5 +1,5 @@ module Hyperstack class Operation - VERSION = '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' end end diff --git a/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb b/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb index f4e27d908..8b775e61e 100644 --- a/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb +++ b/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb @@ -1,152 +1,165 @@ require 'spec_helper' -describe Hyperstack::Connection do - - before(:all) do - Hyperstack.configuration do |config| +%i[redis active_record].each do |adapter| + describe Hyperstack::Connection do + if adapter == :active_record + if ActiveRecord::Base.connection.respond_to? :data_sources + it 'creates the tables (rails 5.x)' do + ActiveRecord::Base.connection.data_sources.should include('hyperstack_connections') + ActiveRecord::Base.connection.data_sources.should include('hyperstack_queued_messages') + + expect(described_class.column_names) + .to match(%w[id channel session created_at expires_at refresh_at]) + end + else + it 'creates the tables (rails 4.x)' do + ActiveRecord::Base.connection.tables.should include('hyperstack_connections') + ActiveRecord::Base.connection.tables.should include('hyperstack_queued_messages') + + expect(described_class.column_names) + .to match(%w['id channel session created_at expires_at refresh_at]) + end + end end - end - - before(:each) do - Timecop.return - end - if ActiveRecord::Base.connection.respond_to? :data_sources - it 'creates the tables (rails 5.x)' do - ActiveRecord::Base.connection.data_sources.should include('hyperstack_connections') - ActiveRecord::Base.connection.data_sources.should include('hyperstack_queued_messages') - described_class.column_names.should =~ ['id', 'channel', 'session', 'created_at', 'expires_at', 'refresh_at'] - end - else - it 'creates the tables (rails 4.x)' do - ActiveRecord::Base.connection.tables.should include('hyperstack_connections') - ActiveRecord::Base.connection.tables.should include('hyperstack_queued_messages') - described_class.column_names.should =~ ['id', 'channel', 'session', 'created_at', 'expires_at', 'refresh_at'] + before(:all) do + Hyperstack.configuration do |config| + config.connection = { adapter: adapter } + end end - end - it 'creates the messages queue' do - channel = described_class.new - channel.messages << Hyperstack::Connection::QueuedMessage.new - channel.save - channel.reload - channel.messages.should eq(Hyperstack::Connection::QueuedMessage.all) - end + before(:each) do + Timecop.return + end - it 'can set the root path' do - described_class.root_path = 'foobar' - expect(described_class.root_path).to eq('foobar') - end + it 'creates the messages queue' do + channel = described_class.adapter::Connection.create + channel.messages << described_class.adapter::QueuedMessage.new - it 'adding new connection' do - described_class.open('TestChannel', 0) - expect(described_class.active).to eq(['TestChannel']) - end + channel.messages.should eq(described_class.adapter::QueuedMessage.all) + end - it 'new connections expire' do - described_class.open('TestChannel', 0) - expect(described_class.active).to eq(['TestChannel']) - Timecop.travel(Time.now+described_class.transport.expire_new_connection_in) - expect(described_class.active).to eq([]) - end + it 'can set the root path' do + described_class.root_path = 'foobar' + expect(described_class.root_path).to eq('foobar') + end - it 'can send and read data from a channel' do - described_class.open('TestChannel', 0) - described_class.open('TestChannel', 1) - described_class.open('AnotherChannel', 0) - described_class.send_to_channel('TestChannel', 'data') - expect(described_class.read(0, 'path')).to eq(['data']) - expect(described_class.read(0, 'path')).to eq([]) - expect(described_class.read(1, 'path')).to eq(['data']) - expect(described_class.read(1, 'path')).to eq([]) - expect(described_class.read(0, 'path')).to eq([]) - end + it 'adding new connection' do + described_class.open('TestChannel', 0) - it 'will update the expiration time after reading' do - described_class.open('TestChannel', 0) - described_class.send_to_channel('TestChannel', 'data') - described_class.read(0, 'path') - Timecop.travel(Time.now+described_class.transport.expire_new_connection_in) - expect(described_class.active).to eq(['TestChannel']) - end + expect(described_class.active).to eq(['TestChannel']) + end - it 'will expire a polled connection' do - described_class.open('TestChannel', 0) - described_class.send_to_channel('TestChannel', 'data') - described_class.read(0, 'path') - Timecop.travel(Time.now+described_class.transport.expire_polled_connection_in) - expect(described_class.active).to eq([]) - end + it 'new connections expire' do + described_class.open('TestChannel', 0) + expect(described_class.active).to eq(['TestChannel']) + Timecop.travel(Time.now + described_class.transport.expire_new_connection_in) + expect(described_class.active).to eq([]) + end - context 'after connecting to the transport' do - before(:each) do + it 'can send and read data from a channel' do described_class.open('TestChannel', 0) described_class.open('TestChannel', 1) + described_class.open('AnotherChannel', 0) described_class.send_to_channel('TestChannel', 'data') + expect(described_class.read(0, 'path')).to eq(['data']) + expect(described_class.read(0, 'path')).to eq([]) + expect(described_class.read(1, 'path')).to eq(['data']) + expect(described_class.read(1, 'path')).to eq([]) + expect(described_class.read(0, 'path')).to eq([]) end - it "will pass any pending data back" do - expect(described_class.connect_to_transport('TestChannel', 0, nil)).to eq(['data']) - end - - it "will have the root path set for console access" do - described_class.connect_to_transport('TestChannel', 0, "some_path") - expect(Hyperstack::Connection.root_path).to eq("some_path") - end + it 'will update the expiration time after reading' do + described_class.open('TestChannel', 0) + described_class.send_to_channel('TestChannel', 'data') + described_class.read(0, 'path') + Timecop.travel(Time.now + described_class.transport.expire_new_connection_in) - it "the channel will still be active even after initial connection time is expired" do - described_class.connect_to_transport('TestChannel', 0, nil) - Timecop.travel(Time.now+described_class.transport.expire_new_connection_in) expect(described_class.active).to eq(['TestChannel']) end - it "will only effect the session being connected" do - described_class.connect_to_transport('TestChannel', 0, nil) - expect(described_class.read(1, 'path')).to eq(['data']) - end + it 'will expire a polled connection' do + described_class.open('TestChannel', 0) + described_class.send_to_channel('TestChannel', 'data') + described_class.read(0, 'path') + Timecop.travel(Time.now + described_class.transport.expire_polled_connection_in) - it "will begin refreshing the channel list" do - allow(Hyperstack).to receive(:refresh_channels) {['AnotherChannel']} - described_class.open('AnotherChannel', 0) - described_class.connect_to_transport('TestChannel', 0, nil) - described_class.connect_to_transport('AnotherChannel', 0, nil) - expect(described_class.active).to eq(['TestChannel', 'AnotherChannel']) - Timecop.travel(Time.now+described_class.transport.refresh_channels_every) - expect(described_class.active).to eq(['AnotherChannel']) + expect(described_class.active).to eq([]) end - it "refreshing will not effect channels not connected to the transport" do - allow(Hyperstack).to receive(:refresh_channels) {['AnotherChannel']} - described_class.open('AnotherChannel', 0) - described_class.connect_to_transport('TestChannel', 0, nil) - Timecop.travel(Time.now+described_class.transport.refresh_channels_every-1) - described_class.read(1, 'path') - described_class.connect_to_transport('AnotherChannel', 0, nil) - expect(described_class.active).to eq(['TestChannel', 'AnotherChannel']) - Timecop.travel(Time.now+1) - described_class.active - expect(described_class.active).to eq(['TestChannel', 'AnotherChannel']) - end + context 'after connecting to the transport' do + before(:each) do + described_class.open('TestChannel', 0) + described_class.open('TestChannel', 1) + described_class.send_to_channel('TestChannel', 'data') + end + + it 'will pass any pending data back' do + expect(described_class.connect_to_transport('TestChannel', 0, nil)).to eq(['data']) + end + + it 'will have the root path set for console access' do + described_class.connect_to_transport('TestChannel', 0, 'some_path') + expect(Hyperstack::Connection.root_path).to eq('some_path') + end - it "refreshing will not effect channels added during the refresh" do - allow(Hyperstack).to receive(:refresh_channels) do + it 'the channel will still be active even after initial connection time is expired' do described_class.connect_to_transport('TestChannel', 0, nil) - ['AnotherChannel'] + Timecop.travel(Time.now + described_class.transport.expire_new_connection_in) + expect(described_class.active.sort).to eq(['TestChannel'].sort) end - described_class.open('AnotherChannel', 0) - Timecop.travel(Time.now+described_class.transport.refresh_channels_every) - described_class.read(0, 'path') - described_class.connect_to_transport('AnotherChannel', 0, nil) - expect(described_class.active).to eq(['TestChannel', 'AnotherChannel']) - described_class.open('TestChannel', 2) - expect(described_class.active).to eq(['TestChannel', 'AnotherChannel']) - end - it "sends messages to the transport as well as open channels" do - expect(Hyperstack).to receive(:send_data).with('TestChannel', 'data2') - described_class.connect_to_transport('TestChannel', 0, nil) - described_class.send_to_channel('TestChannel', 'data2') - expect(described_class.read(1, 'path')).to eq(['data', 'data2']) + it 'will only effect the session being connected' do + described_class.connect_to_transport('TestChannel', 0, nil) + expect(described_class.read(1, 'path')).to eq(['data']) + end + + it 'will begin refreshing the channel list' do + allow(Hyperstack).to receive(:refresh_channels) { ['AnotherChannel'] } + described_class.open('AnotherChannel', 0) + described_class.connect_to_transport('TestChannel', 0, nil) + described_class.connect_to_transport('AnotherChannel', 0, nil) + + expect(described_class.active.sort).to eq(%w[TestChannel AnotherChannel].sort) + + Timecop.travel(Time.now + described_class.transport.refresh_channels_every) + + expect(described_class.active.sort).to eq(['AnotherChannel'].sort) + end + + it 'refreshing will not effect channels not connected to the transport' do + allow(Hyperstack).to receive(:refresh_channels) { ['AnotherChannel'] } + described_class.open('AnotherChannel', 0) + described_class.connect_to_transport('TestChannel', 0, nil) + Timecop.travel(Time.now + described_class.transport.refresh_channels_every - 1) + described_class.read(1, 'path') + described_class.connect_to_transport('AnotherChannel', 0, nil) + expect(described_class.active.sort).to eq(%w[TestChannel AnotherChannel].sort) + Timecop.travel(Time.now + 1) + described_class.active + expect(described_class.active.sort).to eq(%w[TestChannel AnotherChannel].sort) + end + + it 'refreshing will not effect channels added during the refresh' do + allow(Hyperstack).to receive(:refresh_channels) do + described_class.connect_to_transport('TestChannel', 0, nil) + ['AnotherChannel'] + end + described_class.open('AnotherChannel', 0) + Timecop.travel(Time.now + described_class.transport.refresh_channels_every) + described_class.read(0, 'path') + described_class.connect_to_transport('AnotherChannel', 0, nil) + expect(described_class.active.sort).to eq(%w[TestChannel AnotherChannel].sort) + described_class.open('TestChannel', 2) + expect(described_class.active.sort).to eq(%w[TestChannel AnotherChannel].sort) + end + + it 'sends messages to the transport as well as open channels' do + expect(Hyperstack).to receive(:send_data).with('TestChannel', 'data2') + described_class.connect_to_transport('TestChannel', 0, nil) + described_class.send_to_channel('TestChannel', 'data2') + expect(described_class.read(1, 'path').sort).to eq(%w[data data2].sort) + end end end end diff --git a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb index c77da4adc..c3756788a 100644 --- a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb +++ b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb @@ -9,411 +9,449 @@ def pusher_credentials nil end -describe "Transport Tests", js: true do - - before(:each) do - stub_const "CreateTestModel", Class.new(Hyperstack::ServerOp) - stub_const "MyControllerOp", Class.new(Hyperstack::ControllerOp) - isomorphic do - class MyControllerOp < Hyperstack::ControllerOp - param :data - dispatch_to { session_channel } - end - class CreateTestModel < Hyperstack::ServerOp - param :test_attribute +%i[redis active_record].each do |adapter| + describe 'Transport Tests', js: true do + before(:all) do + Hyperstack.configuration do |config| + config.connection = { adapter: adapter } end end - on_client do - class TestComponent - include Hyperstack::Component - include Hyperstack::State::Observable - before_mount do - @items = [] - receives(CreateTestModel) { |params| mutate @items += [params.test_attribute] } - receives(MyControllerOp) { |params| mutate @message = params.data } + + before(:each) do + stub_const 'CreateTestModel', Class.new(Hyperstack::ServerOp) + stub_const 'MyControllerOp', Class.new(Hyperstack::ControllerOp) + isomorphic do + class MyControllerOp < Hyperstack::ControllerOp + param :data + dispatch_to { session_channel } end - render(DIV) do - DIV { "#{@items.count} items" } - UL { @items.each { |test_attribute| LI { test_attribute }}} - DIV { @message } + class CreateTestModel < Hyperstack::ServerOp + param :test_attribute end end - end - end - - before(:each) do - ApplicationController.acting_user = nil - # spec_helper resets the policy system after each test so we have to setup - # before each test - #stub_const 'ScopeIt', Class.new - stub_const 'ScopeIt::TestApplicationPolicy', Class.new - ScopeIt::TestApplicationPolicy.class_eval do - regulate_class_connection { !self } - always_dispatch_from(CreateTestModel) - end - size_window(:small, :portrait) - on_client do - # patch Hyperstack.connect so it doesn't execute until we say so - # this is NOT used by the polling connection FYI - module Hyperstack - class << self - alias old_connect connect - def go_ahead_and_connect - old_connect(*@connect_args) + on_client do + class TestComponent + include Hyperstack::Component + include Hyperstack::State::Observable + before_mount do + @items = [] + receives(CreateTestModel) { |params| mutate @items += [params.test_attribute] } + receives(MyControllerOp) { |params| mutate @message = params.data } end - def connect(*args) - @connect_args = args + render(DIV) do + DIV { "#{@items.count} items" } + UL { @items.each { |test_attribute| LI { test_attribute } } } + DIV { @message } end end end end - end - - after(:each) do - Timecop.return - wait_for_ajax - end - context "Pusher-Fake" do - before(:all) do - - require 'pusher' - require 'pusher-fake' - Pusher.app_id = "MY_TEST_ID" - Pusher.key = "MY_TEST_KEY" - Pusher.secret = "MY_TEST_SECRET" - require "pusher-fake/support/base" - - Hyperstack.configuration do |config| - config.connect_session = false - config.transport = :pusher - config.opts = { - app_id: Pusher.app_id, - key: Pusher.key, - secret: Pusher.secret - }.merge(PusherFake.configuration.web_options) + before(:each) do + ApplicationController.acting_user = nil + # spec_helper resets the policy system after each test so we have to setup + # before each test + # stub_const 'ScopeIt', Class.new + stub_const 'ScopeIt::TestApplicationPolicy', Class.new + ScopeIt::TestApplicationPolicy.class_eval do + regulate_class_connection { !self } + always_dispatch_from(CreateTestModel) + end + size_window(:small, :portrait) + on_client do + # patch Hyperstack.connect so it doesn't execute until we say so + # this is NOT used by the polling connection FYI + module Hyperstack + class << self + alias old_connect connect + def go_ahead_and_connect + old_connect(*@connect_args) + end + def connect(*args) + @connect_args = args + end + end + end end end - it "opens the connection" do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - end - - it "will not keep the temporary polled connection open" do - mount "TestComponent" - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq([]) - sleep 1 - end - - it "sees the connection going offline" do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - ApplicationController.acting_user = true - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.refresh_channels_every) - wait_for { Hyperstack::Connection.active }.to eq([]) - end - - it "receives change notifications" do - # one tricky thing about synchromesh is that we want to capture all - # changes to the database that might be made while the client connections - # is still being initialized. To do this we establish a server side - # queue of all messages sent between the time the page begins rendering - # until the connection is established. - - # mount our test component - mount "TestComponent" - # add a model - CreateTestModel.run(test_attribute: "I'm new here!") - # until we connect there should only be 5 items - page.should have_content("0 items") - # okay now we can go ahead and connect (this runs on the client) - evaluate_ruby "Hyperstack.go_ahead_and_connect" - # once we connect it should change to 6 - page.should have_content("1 items") - # now that we are connected the UI should keep updating - CreateTestModel.run(test_attribute: "I'm also new here!") - page.should have_content("2 items") - sleep 1 - end - - it "broadcasts to the session channel" do - Hyperstack.connect_session = true - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait_for_ajax #rescue nil - evaluate_ruby "MyControllerOp.run(data: 'hello')" - page.should have_content("hello") + after(:each) do + Timecop.return + wait_for_ajax end - end - context "Action Cable" do + context 'Pusher-Fake' do + before(:all) do + require 'pusher' + require 'pusher-fake' + Pusher.app_id = 'MY_TEST_ID' + Pusher.key = 'MY_TEST_KEY' + Pusher.secret = 'MY_TEST_SECRET' + require 'pusher-fake/support/base' + + Hyperstack.configuration do |config| + config.connect_session = false + config.transport = :pusher + config.opts = { + app_id: Pusher.app_id, + key: Pusher.key, + secret: Pusher.secret, + use_tls: false + }.merge(PusherFake.configuration.web_options) + end + end - before(:each) do - Hyperstack.configuration do |config| - config.connect_session = false - config.transport = :action_cable - config.channel_prefix = "synchromesh" + it 'opens the connection' do + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq(['ScopeIt::TestApplication']) end - end - it "opens the connection" do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - end + it 'will not keep the temporary polled connection open' do + mount 'TestComponent' + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq([]) + sleep 1 + end - it "will not keep the temporary polled connection open" do - mount "TestComponent" - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq([]) - end + it 'sees the connection going offline' do + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq(['ScopeIt::TestApplication']) + ApplicationController.acting_user = true + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.refresh_channels_every) + wait_for { Hyperstack::Connection.active }.to eq([]) + end - it "sees the connection going offline" do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - ApplicationController.acting_user = true - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait_for { Hyperstack::Connection.active }.to eq([]) - end + it 'receives change notifications' do + # one tricky thing about synchromesh is that we want to capture all + # changes to the database that might be made while the client connections + # is still being initialized. To do this we establish a server side + # queue of all messages sent between the time the page begins rendering + # until the connection is established. + + # mount our test component + mount 'TestComponent' + # add a model + CreateTestModel.run(test_attribute: "I'm new here!") + # until we connect there should only be 5 items + page.should have_content('0 items') + # okay now we can go ahead and connect (this runs on the client) + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + # once we connect it should change to 6 + page.should have_content('1 items') + # now that we are connected the UI should keep updating + CreateTestModel.run(test_attribute: "I'm also new here!") + page.should have_content('2 items') + sleep 1 + end - it "receives change notifications" do - # one tricky thing about synchromesh is that we want to capture all - # changes to the database that might be made while the client connections - # is still being initialized. To do this we establish a server side - # queue of all messages sent between the time the page begins rendering - # until the connection is established. - - # mount our test component - mount "TestComponent" - # add a model - CreateTestModel.run(test_attribute: "I'm new here!") - # until we connect there should only be 5 items - page.should have_content("0 items") - # okay now we can go ahead and connect (this runs on the client) - evaluate_ruby "Hyperstack.go_ahead_and_connect" - # once we connect it should change to 6 - page.should have_content("1 items") - # now that we are connected the UI should keep updating - CreateTestModel.run(test_attribute: "I'm also new here!") - page.should have_content("2 items") - sleep 1 + it 'broadcasts to the session channel' do + Hyperstack.connect_session = true + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + wait_for_ajax #rescue nil + evaluate_ruby "MyControllerOp.run(data: 'hello')" + page.should have_content('hello') + end end - it "broadcasts to the session channel" do - Hyperstack.connect_session = true - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait_for_ajax rescue nil - evaluate_ruby "MyControllerOp.run(data: 'hello')" - page.should have_content("hello") - end - end + context 'Action Cable' do + before(:each) do + Hyperstack.configuration do |config| + config.connect_session = false + config.transport = :action_cable + config.channel_prefix = 'synchromesh' + end + end - context "Real Pusher Account", skip: (pusher_credentials ? false : SKIP_MESSAGE) do + it 'opens the connection' do + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in - 1) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq(['ScopeIt::TestApplication']) + end - before(:each) do - require 'pusher' - Object.send(:remove_const, :PusherFake) if defined?(PusherFake) + it 'will not keep the temporary polled connection open' do + mount 'TestComponent' + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq([]) + end - Hyperstack.configuration do |config| - config.connect_session = false - config.transport = :pusher - config.opts = pusher_credentials + it 'sees the connection going offline' do + mount 'TestComponent' + puts "active connections after mounting: #{Hyperstack::Connection.active}" + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in - 1) + wait_for do + sleep 0.25 + Hyperstack::Connection.active.tap { |c| puts "active connections now = #{c}"} + end.to eq(['ScopeIt::TestApplication']) + ApplicationController.acting_user = true + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + wait_for { Hyperstack::Connection.active }.to eq([]) end - end - it "opens the connection" do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - end + it 'receives change notifications' do + # one tricky thing about synchromesh is that we want to capture all + # changes to the database that might be made while the client connections + # is still being initialized. To do this we establish a server side + # queue of all messages sent between the time the page begins rendering + # until the connection is established. + + # mount our test component + mount 'TestComponent' + # add a model + CreateTestModel.run(test_attribute: "I'm new here!") + # until we connect there should only be 5 items + page.should have_content('0 items') + # okay now we can go ahead and connect (this runs on the client) + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + # once we connect it should change to 6 + page.should have_content('1 items') + # now that we are connected the UI should keep updating + CreateTestModel.run(test_attribute: "I'm also new here!") + page.should have_content('2 items') + sleep 1 + end - it "will not keep the temporary polled connection open" do - mount "TestComponent" - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq([]) + it 'broadcasts to the session channel' do + Hyperstack.connect_session = true + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + wait_for_ajax rescue nil + evaluate_ruby "MyControllerOp.run(data: 'hello')" + page.should have_content('hello') + end end - it "sees the connection going offline", skip: 'this keeps failing intermittently, but not due to functional issues' do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - ApplicationController.acting_user = true - sleep 1 # needed so Pusher can catch up since its not controlled by timecop - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.refresh_channels_every) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq([]) - end + context 'Real Pusher Account', skip: (pusher_credentials ? false : SKIP_MESSAGE) do + before(:each) do + require 'pusher' + Object.send(:remove_const, :PusherFake) if defined?(PusherFake) - it "receives change notifications" do - # one tricky thing about synchromesh is that we want to capture all - # changes to the database that might be made while the client connections - # is still being initialized. To do this we establish a server side - # queue of all messages sent between the time the page begins rendering - # until the connection is established. - - # mount our test component - mount "TestComponent" - # add a model - CreateTestModel.run(test_attribute: "I'm new here!") - # until we connect there should only be 5 items - page.should have_content("0 items") - # okay now we can go ahead and connect (this runs on the client) - evaluate_ruby "Hyperstack.go_ahead_and_connect" - # once we connect it should change to 6 - page.should have_content("1 items") - # now that we are connected the UI should keep updating - CreateTestModel.run(test_attribute: "I'm also new here!") - page.should have_content("2 items") - sleep 1 - end + Hyperstack.configuration do |config| + config.connect_session = false + config.transport = :pusher + config.opts = pusher_credentials + end + end - it "broadcasts to the session channel" do - Hyperstack.connect_session = true - mount "TestComponent" - sleep 0.25 - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait_for_ajax rescue nil - evaluate_ruby "MyControllerOp.run(data: 'hello')" - page.should have_content("hello") - end - end + it 'opens the connection' do + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in - 1) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq(['ScopeIt::TestApplication']) + end - context "Simple Polling" do + it 'will not keep the temporary polled connection open' do + mount 'TestComponent' + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq([]) + end - before(:all) do - Hyperstack.configuration do |config| - config.connect_session = false - config.transport = :simple_poller - # slow down the polling so wait_for_ajax works - config.opts = { seconds_between_poll: 2 } + it "sees the connection going offline", skip: 'this keeps failing intermittently, but not due to functional issues' do + mount "TestComponent" + evaluate_ruby "Hyperstack.go_ahead_and_connect" + Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) + wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) + ApplicationController.acting_user = true + sleep 1 # needed so Pusher can catch up since its not controlled by timecop + mount "TestComponent" + evaluate_ruby "Hyperstack.go_ahead_and_connect" + Timecop.travel(Time.now+Hyperstack::Connection.transport.refresh_channels_every) + wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq([]) end - end - it "opens the connection" do - mount "TestComponent" - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - end + it "receives change notifications" do + # one tricky thing about synchromesh is that we want to capture all + # changes to the database that might be made while the client connections + # is still being initialized. To do this we establish a server side + # queue of all messages sent between the time the page begins rendering + # until the connection is established. + + # mount our test component + mount "TestComponent" + # add a model + CreateTestModel.run(test_attribute: "I'm new here!") + # until we connect there should only be 5 items + page.should have_content("0 items") + # okay now we can go ahead and connect (this runs on the client) + evaluate_ruby "Hyperstack.go_ahead_and_connect" + # once we connect it should change to 6 + page.should have_content("1 items") + # now that we are connected the UI should keep updating + CreateTestModel.run(test_attribute: "I'm also new here!") + page.should have_content("2 items") + sleep 1 + end - it "sees the connection going offline" do - mount "TestComponent" - wait_for_ajax - ApplicationController.acting_user = true - mount "TestComponent" - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - Timecop.travel(Time.now+Hyperstack.expire_polled_connection_in) - wait(10.seconds).for { Hyperstack::Connection.active }.to eq([]) + it "broadcasts to the session channel" do + Hyperstack.connect_session = true + mount "TestComponent" + sleep 0.25 + evaluate_ruby "Hyperstack.go_ahead_and_connect" + wait_for_ajax rescue nil + evaluate_ruby "MyControllerOp.run(data: 'hello')" + page.should have_content("hello") + end end - it "receives change notifications" do - mount "TestComponent" - CreateTestModel.run(test_attribute: "I'm new here!") - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - page.should have_content("1 items") - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - end + context "Simple Polling" do + before(:all) do + Hyperstack.configuration do |config| + config.connect_session = false + config.transport = :simple_poller + # slow down the polling so wait_for_ajax works + config.opts = { seconds_between_poll: 2 } + end + end - it "broadcasts to the session channel" do - Hyperstack.connect_session = true - mount "TestComponent" - sleep 0.25 - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait_for_ajax - evaluate_ruby "MyControllerOp.run(data: 'hello')" - page.should have_content("hello") - end - end + it "opens the connection" do + mount "TestComponent" + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + end - context 'Misc Tests' do - it 'has a anti_csrf_token' do - expect_evaluate_ruby('Hyperstack.anti_csrf_token').to be_present - end + it "sees the connection going offline" do + mount "TestComponent" + wait_for_ajax + ApplicationController.acting_user = true + mount "TestComponent" + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + Timecop.travel(Time.now+Hyperstack.expire_polled_connection_in) + wait(10.seconds).for { Hyperstack::Connection.active }.to eq([]) + end - it 'wait for an instance channel to be loaded before connecting' do - # Make a pretend mini model, and allow it to be accessed by user-123 - stub_const "UserModelPolicy", Class.new - stub_const "UserModel", Class.new - UserModel.class_eval do - def initialize(id) - @id = id - end - def ==(other) - id == other.id - end - def self.find(id) - new(id) - end - def id - @id.to_s - end + it "receives change notifications" do + mount "TestComponent" + CreateTestModel.run(test_attribute: "I'm new here!") + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + page.should have_content("1 items") + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] end - UserModelPolicy.class_eval do - regulate_instance_connections { UserModel.new(123) } + + it "broadcasts to the session channel" do + Hyperstack.connect_session = true + mount "TestComponent" + sleep 0.25 + evaluate_ruby "Hyperstack.go_ahead_and_connect" + wait_for_ajax + evaluate_ruby "MyControllerOp.run(data: 'hello')" + page.should have_content("hello") end + end - # During checkin we will run this op. The spec will make sure it runs - isomorphic do - class CheckIn < Hyperstack::ControllerOp - param :id - validate { params.id == '123' } - step { params.id } - end + context 'Misc Tests' do + it 'has a anti_csrf_token' do + expect_evaluate_ruby('Hyperstack.anti_csrf_token').to be_present end + # after(:each) do |example| + # binding.pry #if example.exception + # end - on_client do - # Stub the user model on the client - class UserModel + it 'wait for an instance channel to be loaded before connecting' do # 2 = action_cable + # Make a pretend mini model, and allow it to be accessed by user-123 + stub_const "UserModelPolicy", Class.new + stub_const "UserModel", Class.new + UserModel.class_eval do def initialize(id) @id = id end + def ==(other) + id == other.id + end + def self.find(id) + new(id) + end def id @id.to_s end end - # stub the normal Hyperstack::Model.load to call checkin - # note that load returns a promise and so does checkin! Lovely - module Hyperstack - module Model - def self.load - CheckIn.run(id: yield) + UserModelPolicy.class_eval do + regulate_instance_connections { UserModel.new(123) } + end + + # During checkin we will run this op. The spec will make sure it runs + isomorphic do + class CheckIn < Hyperstack::ControllerOp + param :id + validate { puts "validating params.id == '123': #{params.id == '123'}"; params.id == '123' } + step { params.id } # return the id to the client simulating checkin of valid user + end + end + + on_client do + # Stub the user model on the client + class UserModel + def initialize(id) + puts "UserModel.new(#{id})" + @id = id + end + def id + puts "self.id = #{@id}" + @id.to_s + end + end + # stub the normal Hyperstack::Model.load to call checkin + # note that load returns a promise and so does checkin! Lovely + module Hyperstack + module Model + def self.load + puts "loading id from server - simulating HyperModel load of id for an object" + CheckIn.run(id: yield) + end end end end - end - # use action cable - Hyperstack.configuration do |config| - config.connect_session = false - config.transport = :action_cable - config.channel_prefix = "synchromesh" - end + # use action cable + Hyperstack.configuration do |config| + config.connect_session = false + config.transport = :action_cable + config.channel_prefix = 'synchromesh' + end - expect(CheckIn).to receive(:run).and_call_original - evaluate_ruby "Hyperstack.connect(UserModel.new(123))" - # the suite previously redefined connect so we have to call this to initiate - # the connection - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait(10.seconds).for { Hyperstack::Connection.active }.to eq(['UserModel-123']) + expect(CheckIn).to receive(:run).and_call_original + evaluate_ruby { puts "setting up connecting"; Hyperstack.connect(UserModel.new(123)) } + # the suite previously redefined connect so we have to call this to initiate + # the connection + evaluate_ruby { puts "connecting"; Hyperstack.go_ahead_and_connect } + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active.tap { |c| puts "active = [#{c}]"} + end.to eq(['UserModel-123']) + end end - end end diff --git a/ruby/hyper-operation/spec/hyper-operation/basics_spec.rb b/ruby/hyper-operation/spec/hyper-operation/basics_spec.rb index b57bb68e0..7df0ad29e 100644 --- a/ruby/hyper-operation/spec/hyper-operation/basics_spec.rb +++ b/ruby/hyper-operation/spec/hyper-operation/basics_spec.rb @@ -189,6 +189,7 @@ def count it "will use the promise returned by execute", js: true do isomorphic do class MyOperation < Hyperstack::Operation + include Hyperstack::AsyncSleep param :wait, type: Float, min: 0 param :result step do diff --git a/ruby/hyper-operation/spec/hyper-operation/execution_spec.rb b/ruby/hyper-operation/spec/hyper-operation/execution_spec.rb index 164aeeed8..935ce9f25 100644 --- a/ruby/hyper-operation/spec/hyper-operation/execution_spec.rb +++ b/ruby/hyper-operation/spec/hyper-operation/execution_spec.rb @@ -28,6 +28,41 @@ def self.promise expect(MyOperation.run(i: 1).tap { MyOperation.promise.resolve(2) }.value).to eq 4 end + it "will chain rejected promises" do + MyOperation.class_eval do + def self.promise + @promise ||= Promise.new + end + param :i + step { self.class.promise } + step { @took_a_bad_step = true } + failed { |e| e unless @took_a_bad_step } + end + expect( + MyOperation.run(i: 1) + .always { |failure| failure } + .tap { MyOperation.promise.reject("promise rejected") } + .value + ).to eq "promise rejected" + end + + it "will chain promises that raise exceptions" do + MyOperation.class_eval do + def self.promise + @promise ||= Promise.new.then { raise "exception raised" }.tap { |p| p.resolve } + end + param :i + step { self.class.promise } + step { @took_a_bad_step = true } + failed { |e| e unless @took_a_bad_step } + end + expect( + MyOperation.run(i: 1) + .always { |failure| failure.message } + .value + ).to eq "exception raised" + end + it "will interrupt the promise chain with async" do MyOperation.class_eval do def self.promise @@ -217,7 +252,8 @@ def say_instance_hello() before(:step) do on_client do def get_round_tuit(value) - Promise.new.tap { |p| after(0.1) { p.resolve(value) } } + Promise.new.tap { |p| after(0.2) { value == :reject ? p.reject("promise rejected") : p.resolve(value) } } + .then { |v| value == :exception ? raise("exception raised") : v } end module DontCallMe def called? @@ -260,6 +296,40 @@ def say_hello end.to eq 4 end + it "will chain rejected promises" do + expect do + Class.new(Hyperstack::Operation) do + param :i + step { get_round_tuit(:reject) } + step { @took_a_bad_step = true } + failed { |e| e unless @took_a_bad_step } + end.run(i: 1).always { |failure| failure } + end.on_client_to eq "promise rejected" + end + + it "will chain promises that raise exceptions" do + expect do + Class.new(Hyperstack::Operation) do + param :i + step { get_round_tuit(:exception) } + step { @took_a_bad_step = true } + failed { |e| e unless @took_a_bad_step } + end.run(i: 1).always { |failure| failure } + end.on_client_to eq "exception raised" + end + + it "will interrupt the promise chain with async" do + expect_promise do + Class.new(Hyperstack::Operation) do + param :i + step { get_round_tuit(2) } + step { |n| params.i + n } + step { |r| r + params.i } + async { 'hi' } + end.run(i: 1) + end.to eq 'hi' + end + it "will interrupt the promise chain with async" do expect_promise do Class.new(Hyperstack::Operation) do @@ -381,8 +451,8 @@ class SayHelloOp < Hyperstack::Operation TestOperation.class_eval do param :xxx extend HelloCounter - def say_hello(test = false) - self.class.say_hello(test) + def say_hello + self.class.say_hello end step { say_hello } step :say_hello diff --git a/ruby/hyper-operation/spec/hyper-operation/reset_context_spec.rb b/ruby/hyper-operation/spec/hyper-operation/reset_context_spec.rb index add0f085a..3b7094c60 100644 --- a/ruby/hyper-operation/spec/hyper-operation/reset_context_spec.rb +++ b/ruby/hyper-operation/spec/hyper-operation/reset_context_spec.rb @@ -52,12 +52,12 @@ class Store class << self attr_reader :boot_calls attr_reader :another_receiver_calls - def booted + def booted(*) puts " booted called" @boot_calls ||= 0 @boot_calls += 1 end - def another_receiver + def another_receiver(*) puts " receiver called" @another_receiver_calls ||= 0 @another_receiver_calls += 1 @@ -72,14 +72,14 @@ class Store receives Hyperstack::Application::Boot, :another_receiver end end - evaluate_ruby("puts '>>booting'") # do a separate evaluation to get initial boot completed + evaluate_ruby { puts '>>booting' } # do a separate evaluation to get initial boot completed evaluate_ruby do puts '>>resetting again' Hyperstack::Context.reset!(nil) puts '>>booting again' Hyperstack::Application::Boot.run end - expect_evaluate_ruby('Store.boot_calls').to eq(2) - expect_evaluate_ruby('Store.another_receiver_calls').to eq(1) + expect { Store.boot_calls }.on_client_to eq(2) + expect { Store.another_receiver_calls }.on_client_to eq(1) end end diff --git a/ruby/hyper-operation/spec/hyper-operation/server_op_spec.rb b/ruby/hyper-operation/spec/hyper-operation/server_op_spec.rb index a8e1d24cc..9ba4ed4bd 100644 --- a/ruby/hyper-operation/spec/hyper-operation/server_op_spec.rb +++ b/ruby/hyper-operation/spec/hyper-operation/server_op_spec.rb @@ -89,11 +89,11 @@ def fact(x) class ServerFacts < Hyperstack::ServerOp step { abort! } end - expect_promise do + expect do ServerFacts.run(n: 5).fail do |exception| Promise.new.resolve(exception.inspect) end - end.to eq('#') + end.on_client_to eq("#") expect(response_spy).to have_received(:status=).with(500) end @@ -147,7 +147,7 @@ class ServerFacts < Hyperstack::ServerOp Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end @@ -298,7 +298,7 @@ class Operation < Hyperstack::ServerOp Hyperstack.configuration do |config| config.transport = :pusher config.channel_prefix = "synchromesh" - config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options) end end diff --git a/ruby/hyper-operation/spec/mutations/mutations_client_integration_spec.rb b/ruby/hyper-operation/spec/mutations/mutations_client_integration_spec.rb index ce888509d..a296f2caa 100644 --- a/ruby/hyper-operation/spec/mutations/mutations_client_integration_spec.rb +++ b/ruby/hyper-operation/spec/mutations/mutations_client_integration_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' + describe 'mutation client integration', js: true do it "can load and run the mutation gem on the client" do isomorphic do diff --git a/ruby/hyper-operation/spec/spec_helper.rb b/ruby/hyper-operation/spec/spec_helper.rb index 1706d2cc4..c6d3924da 100644 --- a/ruby/hyper-operation/spec/spec_helper.rb +++ b/ruby/hyper-operation/spec/spec_helper.rb @@ -21,6 +21,14 @@ RSpec.configure do |config| + if config.formatters.empty? + module Hyperstack + def self.log_import(s) + # turn off import logging unless in verbose mode + end + end + end + config.after :each do Rails.cache.clear end @@ -86,6 +94,12 @@ def self.on_server? # end end + config.before(:each) do + if Hyperstack.connection[:adapter] == :redis + Hyperstack::Connection.adapter::RedisRecord::Base.client.flushdb + end + end + config.before(:each, :js => true) do # -sfc george DatabaseCleaner.strategy = :truncation end @@ -111,16 +125,19 @@ def self.on_server? # class JavaScriptError < StandardError; end # config.after(:each, js: true) do |spec| - # errors = page.driver.browser.manage.logs.get(:browser) + # errors = page.driver.browser.logs.get(:browser) # .select { |e| e.level == "SEVERE" && e.message.present? } # #.map { |m| m.message.gsub(/\\n/, "\n") }.to_a # #.reject { |e| e =~ /Unexpected response code: 200/ } # raise JavaScriptError, errors.join("\n\n") if errors.present? # end + + # Use legacy hyper-spec on_client behavior + HyperSpec::Helpers.alias_method :on_client, :before_mount end module HyperSpec - module ComponentTestHelpers + module Helpers alias old_expect_promise expect_promise def expect_promise(str_or_promise = nil, &block) if str_or_promise.is_a? Promise diff --git a/ruby/hyper-operation/spec/test_app/app/assets/config/manifest.js b/ruby/hyper-operation/spec/test_app/app/assets/config/manifest.js new file mode 100644 index 000000000..9c361e667 --- /dev/null +++ b/ruby/hyper-operation/spec/test_app/app/assets/config/manifest.js @@ -0,0 +1,2 @@ + //= link_directory ../javascripts .js + //= link_directory ../stylesheets .css \ No newline at end of file diff --git a/ruby/hyper-operation/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-operation/spec/test_app/app/assets/javascripts/application.js index 93a6e91c8..843c429fd 100644 --- a/ruby/hyper-operation/spec/test_app/app/assets/javascripts/application.js +++ b/ruby/hyper-operation/spec/test_app/app/assets/javascripts/application.js @@ -1,5 +1,2 @@ -//= require 'react' -//= require 'react_ujs' -//= require 'components' //= require action_cable -Opal.load('components'); +//= require hyperstack-loader diff --git a/ruby/hyper-operation/spec/test_app/app/views/components/say_hello.rb b/ruby/hyper-operation/spec/test_app/app/hyperstack/components/say_hello.rb similarity index 100% rename from ruby/hyper-operation/spec/test_app/app/views/components/say_hello.rb rename to ruby/hyper-operation/spec/test_app/app/hyperstack/components/say_hello.rb diff --git a/ruby/hyper-operation/spec/test_app/app/views/components.rb b/ruby/hyper-operation/spec/test_app/app/views/components.rb deleted file mode 100644 index e80cff6f3..000000000 --- a/ruby/hyper-operation/spec/test_app/app/views/components.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'opal' -#require 'react/react-source-browser' -require 'hyper-component' -if Hyperstack::Component::IsomorphicHelpers.on_opal_client? - require 'browser' - require 'browser/delay' - require 'browser/interval' - require 'hyperstack/pusher' -end -require 'hyper-state' -require 'hyper-operation' -require_tree './components' diff --git a/ruby/hyper-operation/spec/test_app/config/application.rb b/ruby/hyper-operation/spec/test_app/config/application.rb index 266fdf6c0..b763a19e4 100644 --- a/ruby/hyper-operation/spec/test_app/config/application.rb +++ b/ruby/hyper-operation/spec/test_app/config/application.rb @@ -17,12 +17,13 @@ class Application < Rails::Application config.assets.paths << ::Rails.root.join('app', 'models').to_s config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check = false + config.opal.arity_check_enabled = true config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true config.opal.spec_location = 'spec-opal' config.hyperstack.auto_config = false + config.active_record.yaml_column_permitted_classes = [Symbol, ActiveSupport::HashWithIndifferentAccess] config.assets.cache_store = :null_store # Settings in config/environments/* take precedence over those specified here. diff --git a/ruby/hyper-operation/spec/test_app/config/initializers/hyperstack.rb b/ruby/hyper-operation/spec/test_app/config/initializers/hyperstack.rb new file mode 100644 index 000000000..1d1a370cd --- /dev/null +++ b/ruby/hyper-operation/spec/test_app/config/initializers/hyperstack.rb @@ -0,0 +1,4 @@ +Hyperstack.import 'hyperstack/pusher', client_only: true +Hyperstack.cancel_import 'hyperstack/autoloader' +Hyperstack.cancel_import 'hyperstack/autoloader_starter' +Hyperstack.cancel_import 'config/initializers/inflections.rb' diff --git a/ruby/hyper-router/hyper-router.gemspec b/ruby/hyper-router/hyper-router.gemspec index dfbd7180a..c6d186d38 100644 --- a/ruby/hyper-router/hyper-router.gemspec +++ b/ruby/hyper-router/hyper-router.gemspec @@ -7,7 +7,8 @@ Gem::Specification.new do |spec| spec.version = HyperRouter::VERSION spec.authors = ['Adam George', 'Jan Biedermann'] spec.email = ['adamgeorge.31@gmail.com', 'jan@kursator.com'] - spec.homepage = 'http://hyperstack.org' + spec.homepage = 'http://ruby-hyperstack.org' + spec.metadata = { 'documentation_uri' => 'https://docs.hyperstack.org/' } spec.license = 'MIT' spec.summary = 'hyper-router for Opal, part of the hyperstack framework' @@ -16,19 +17,19 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-component', HyperRouter::VERSION spec.add_dependency 'hyper-state', HyperRouter::VERSION - spec.add_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] - spec.add_development_dependency 'capybara' + + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' - spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'hyper-spec', HyperRouter::VERSION spec.add_development_dependency 'hyper-store', HyperRouter::VERSION spec.add_development_dependency 'listen' - spec.add_development_dependency 'mini_racer', '~> 0.2.6' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' - spec.add_development_dependency 'parser' - spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'mini_racer', '< 0.4.0' # something is busted with 0.4.0 and its libv8-node dependency + spec.add_development_dependency 'opal-rails' + spec.add_development_dependency 'opal-jquery' + spec.add_development_dependency 'pry-rescue' + spec.add_development_dependency 'pry-stack_explorer' + spec.add_development_dependency 'puma', '<= 5.4.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' @@ -36,13 +37,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec-mocks' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' - spec.add_development_dependency 'selenium-webdriver' spec.add_development_dependency 'shoulda' spec.add_development_dependency 'shoulda-matchers' - spec.add_development_dependency 'sinatra' - spec.add_development_dependency 'spring-commands-rspec' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' - spec.add_development_dependency 'unparser' - spec.add_development_dependency 'webdrivers' end diff --git a/ruby/hyper-router/lib/hyperstack/internal/router/helpers.rb b/ruby/hyper-router/lib/hyperstack/internal/router/helpers.rb index b8874955c..c2c1b71d3 100644 --- a/ruby/hyper-router/lib/hyperstack/internal/router/helpers.rb +++ b/ruby/hyper-router/lib/hyperstack/internal/router/helpers.rb @@ -31,7 +31,7 @@ def Redirect(to, opts = {}) React::Router::Redirect(opts) end - def format_params(e) + def format_params(e, *) { match: Hyperstack::Router::Match.new(`#{e}.match`), location: Hyperstack::Router::Location.new(`#{e}.location`), @@ -47,16 +47,16 @@ def Route(to, opts = {}, &block) if opts[:mounts] component = opts.delete(:mounts) - opts[:component] = lambda do |e| - route_params = format_params(e) + opts[:component] = lambda do |*e| + route_params = format_params(*e) Hyperstack::Component::ReactAPI.create_element(component, route_params).to_n end end if block - opts[:render] = lambda do |e| - route_params = format_params(e) + opts[:render] = lambda do |*e| + route_params = format_params(*e) yield(*route_params.values).to_n end diff --git a/ruby/hyper-router/lib/hyperstack/router/history.rb b/ruby/hyper-router/lib/hyperstack/router/history.rb index 38bec876d..30644cdee 100644 --- a/ruby/hyper-router/lib/hyperstack/router/history.rb +++ b/ruby/hyper-router/lib/hyperstack/router/history.rb @@ -1,7 +1,7 @@ module Hyperstack module Router class History - include Native + include Native::Wrapper def initialize(native) @native = native diff --git a/ruby/hyper-router/lib/hyperstack/router/location.rb b/ruby/hyper-router/lib/hyperstack/router/location.rb index 814894b0b..def33c3cf 100644 --- a/ruby/hyper-router/lib/hyperstack/router/location.rb +++ b/ruby/hyper-router/lib/hyperstack/router/location.rb @@ -1,7 +1,7 @@ module Hyperstack module Router class Location - include Native + include Native::Wrapper def initialize(native) @native = native diff --git a/ruby/hyper-router/lib/hyperstack/router/match.rb b/ruby/hyper-router/lib/hyperstack/router/match.rb index bc29a3016..8e16ced2d 100644 --- a/ruby/hyper-router/lib/hyperstack/router/match.rb +++ b/ruby/hyper-router/lib/hyperstack/router/match.rb @@ -1,7 +1,7 @@ module Hyperstack module Router class Match - include Native + include Native::Wrapper def initialize(native) @native = native diff --git a/ruby/hyper-router/lib/hyperstack/router/version.rb b/ruby/hyper-router/lib/hyperstack/router/version.rb index 70000bb0c..b386e5e32 100644 --- a/ruby/hyper-router/lib/hyperstack/router/version.rb +++ b/ruby/hyper-router/lib/hyperstack/router/version.rb @@ -1,3 +1,3 @@ module HyperRouter - VERSION = '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' end diff --git a/ruby/hyper-router/lib/react/router/history.rb b/ruby/hyper-router/lib/react/router/history.rb index 0808421f9..841c98304 100644 --- a/ruby/hyper-router/lib/react/router/history.rb +++ b/ruby/hyper-router/lib/react/router/history.rb @@ -1,7 +1,7 @@ module React class Router class History - include Native + include Native::Wrapper def self.current new(`History`) diff --git a/ruby/hyper-router/spec/hyper-router/basic_dsl_spec.rb b/ruby/hyper-router/spec/hyper-router/basic_dsl_spec.rb index b40a81785..b3635b056 100644 --- a/ruby/hyper-router/spec/hyper-router/basic_dsl_spec.rb +++ b/ruby/hyper-router/spec/hyper-router/basic_dsl_spec.rb @@ -20,7 +20,7 @@ end [:server_only, :client_only].each do |render_on| - it "a routers render method can return a string (#{render_on})" do + it "a routers render method can return a string (#{render_on})", prerendering_on: render_on == :server_only do client_option render_on: render_on mount 'SimpleStringRouter' expect(page).to have_content('a simple string') diff --git a/ruby/hyper-router/spec/spec_helper.rb b/ruby/hyper-router/spec/spec_helper.rb index 6fbe79626..b600a261c 100644 --- a/ruby/hyper-router/spec/spec_helper.rb +++ b/ruby/hyper-router/spec/spec_helper.rb @@ -16,6 +16,17 @@ RSpec.configure do |config| + config.before :suite do + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end + + config.around(:each, :prerendering_on) do |example| + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) + end + config.after :each do Rails.cache.clear end diff --git a/ruby/hyper-router/spec/test_app/app/assets/config/manifest.js b/ruby/hyper-router/spec/test_app/app/assets/config/manifest.js new file mode 100644 index 000000000..92bfa3059 --- /dev/null +++ b/ruby/hyper-router/spec/test_app/app/assets/config/manifest.js @@ -0,0 +1,3 @@ + //= link_tree ../images + //= link_directory ../javascripts .js + //= link_directory ../stylesheets .css \ No newline at end of file diff --git a/ruby/hyper-router/spec/test_app/config/application.rb b/ruby/hyper-router/spec/test_app/config/application.rb index 179355aa7..a3ff759e6 100644 --- a/ruby/hyper-router/spec/test_app/config/application.rb +++ b/ruby/hyper-router/spec/test_app/config/application.rb @@ -14,6 +14,7 @@ module TestApp class Application < Rails::Application + config.opal.arity_check_enabled = true # config.opal.method_missing = true # config.opal.optimized_operators = true # config.opal.arity_check = false diff --git a/ruby/hyper-router/spec/test_app/config/initializers/hyperstack.rb b/ruby/hyper-router/spec/test_app/config/initializers/hyperstack.rb deleted file mode 100644 index a70c483de..000000000 --- a/ruby/hyper-router/spec/test_app/config/initializers/hyperstack.rb +++ /dev/null @@ -1,3 +0,0 @@ -Hyperstack.configuration do |config| - config.prerendering = :on -end diff --git a/ruby/hyper-spec/Gemfile b/ruby/hyper-spec/Gemfile index c0e97c691..12fbd76fc 100644 --- a/ruby/hyper-spec/Gemfile +++ b/ruby/hyper-spec/Gemfile @@ -4,4 +4,5 @@ gem 'hyper-store', path: '../hyper-store' gem 'hyper-state', path: '../hyper-state' gem 'hyperstack-config', path: '../hyperstack-config' #gem 'unparser', path: '../../../unparser' +gem 'opal', '1.0.5' gemspec diff --git a/ruby/hyper-spec/bin/console b/ruby/hyper-spec/bin/console old mode 100755 new mode 100644 diff --git a/ruby/hyper-spec/bin/setup b/ruby/hyper-spec/bin/setup old mode 100755 new mode 100644 diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index 22276b5fa..2e11da315 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -6,54 +6,51 @@ require 'hyper-spec/version' Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.name = 'hyper-spec' spec.version = HyperSpec::VERSION - spec.authors = ['Mitch VanDuyn', 'Adam Creekroad', 'Jan Biedermann'] + spec.authors = ['Mitch VanDuyn', 'AdamCreekroad', 'Jan Biedermann'] spec.email = ['mitch@catprint.com', 'jan@kursator.com'] - spec.summary = 'Drive your Hyperloop client and server specs from RSpec and Capybara' - spec.description = 'A Hyperloop application consists of isomorphic React Components, '\ + spec.summary = 'Drive your Opal and Hyperstack client and server specs from RSpec and Capybara' + spec.description = 'A Hyperstack application consists of isomorphic React Components, '\ 'Active Record Models, Stores, Operations and Policiespec. '\ 'Test them all from Rspec, regardless if the code runs on the client or server.' - spec.homepage = 'http://ruby-hyperloop.org' + spec.homepage = 'http://ruby-hyperstack.org' + spec.metadata = { 'documentation_uri' => 'https://docs.hyperstack.org/' } spec.license = 'MIT' - # spec.metadata = { - # "homepage_uri" => 'http://ruby-hyperloop.org', - # "source_code_uri" => 'https://github.com/ruby-hyperloop/hyper-component' - # } - spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(gemfiles|spec)/}) } spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] + spec.add_dependency 'actionview' spec.add_dependency 'capybara' spec.add_dependency 'chromedriver-helper', '1.2.0' - spec.add_dependency 'libv8', '~> 7.3.492.27.1' + spec.add_dependency 'filecache' spec.add_dependency 'method_source' - spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' - spec.add_dependency 'parser', '>= 2.3.3.1' - spec.add_dependency 'pry' - spec.add_dependency 'rspec-rails' + spec.add_dependency 'opal', ENV['OPAL_VERSION'] || '>= 0.11.0', '< 2.0' + spec.add_dependency 'parser' + spec.add_dependency 'rspec' spec.add_dependency 'selenium-webdriver' spec.add_dependency 'timecop', '~> 0.8.1' spec.add_dependency 'uglifier' - spec.add_dependency 'unparser', '>= 0.4.2' # 0.4 is incompatible + spec.add_dependency 'unparser', '>= 0.4.2' spec.add_dependency 'webdrivers' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'hyper-component', HyperSpec::VERSION - spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' + spec.add_development_dependency 'mini_racer', '< 0.4.0' # something is busted with 0.4.0 and its libv8-node dependency + spec.add_development_dependency 'opal-rails', '>= 0.9.4' spec.add_development_dependency 'pry-rescue' - spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'pry-stack_explorer' + spec.add_development_dependency 'puma', '<= 5.4.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.3.0', '< 2.5.0' + spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' spec.add_development_dependency 'rspec-its' spec.add_development_dependency 'rspec-mocks' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' - spec.add_development_dependency 'rubocop', '~> 0.51.0' + spec.add_development_dependency 'rubocop' #, '~> 0.51.0' spec.add_development_dependency 'shoulda' spec.add_development_dependency 'shoulda-matchers' spec.add_development_dependency 'spring-commands-rspec' diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index e6e9664cb..f1ab52569 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -1,14 +1,148 @@ -require 'capybara/rspec' +# hyper-spec +require 'action_view' require 'opal' +require 'unparser' +require 'method_source' +require 'filecache' +require 'webdrivers' + + +require 'capybara/rspec' +require 'hyper-spec/internal/client_execution' +require 'hyper-spec/internal/component_mount' +require 'hyper-spec/internal/controller' +require 'hyper-spec/internal/copy_locals' +require 'hyper-spec/internal/patches' +require 'hyper-spec/internal/rails_controller_helpers' +require 'hyper-spec/internal/time_cop.rb' +require 'hyper-spec/internal/window_sizing' + +require 'hyper-spec/controller_helpers' + +require 'hyper-spec/wait_for_ajax' + +require 'hyper-spec/helpers' +require 'hyper-spec/expectations' + +require 'parser/current' +if defined?(Selenium::WebDriver::Firefox) + require 'selenium/web_driver/firefox/profile' +end require 'selenium-webdriver' -require 'hyper-spec/component_test_helpers' require 'hyper-spec/version' -require 'hyper-spec/wait_for_ajax' -require 'selenium/web_driver/firefox/profile' + + +begin + require 'pry' +rescue LoadError + nil +end + +# opt-in to most recent AST format: +Parser::Builders::Default.emit_lambda = true +Parser::Builders::Default.emit_procarg0 = true +(Parser::Builders::Default.emit_encoding = true) rescue nil +(Parser::Builders::Default.emit_index = true) rescue nil +(Parser::Builders::Default.emit_arg_inside_procarg0 = true) rescue nil +(Parser::Builders::Default.emit_forward_arg = true) rescue nil +(Parser::Builders::Default.emit_kwargs = true) rescue nil +(Parser::Builders::Default.emit_match_pattern = true) rescue nil + +# not available in parser 2.3 +if Parser::Builders::Default.respond_to? :emit_arg_inside_procarg0 + Parser::Builders::Default.emit_arg_inside_procarg0 = true +end + +module HyperSpec + if defined? Pry + # add a before eval hook to pry so we can capture the source + class << self + attr_accessor :current_pry_code_block + Pry.hooks.add_hook(:before_eval, 'hyper_spec_code_capture') do |code| + HyperSpec.current_pry_code_block = code + end + end + end + + def self.reset_between_examples + @reset_between_examples ||= [] + end + + def self.reset_between_examples? + RSpec.configuration.reset_between_examples + end + + def self.reset_sessions! + Capybara.old_reset_sessions! + end +end + +# TODO: figure out why we need this patch - its because we are on an old version +# of Selenium Webdriver, but why? +require 'selenium-webdriver' + +module Selenium + module WebDriver + module Chrome + module Bridge + if const_defined?(:COMMANDS) + COMMANDS = remove_const(:COMMANDS).dup + COMMANDS[:get_log] = [:post, 'session/:session_id/log'] + COMMANDS.freeze + end + + def log(type) + data = execute :get_log, {}, type: type.to_s + + Array(data).map do |l| + begin + LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message') + rescue KeyError + next + end + end + end + end + end + end +end + +module Capybara + class << self + alias old_reset_sessions! reset_sessions! + def reset_sessions! + old_reset_sessions! if HyperSpec.reset_between_examples? + end + end +end + +RSpec.configure do |config| + config.add_setting :reset_between_examples, default: true + config.before(:all, no_reset: true) do + HyperSpec.reset_between_examples << RSpec.configuration.reset_between_examples + RSpec.configuration.reset_between_examples = false + end + config.before(:all, no_reset: false) do + HyperSpec.reset_between_examples << RSpec.configuration.reset_between_examples + RSpec.configuration.reset_between_examples = true + end + config.after(:all) do + HyperSpec.reset_sessions! unless HyperSpec.reset_between_examples? + # If rspecs step is used first in a file, it will NOT call config.before(:all) causing the + # reset_between_examples stack to be mismatched, so we check, if its already empty we + # just leave. + next if HyperSpec.reset_between_examples.empty? + + RSpec.configuration.reset_between_examples = HyperSpec.reset_between_examples.pop + end + config.before(:each) do |example| + insure_page_loaded(true) if example.metadata[:js] && !HyperSpec.reset_between_examples? + end +end RSpec.configure do |config| - config.include HyperSpec::ComponentTestHelpers + config.include HyperSpec::Helpers config.include HyperSpec::WaitForAjax config.include Capybara::DSL @@ -17,17 +151,21 @@ config.add_setting :debugger_width, default: nil config.before(:each) do - Hyperstack.class_eval do - def self.on_server? - true + if defined?(Hyperstack) + Hyperstack.class_eval do + def self.on_server? + true + end end - end if defined?(Hyperstack) + end # for compatibility with HyperMesh - HyperMesh.class_eval do - def self.on_server? - true + if defined?(HyperMesh) + HyperMesh.class_eval do + def self.on_server? + true + end end - end if defined?(HyperMesh) + end end config.before(:each, js: true) do @@ -43,23 +181,60 @@ def self.on_server? PusherFake::Channel.reset if defined? PusherFake end end - end # Capybara config -RSpec.configure do |_config| +RSpec.configure do |config| + config.before(:each) do |example| + HyperSpec::Internal::Controller.current_example = example + HyperSpec::Internal::Controller.description_displayed = false + end + + config.add_setting :wait_for_initialization_time + config.wait_for_initialization_time = 3 + Capybara.default_max_wait_time = 10 + Capybara.register_driver :chrome_undocked do |app| + opts = Selenium::WebDriver::Chrome::Options.new(args: %w[auto-open-devtools-for-tabs]) + opts.add_preference( + 'devtools', + 'preferences' => { + 'currentDockState' => '"undocked"', # Or '"bottom"', '"right"', etc. + 'panel-selectedTab' => '"console"' + } + ) + caps = Selenium::WebDriver::Remote::Capabilities.chrome + caps["goog:loggingPrefs"] = { browser: 'ALL' } + + Capybara::Selenium::Driver.new(app, browser: :chrome, options: opts, desired_capabilities: caps) + end + + Capybara.register_driver :chrome_docked do |app| + opts = Selenium::WebDriver::Chrome::Options.new(args: %w[auto-open-devtools-for-tabs]) + opts.add_preference( + 'devtools', + 'preferences' => { + 'currentDockState' => '"right"', # Or '"bottom"', '"undocked"', etc. + 'panel-selectedTab' => '"console"' + } + ) + caps = Selenium::WebDriver::Remote::Capabilities.chrome + caps["goog:loggingPrefs"] = { browser: 'ALL' } + + Capybara::Selenium::Driver.new(app, browser: :chrome, options: opts, desired_capabilities: caps) + end + Capybara.register_driver :chrome do |app| - options = {} - options.merge!( - args: %w[auto-open-devtools-for-tabs], - prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } - ) unless ENV['NO_DEBUGGER'] - # this does not seem to work properly. Don't document this feature yet. - options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] - capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options) - Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities) + caps = Selenium::WebDriver::Remote::Capabilities.chrome + + caps["goog:loggingPrefs"] = { browser: 'ALL' } # if ENV['LOG_JS'] + + Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: caps) + end + + Capybara.register_driver :firefox do |app| + Capybara::Selenium::Driver.new(app, browser: :firefox) end Capybara.register_driver :chrome_headless_docker_travis do |app| @@ -67,18 +242,15 @@ def self.on_server? options.add_argument('--headless') options.add_argument('--no-sandbox') options.add_argument('--disable-dev-shm-usage') - Capybara::Selenium::Driver.new(app, browser: :chrome, :driver_path => "/usr/lib/chromium-browser/chromedriver", options: options) - end - - Capybara.register_driver :firefox do |app| - Capybara::Selenium::Driver.new(app, browser: :firefox) + Selenium::WebDriver::Chrome::Service.driver_path = '/usr/lib/chromium-browser/chromedriver' + Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) end Capybara.register_driver :firefox_headless do |app| options = Selenium::WebDriver::Firefox::Options.new options.headless! Capybara::Selenium::Driver.new(app, browser: :firefox, options: options) - end + end if defined?(Selenium::WebDriver::Firefox) Capybara.register_driver :selenium_with_firebug do |app| profile = Selenium::WebDriver::Firefox::Profile.new @@ -86,7 +258,7 @@ def self.on_server? profile.enable_firebug options = Selenium::WebDriver::Firefox::Options.new(profile: profile) Capybara::Selenium::Driver.new(app, browser: :firefox, options: options) - end + end if defined?(Selenium::WebDriver::Firefox) Capybara.register_driver :safari do |app| Capybara::Selenium::Driver.new(app, browser: :safari) @@ -95,6 +267,8 @@ def self.on_server? Capybara.javascript_driver = case ENV['DRIVER'] when 'beheaded' then :firefox_headless + when 'chrome_undocked' then :chrome_undocked + when 'chrome_docked' then :chrome_docked when 'chrome' then :chrome when 'ff' then :selenium_with_firebug when 'firefox' then :firefox @@ -103,5 +277,4 @@ def self.on_server? when 'travis' then :chrome_headless_docker_travis else :selenium_chrome_headless end - end diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers working.rbx similarity index 100% rename from ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb rename to ruby/hyper-spec/lib/hyper-spec/component_test_helpers working.rbx diff --git a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb new file mode 100644 index 000000000..5aa2129c5 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb @@ -0,0 +1,164 @@ +module HyperSpec + # Defines a series of methods that will build a test page + # This module is typically included into the HyperSpecTestController class. + module ControllerHelpers + # These methods are dependent on the stack being used. See the + # RailsControllerHelpers module and rack.rb for two implementations. + # Each method should append the appropriate code to the @page array + + # return an empty 204 status either by setting headers or + # returning and appropriate response for rack. + def ping! + raise 'must implement' + end + + def json! + # this can be a no-op but if json is not included by the application, + # hyper-spec will fail, with an error complaining about to_json + end + + # return a script or style_sheet tag pointing to some asset. + # typically you be pointing to a path on the server or using + # sprockets to deliver the file. + + def require!(_file_) + raise 'must implement' + end + + def style_sheet!(_file_) + raise 'must implement' + end + + # deliver the page. The @page variable will contain the html ready to go, + # any additional options that are passed through from the spec will be + # in the @render_params variable. For example layout: 'my_special_layout' + + def deliver! + raise 'must implement' + end + + # generate a react_render top level block. This will only be called if + # you use the mount directive in your specs, so it is optional. + + def mount_component! + raise 'mount_component not implemented in HyperSpecTestController' + end + + # by default the route back to the controller will be the controller name, less the + # word Controller, and underscored. If you want some other name redefine this + # method in the HyperSpecController class. + + def self.included(base) + def base.route_root + # Implement underscore without using rails underscore, so we don't have a + # dependency on ActiveSupport + name.gsub(/Controller$/, '') + .gsub(/::/, '/') + .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') + .gsub(/([a-z\d])([A-Z])/, '\1_\2') + .tr('-', '_') + .downcase + end + end + + # The remainder of the methods should work for most implementations. + + # helper method checking the render_on parameter + + def on_client? + @render_on != :server_only + end + + # The controllers behavior is kept as an array of values in the Controller cache + # under a unique id for each test run. Grab the parameters and move them to instance vars + + # If this is just a ping! Then we can just exit with nil. + + def initialize! + return if params[:id] == 'ping' + + key = "/#{self.class.route_root}/#{params[:id]}" + test_params = Internal::Controller.cache_read(key) + + @component_name = test_params[0] + @component_params = test_params[1] + @html_block = test_params[2] + @render_params = test_params[3] + @render_on = @render_params.delete(:render_on) || :client_only + @_mock_time = @render_params.delete(:mock_time) + @style_sheet = @render_params.delete(:style_sheet) + @javascript = @render_params.delete(:javascript) + @code = @render_params.delete(:code) + + @page = [''] + end + + # add any html code generated by the insert_html directive + + def html_block! + @page << @html_block + end + + # patch behavior of the HyperComponent TopLevelRailsComponent class + # so that things like events are passed back to the test harness + TOP_LEVEL_COMPONENT_PATCH = + Opal.compile(File.read(File.expand_path('../sources/top_level_rails_component.rb', __dir__))) + + # patch time cop and lolex so they stay in sync across the client and server + TIME_COP_CLIENT_PATCH = + Opal.compile(File.read(File.expand_path('../hyper-spec/internal/time_cop.rb', __dir__))) + + "\n#{File.read(File.expand_path('../sources/lolex.js', __dir__))}" + + def client_code! + if @component_name + @page << "" + end + @page << "" if @code + end + + def time_cop_patch! + @page << "" + end + + # Add the go_function to the client console. This is used to stop a hyper-spec pause directive. + + def go_function! + @page << "' + end + + # First lines displayed on the console will be the name of the spec + + def example_title! + title = Internal::Controller.current_example_description! + @page << "" + end + + # generate each piece of the page, and then deliver it + + def style_sheet_file + @style_sheet || (!@render_params[:layout] && 'application') + end + + def application_file + @javascript || (on_client? && !@render_params[:layout] && 'application') + end + + def test + return ping! unless initialize! + + html_block! + example_title! if Internal::Controller.current_example + go_function! if on_client? + style_sheet!(style_sheet_file) if style_sheet_file + application!(application_file) if application_file + json! + time_cop_patch! if on_client? || Lolex.initialized? + client_code! if on_client? + mount_component! if @component_name + @page = @page.join("\n") + "\n\n" + deliver! + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/expectations.rb b/ruby/hyper-spec/lib/hyper-spec/expectations.rb new file mode 100644 index 000000000..4b2a033c6 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/expectations.rb @@ -0,0 +1,64 @@ +# don't put this in directory lib/rspec/ as that will cause stack overflow with rails/rspec loads +module RSpec + module Expectations + class ExpectationTarget; end + module HyperSpecInstanceMethods + def self.included(base) + base.include HyperSpec::Helpers + end + + def to_on_client(matcher, message = nil, &block) + evaluate_client.to(matcher, message, &block) + end + + alias on_client_to to_on_client + alias to_then to_on_client + alias then_to to_on_client + + def to_on_client_not(matcher, message = nil, &block) + evaluate_client.not_to(matcher, message, &block) + end + + alias on_client_to_not to_on_client_not + alias on_client_not_to to_on_client_not + alias to_not_on_client to_on_client_not + alias not_to_on_client to_on_client_not + alias then_to_not to_on_client_not + alias then_not_to to_on_client_not + alias to_not_then to_on_client_not + alias not_to_then to_on_client_not + + private + + def evaluate_client + source = add_opal_block(@args_str, @target) + value = @target.binding.eval("evaluate_ruby(#{source.inspect}, {}, {})") + ExpectationTarget.for(value, nil) + end + end + + class OnClientWithArgsTarget + include HyperSpecInstanceMethods + + def initialize(target, args) + unless args.is_a? Hash + raise ExpectationNotMetError, + "You must pass a hash of local var, value pairs to the 'with' modifier" + end + + @target = target + @args_str = args.collect do |name, value| + set_local_var(name, value) + end.join("\n") + end + end + + class BlockExpectationTarget < ExpectationTarget + include HyperSpecInstanceMethods + + def with(args) + OnClientWithArgsTarget.new(@target, args) + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/helpers.rb b/ruby/hyper-spec/lib/hyper-spec/helpers.rb new file mode 100644 index 000000000..e86104f20 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/helpers.rb @@ -0,0 +1,225 @@ +module HyperSpec + module Helpers + include Internal::ClientExecution + include Internal::Controller + include Internal::ComponentMount + include Internal::CopyLocals + include Internal::WindowSizing + + ## + # Mount a component on a page, with a full execution environment + # i.e. `mount('MyComponent')` will mount MyComponent on the page. + + # The params argument is a hash of parameters to be passed to the + # component. + # i.e. `mount('MyComponent', title: 'hello')` + + # The options parameters can set things like: + # + controller: the controller class, defaults to HyperSpecTestController + # + no_wait: do not wait for any JS to finish executing before proceeding with the spec + # + render_on: :client_only (default), :client_and_server, or :server_only + # + style_sheet: style sheet file defaults to 'application' + # + javascript: javascript file defaults to 'application' + # + layout: if provided will use the specified layout otherwise no layout is used + # Note that if specifying options the params will have to inclosed in their + # own hash. + # i.e. `mount('MyComponent', { title: 'hello' }, render_on: :server_only)` + # The options can be specified globally using the client_options method (see below.) + + # You may provide a block to mount. This block will be executed on the client + # before mounting the component. This is useful for setting up test + # components or modifying a components behavior. + # i.e. + # ```ruby + # mount('MyComponent', title: 'hello') do + # # this line will be printed on the client console + # puts "I'm about to mount my component!" + # end + # ``` + def mount(component_name = nil, params = nil, opts = {}, &block) + unless params + params = opts + opts = {} + end + internal_mount(component_name, params, client_options(opts), &block) + end + + ## + # The following methods retrieve callback and event responses from + # the mounted components. The history methods contain the array of all + # responses, while last_... returns the last response. + # i.e. event_history_for(:save) would return any save events + # that the component has raised. + + %i[ + callback_history_for last_callback_for clear_callback_history_for + event_history_for last_event_for clear_event_history_for + ].each do |method| + define_method(method) do |event_name| + evaluate_ruby( + "Hyperstack::Internal::Component::TopLevelRailsComponent.#{method}('#{event_name}')" + ) + end + end + + ## + # Define a code block to be prefixed to the mount code. + # Useful in before(:each) blocks. + + # In legacy code this was called `on_client`. To get the legacy + # behavior alias on_client before_mount + # but be aware that on_client is now by default the method + # for executing a block of code on the client which was called + # evaluate_ruby + + def before_mount(&block) + @_hyperspec_private_client_code = + "#{@_hyperspec_private_client_code}#{add_opal_block('', block)}" + end + + # Execute the block both on the client and on the server. Useful + # for mocking isomorphic classes such as ActiveRecord models. + + def isomorphic(&block) + yield + if page.instance_variable_get('@hyper_spec_mounted') + internal_evaluate_ruby(&block) + else + before_mount(&block) + end + end + + # Allows options to the mount method to be specified globally + + def client_option(opts = {}) + @_hyperspec_private_client_options ||= { arity_check: default_arity_check } + @_hyperspec_private_client_options.merge! opts + build_var_inclusion_lists + @_hyperspec_private_client_options + end + + def default_arity_check + Rails.application.config.opal.arity_check_enabled if defined? Rails + rescue StandardError + false + end + + alias client_options client_option + + ## + # shorthand for mount with no params (which will effectively reload the page.) + # also aliased as reload_page + def load_page + mount + end + + alias reload_page load_page + + ## + # evaluate a block (or string) on the client + # on_client(, , &block) + # + # normal use is to pass a block that will be compiled to the client + # but if the ruby code can be supplied as a string in the first arg. + + # opts are passed on to JSON.parse when retrieving the result + # from the client. + + # vars is a hash of name: value pairs. Each name will be initialized + # as a local variable on the client. + + # example: on_client(x: 12) { x * x } => 144 + + # in legacy code on_client was called before_mount + # to get legacy on_client behavior you can alias + # on_client before_mount + + alias on_client internal_evaluate_ruby + + # attempt to set the window to a particular size + + def size_window(width = nil, height = nil) + hs_internal_resize_to(*determine_size(width, height)) + rescue StandardError + true + end + + # same signature as on_client, but just returns the compiled + # js code. Useful for debugging suspected issues with the + # Opal compiler, etc. + + def to_js(*args, &block) + opal_compile(*process_params(*args, &block)) + end + + # legacy methods for backwards compatibility + # these may be removed in a future version + + def expect_evaluate_ruby(*args, &block) + expect(evaluate_ruby(*args, &block)) + end + + alias evaluate_ruby internal_evaluate_ruby + alias evaluate_promise evaluate_ruby + alias expect_promise expect_evaluate_ruby + + def run_on_client(&block) + script = opal_compile(Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last)) + page.execute_script(script) + end + + def insert_html(str) + @_hyperspec_private_html_block = "#{@_hyperspec_private_html_block}\n#{str}" + end + + def ppr(str) + js = opal_compile(str) + execute_script("console.log(#{js})") + end + + def debugger + `debugger` + nil + end + + def add_class(class_name, style) + @_hyperspec_private_client_code = + "#{@_hyperspec_private_client_code}ComponentHelpers.add_class('#{class_name}', #{style})\n" + end + + def attributes_on_client(model) + evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes").symbolize_keys + end + + ### --- Debugging Helpers ---- + + def pause(message = nil) + if message + puts message + internal_evaluate_ruby "puts #{message.inspect}.to_s + ' (type go() to continue)'" + end + + page.evaluate_script('window.hyper_spec_waiting_for_go = true') + + loop do + sleep 0.25 + break unless page.evaluate_script('window.hyper_spec_waiting_for_go') + end + end + + def open_in_chrome + # if ['linux', 'freebsd'].include?(`uname`.downcase) + # `google-chrome http://#{page.server.host}:#{page.server.port}#{page.current_path}` + # else + `open http://#{page.server.host}:#{page.server.port}#{page.current_path}` + # end + + loop do + sleep 1.hour + end + end + + # short hand for use in pry sessions + alias c? internal_evaluate_ruby + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb b/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb new file mode 100644 index 000000000..39d3666fb --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb @@ -0,0 +1,94 @@ +module HyperSpec + module Internal + module ClientExecution + def internal_evaluate_ruby(*args, &block) + insure_page_loaded + add_promise_execute_and_wait(*process_params(*args, &block)) + end + + private + + def add_opal_block(str, block) + return str unless block + + source = block.source + ast = Parser::CurrentRuby.parse(source) + ast = find_block(ast) + raise "could not find block within source: #{block.source}" unless ast + + "#{add_locals(str, block)}\n#{Unparser.unparse ast.children.last}" + end + + def add_promise_execute_and_wait(str, opts) + js = opal_compile(add_promise_wrapper(str)) + page.execute_script("window.hyper_spec_promise_result = false; #{js}") + Timeout.timeout(Capybara.default_max_wait_time) do + loop do + break if page.evaluate_script('!!window.hyper_spec_promise_result') + page.evaluate_script('!!window.hyper_spec_promise_failed && Opal.Opal.$raise(window.hyper_spec_promise_failed)') + + sleep 0.25 + end + end + JSON.parse(page.evaluate_script('window.hyper_spec_promise_result.$to_json()'), opts).first + end + + def add_promise_wrapper(str) + <<~RUBY + (#{str}).tap do |r| + if defined?(Promise) && r.is_a?(Promise) + r.then { |args| `window.hyper_spec_promise_result = [args]` } + .fail { |e| `window.hyper_spec_promise_failed = e` } + else + #after(0) do + #puts "setting window.hyper_spec_promise_result = [\#{r}]" + `window.hyper_spec_promise_result = [r]` + #end + end + end + RUBY + end + + def find_block(node) + # find a block with the ast tree. + + return false unless node.class == Parser::AST::Node + return node if the_node_you_are_looking_for?(node) + + node.children.each do |child| + found = find_block(child) + return found if found + end + false + end + + def process_params(*args, &block) + args = ['', *args] if args[0].is_a? Hash + args = [args[0], {}, args[1] || {}] if args.length < 3 + str, opts, vars = args + vars.each do |name, value| + str = "#{name} = #{value.inspect}\n#{str}" + end + [add_opal_block(str, block), opts] + end + + def the_node_you_are_looking_for?(node) + # we could also check that the block is going to the right method + # respond_to?(node.children.first.children[1]) && + # method(node.children.first.children[1]) == method(:evaluate_ruby) + # however that does not work for expect { ... }.on_client_to ... + # because now the block is being sent to expect... so we could + # check the above OR node.children.first.children[1] == :expect + # but what if there are two blocks? on and on... + node.type == :block && + node.children.first.class == Parser::AST::Node && + node.children.first.type == :send + end + + + def opal_compile(str) + Opal.hyperspec_compile(str, arity_check: client_options[:arity_check]) + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb new file mode 100644 index 000000000..366ea71a7 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb @@ -0,0 +1,140 @@ +module HyperSpec + module Internal + module ComponentMount + private + + TEST_CODE_KEY = 'hyper_spec_prerender_test_code.js'.freeze + + # rubocop:disable Metrics/MethodLength + def add_block_with_helpers(component_name, opts, block) + return unless block || @_hyperspec_private_client_code || component_name.nil? + + block_with_helpers = <<-RUBY + module ComponentHelpers + def self.js_eval(s) + `eval(s)` + end + def self.dasherize(s) + res = %x{ + s.replace(/[-_\\s]+/g, '-') + .replace(/([A-Z\\d]+)([A-Z][a-z])/g, '$1-$2') + .replace(/([a-z\\d])([A-Z])/g, '$1-$2') + .toLowerCase() + } + res + end + def self.add_class(class_name, styles={}) + style = styles.collect { |attr, value| "\#{dasherize(attr)}:\#{value}" }.join("; ") + cs = class_name.to_s + %x{ + var style_el = document.createElement("style"); + var css = "." + cs + " { " + style + " }"; + style_el.type = "text/css"; + if (style_el.styleSheet){ + style_el.styleSheet.cssText = css; + } else { + style_el.appendChild(document.createTextNode(css)); + } + document.head.appendChild(style_el); + } + end + end + #{test_dummy} + #{@_hyperspec_private_client_code} + #{"#{add_locals('', block)}\n#{Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last)}" if block} + RUBY + @_hyperspec_private_client_code = nil + opts[:code] = opal_compile(block_with_helpers) + end + # rubocop:enable Metrics/MethodLength + + def build_test_url_for(controller = nil, ping = nil) + id = ping ? 'ping' : Controller.test_id + "/#{route_root_for(controller)}/#{id}" + end + + def insure_page_loaded(only_if_code_or_html_exists = nil) + return if only_if_code_or_html_exists && !@_hyperspec_private_client_code && !@_hyperspec_private_html_block + + # if we are not resetting between examples, or think its mounted + # then look for Opal, but if we can't find it, then ping to clear and try again + if !HyperSpec.reset_between_examples? || page.instance_variable_get('@hyper_spec_mounted') + r = evaluate_script('Opal && true') rescue nil + return if r + + page.visit build_test_url_for(nil, true) rescue nil + end + load_page + end + + def internal_mount(component_name, params, opts, &block) + # TODO: refactor this + test_url = build_test_url_for(opts.delete(:controller)) + add_block_with_helpers(component_name, opts, block) + send_params_to_controller_via_cache(test_url, component_name, params, opts) + setup_prerendering(opts) + page.instance_variable_set('@hyper_spec_mounted', false) + visit test_url + wait_for_ajax unless opts[:no_wait] + page.instance_variable_set('@hyper_spec_mounted', true) + Lolex.init(self, client_options[:time_zone], client_options[:clock_resolution]) + end + + def prerendering?(opts) + %i[both server_only].include?(opts[:render_on]) + end + + def send_params_to_controller_via_cache(test_url, component_name, params, opts) + component_name ||= 'Hyperstack::Internal::Component::TestDummy' if test_dummy + Controller.cache_write( + test_url, + [component_name, params, @_hyperspec_private_html_block, opts] + ) + @_hyperspec_private_html_block = nil + end + + # test_code_key = "hyper_spec_prerender_test_code.js" + # if defined? ::Hyperstack::Component + # @@original_server_render_files ||= ::Rails.configuration.react.server_renderer_options[:files] + # if opts[:render_on] == :both || opts[:render_on] == :server_only + # unless opts[:code].blank? + # ComponentTestHelpers.cache_write(test_code_key, opts[:code]) + # ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + [test_code_key] + # ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes + # else + # ComponentTestHelpers.cache_delete(test_code_key) + # ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + # ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes + # end + # end + # end + + def setup_prerendering(opts) + return unless defined?(::Hyperstack::Component) && prerendering?(opts) + + @@original_server_render_files ||= ::Rails.configuration.react.server_renderer_options[:files] + ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + if opts[:code].blank? + Controller.cache_delete(TEST_CODE_KEY) + else + Controller.cache_write(TEST_CODE_KEY, opts[:code]) + ::Rails.configuration.react.server_renderer_options[:files] += [TEST_CODE_KEY] + end + ::React::ServerRendering.reset_pool + # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher + # doesnt look for cache changes + end + + def test_dummy + return unless defined? ::Hyperstack::Component + + <<-RUBY + class Hyperstack::Internal::Component::TestDummy + include Hyperstack::Component + render {} + end + RUBY + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/controller.rb b/ruby/hyper-spec/lib/hyper-spec/internal/controller.rb new file mode 100644 index 000000000..04de4251d --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/controller.rb @@ -0,0 +1,70 @@ +module HyperSpec + module Internal + module Controller + class << self + attr_accessor :current_example + attr_accessor :description_displayed + + def test_id + @_hyperspec_private_test_id ||= 0 + @_hyperspec_private_test_id += 1 + end + + include ActionView::Helpers::JavaScriptHelper + + def current_example_description! + title = "#{title}...continued." if description_displayed + self.description_displayed = true + "#{escape_javascript(current_example.description)}#{title}" + end + + def file_cache + @file_cache ||= FileCache.new('cache', '/tmp/hyper-spec-caches', 30, 3) + end + + def cache_read(key) + file_cache.get(key) + end + + def cache_write(key, value) + file_cache.set(key, value) + end + + def cache_delete(key) + file_cache.delete(key) + rescue StandardError + nil + end + end + + # By default we assume we are operating in a Rails environment and will + # hook in using a rails controller. To override this define the + # HyperSpecController class in your spec helper. See the rack.rb file + # for an example of how to do this. + + def hyper_spec_test_controller + return ::HyperSpecTestController if defined?(::HyperSpecTestController) + + base = if defined? ApplicationController + Class.new ApplicationController + elsif defined? ::ActionController::Base + Class.new ::ActionController::Base + else + raise "Unless using Rails you must define the HyperSpecTestController\n"\ + 'For rack apps try requiring hyper-spec/rack.' + end + Object.const_set('HyperSpecTestController', base) + end + + # First insure we have a controller, then make sure it responds to the test method + # if not, then add the rails specific controller methods. The RailsControllerHelpers + # module will automatically add a top level route back to the controller. + + def route_root_for(controller) + controller ||= hyper_spec_test_controller + controller.include RailsControllerHelpers unless controller.method_defined?(:test) + controller.route_root + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/copy_locals.rb b/ruby/hyper-spec/lib/hyper-spec/internal/copy_locals.rb new file mode 100644 index 000000000..6ba1e8a33 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/copy_locals.rb @@ -0,0 +1,103 @@ +module HyperSpec + module Internal + module CopyLocals + private + + def build_var_inclusion_lists + build_included_list + build_excluded_list + end + + def build_included_list + @_hyperspec_private_included_vars = nil + return unless @_hyperspec_private_client_options.key? :include_vars + + included = @_hyperspec_private_client_options[:include_vars] + if included.is_a? Symbol + @_hyperspec_private_included_vars = [included] + elsif included.is_a?(Array) + @_hyperspec_private_included_vars = included + elsif !included + @_hyperspec_private_included_vars = [] + end + end + + PRIVATE_VARIABLES = %i[ + @__inspect_output @__memoized @example @_hyperspec_private_client_code + @_hyperspec_private_html_block @fixture_cache + @fixture_connections @connection_subscriber @loaded_fixtures + @_hyperspec_private_client_options + @_hyperspec_private_included_vars + @_hyperspec_private_excluded_vars + b __ _ _ex_ pry_instance _out_ _in_ _dir_ _file_ + ] + + def build_excluded_list + return unless @_hyperspec_private_client_options + + excluded = @_hyperspec_private_client_options[:exclude_vars] + if excluded.is_a? Symbol + @_hyperspec_private_excluded_vars = [excluded] + elsif excluded.is_a?(Array) + @_hyperspec_private_excluded_vars = excluded + elsif excluded + @_hyperspec_private_included_vars = [] + end + end + + def var_excluded?(var, binding) + return true if PRIVATE_VARIABLES.include? var + + excluded = binding.eval('instance_variable_get(:@_hyperspec_private_excluded_vars)') + return true if excluded&.include?(var) + + included = binding.eval('instance_variable_get(:@_hyperspec_private_included_vars)') + included && !included.include?(var) + end + + def add_locals(in_str, block) + b = block.binding + add_instance_vars(b, add_local_vars(b, add_memoized_vars(b, in_str))) + end + + def add_memoized_vars(binding, in_str) + memoized = binding.eval('__memoized').instance_variable_get(:@memoized) + return in_str unless memoized + + memoized.inject(in_str) do |str, pair| + next str if var_excluded?(pair.first, binding) + + "#{str}\n#{set_local_var(pair.first, pair.last)}" + end + end + + def add_local_vars(binding, in_str) + binding.local_variables.inject(in_str) do |str, var| + next str if var_excluded?(var, binding) + + "#{str}\n#{set_local_var(var, binding.local_variable_get(var))}" + end + end + + def add_instance_vars(binding, in_str) + binding.eval('instance_variables').inject(in_str) do |str, var| + next str if var_excluded?(var, binding) + + "#{str}\n#{set_local_var(var, binding.eval("instance_variable_get('#{var}')"))}" + end + end + + def set_local_var(name, object) + serialized = object.opal_serialize + if serialized + "#{name} = #{serialized}" + else + "self.class.define_method(:#{name}) "\ + "{ raise 'Attempt to access the variable #{name} "\ + 'that was defined in the spec, but its value could not be serialized '\ + "so it is undefined on the client.' }" + end + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb b/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb new file mode 100644 index 000000000..d5f13c3f6 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb @@ -0,0 +1,86 @@ +module Opal + # strips off stuff that confuses things when transmitting to the client + # and prints offending code if it can't be compiled + def self.hyperspec_compile(str, opts = {}) + compile(str, opts).gsub("// Prepare super implicit arguments\n", '') + .delete("\n").gsub('(Opal);', '(Opal)') + # rubocop:disable Lint/RescueException + # we are going to reraise it anyway, so its fine to catch EVERYTHING! + rescue Exception => e + puts "puts could not compile: \n\n#{str}\n\n" + raise e + end + # rubocop:enable Lint/RescueException +end + +module Unparser + class Emitter + # Emitter for send + class Send < self + def local_variable_clash? + selector =~ /^[A-Z]/ || + local_variable_scope.local_variable_defined_for_node?(node, selector) + end + end + end +end + +module MethodSource + class << self + alias original_lines_for_before_hyper_spec lines_for + alias original_source_helper_before_hyper_spec source_helper + + def source_helper(source_location, name = nil) + source_location[1] = 1 if source_location[0] == '(pry)' + original_source_helper_before_hyper_spec source_location, name + end + + def lines_for(file_name, name = nil) + if file_name == '(pry)' + HyperSpec.current_pry_code_block + else + original_lines_for_before_hyper_spec file_name, name + end + end + end +end + +class Object + def opal_serialize + nil + end +end + +class Hash + def opal_serialize + "{#{collect { |k, v| "#{k.opal_serialize} => #{v.opal_serialize}" }.join(', ')}}" + end +end + +class Array + def opal_serialize + "[#{collect { |v| v.opal_serialize }.join(', ')}]" + end +end + +[FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass].each do |klass| + klass.send(:define_method, :opal_serialize) do + inspect + end +end + +# rubocop:disable Lint/UnifiedInteger - patch for ruby prior to 2.4 +if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0') + [Bignum, Fixnum].each do |klass| + klass.send(:define_method, :opal_serialize) do + inspect + end + end +end +# rubocop:enable Lint/UnifiedInteger + +class Time + def to_opal_expression + "Time.parse('#{inspect}')" + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/rails_controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/internal/rails_controller_helpers.rb new file mode 100644 index 000000000..e36089858 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/rails_controller_helpers.rb @@ -0,0 +1,50 @@ +module HyperSpec + module Internal + module RailsControllerHelpers + def self.included(base) + base.include ControllerHelpers + base.include Helpers + routes = ::Rails.application.routes + routes.disable_clear_and_finalize = true + routes.clear! + routes.draw { get "/#{base.route_root}/:id", to: "#{base.route_root}#test" } + ::Rails.application.routes_reloader.paths.each { |path| load(path) } + routes.finalize! + ActiveSupport.on_load(:action_controller) { routes.finalize! } + ensure + routes.disable_clear_and_finalize = false + end + + module Helpers + def ping! + head(:no_content) + nil + end + + def mount_component! + @page << '<%= react_component @component_name, @component_params, '\ + "{ prerender: #{@render_on != :client_only} } %>" + end + + def application!(file) + @page << "<%= javascript_include_tag '#{file}' %>" + end + + def style_sheet!(file) + @page << "<%= stylesheet_link_tag '#{file}' %>" + end + + def deliver! + @render_params[:inline] = @page + response.headers['Cache-Control'] = 'max-age=120' + response.headers['X-Tracking-ID'] = '123456' + render @render_params + end + + def server_only? + @render_on == :server_only + end + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/time_cop.rb b/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb similarity index 91% rename from ruby/hyper-spec/lib/hyper-spec/time_cop.rb rename to ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb index f5f4fae51..35437a9f2 100644 --- a/ruby/hyper-spec/lib/hyper-spec/time_cop.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb @@ -59,6 +59,10 @@ def create_ticker ticker end + def init(scale: 1, resolution: 10) + update_lolex(Time.now, scale, resolution) + end + def update_lolex(time, scale, resolution) `#{@lolex}.uninstall()` && return if scale.nil? @mock_start_time = time.to_f * 1000 @@ -80,6 +84,14 @@ def update_lolex(time, scale, resolution) end end + # create an alias for Lolex.init so we can say Timecop.init on the client + + class Timecop + def self.init(*args) + Lolex.init(*args) + end + end + else require 'timecop' @@ -136,7 +148,7 @@ def pending_evaluations def evaluate_ruby(&block) if @capybara_page - @capybara_page.evaluate_ruby(yield) + @capybara_page.internal_evaluate_ruby(yield) else pending_evaluations << block end @@ -144,7 +156,7 @@ def evaluate_ruby(&block) def run_pending_evaluations return if pending_evaluations.empty? - @capybara_page.evaluate_ruby(pending_evaluations.collect(&:call).join("\n")) + @capybara_page.internal_evaluate_ruby(pending_evaluations.collect(&:call).join("\n")) @pending_evaluations ||= [] end end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb b/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb new file mode 100644 index 000000000..042f5be51 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb @@ -0,0 +1,73 @@ +module HyperSpec + module Internal + module WindowSizing + private + + STD_SIZES = { + small: [480, 320], + mobile: [640, 480], + tablet: [960, 640], + large: [1920, 6000], + default: [1024, 768] + } + + def determine_size(width, height) + width, height = [height, width] if width == :portrait + width, height = width if width.is_a? Array + portrait = true if height == :portrait + width ||= :default + width, height = STD_SIZES[width] if STD_SIZES[width] + width, height = [height, width] if portrait + [width + debugger_width, height] + end + + def debugger_width + RSpec.configuration.debugger_width ||= begin + hs_internal_resize_to(1000, 500) do + sleep RSpec.configuration.wait_for_initialization_time + end + inner_width = evaluate_script('window.innerWidth') + 1000 - inner_width + end + RSpec.configuration.debugger_width + end + + def hs_internal_resize_to(width, height) + Capybara.current_session.current_window.resize_to(width, height) + yield if block_given? + wait_for_size(width, height) + end + + def wait_for_size(width, height) + @start_time = Capybara::Helpers.monotonic_time + @stable_count_w = @stable_count_h = 0 + prev_size = [0, 0] + loop do + sleep 0.05 + curr_size = evaluate_script('[window.innerWidth, window.innerHeight]') + + return true if curr_size == [width, height] || stalled?(prev_size, curr_size) + + prev_size = curr_size + check_time! + end + end + + def check_time! + if (Capybara::Helpers.monotonic_time - @start_time) > + Capybara.current_session.config.default_max_wait_time + raise Capybara::WindowError, + 'Window size not stable within '\ + "#{Capybara.current_session.config.default_max_wait_time} seconds." + end + end + + def stalled?(prev_size, curr_size) + # some maximum or minimum is reached and size doesn't change anymore + @stable_count_w += 1 if prev_size[0] == curr_size[0] + @stable_count_h += 1 if prev_size[1] == curr_size[1] + @stable_count_w > 4 || @stable_count_h > 4 + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/rack.rb b/ruby/hyper-spec/lib/hyper-spec/rack.rb new file mode 100644 index 000000000..14fed7e37 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/rack.rb @@ -0,0 +1,67 @@ +require 'hyper-spec' + +class HyperSpecTestController < SimpleDelegator + include HyperSpec::ControllerHelpers + + class << self + attr_reader :sprocket_server + attr_reader :asset_path + + def wrap(app:, append_path: 'app', asset_path: '/assets') + @sprocket_server = Opal::Sprockets::Server.new do |s| + s.append_path append_path + end + + @asset_path = asset_path + + ::Rack::Builder.app(app) do + map "/#{HyperSpecTestController.route_root}" do + use HyperSpecTestController + end + end + end + end + + def sprocket_server + self.class.sprocket_server + end + + def asset_path + self.class.asset_path + end + + def ping! + [204, {}, []] + end + + def application!(file) + @page << Opal::Sprockets.javascript_include_tag( + file, + debug: true, + sprockets: sprocket_server.sprockets, + prefix: asset_path + ) + end + + def json! + @page << Opal::Sprockets.javascript_include_tag( + 'json', + debug: true, + sprockets: sprocket_server.sprockets, + prefix: asset_path + ) + end + + + def style_sheet!(_file_); end + + def deliver! + [200, { 'Content-Type' => 'text/html' }, [@page]] + end + + def call(env) + __setobj__(Rack::Request.new(env)) + params[:id] = path.split('/').last + test + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/unparser_patch.rb b/ruby/hyper-spec/lib/hyper-spec/unparser_patch.rb deleted file mode 100644 index dc0a720b8..000000000 --- a/ruby/hyper-spec/lib/hyper-spec/unparser_patch.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Unparser - class Emitter - # Emitter for send - class Send < self - def local_variable_clash? - selector =~ /^[A-Z]/ || local_variable_scope.local_variable_defined_for_node?(node, selector) - end - end - end -end diff --git a/ruby/hyper-spec/lib/hyper-spec/version.rb b/ruby/hyper-spec/lib/hyper-spec/version.rb index 9401ff866..59df37dbd 100644 --- a/ruby/hyper-spec/lib/hyper-spec/version.rb +++ b/ruby/hyper-spec/lib/hyper-spec/version.rb @@ -1,3 +1,3 @@ module HyperSpec - VERSION = '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' end diff --git a/ruby/hyper-spec/lib/hyper-spec/wait_for_ajax.rb b/ruby/hyper-spec/lib/hyper-spec/wait_for_ajax.rb index 9d604582a..a58671f55 100644 --- a/ruby/hyper-spec/lib/hyper-spec/wait_for_ajax.rb +++ b/ruby/hyper-spec/lib/hyper-spec/wait_for_ajax.rb @@ -29,7 +29,7 @@ def running? } })(); CODE - page.evaluate_script(jscode) + Capybara.page.evaluate_script(jscode) rescue Exception => e puts "wait_for_ajax failed while testing state of ajax requests: #{e}" end diff --git a/ruby/hyper-spec/multi_level_how_it_works.md b/ruby/hyper-spec/multi_level_how_it_works.md new file mode 100644 index 000000000..36d56dbc7 --- /dev/null +++ b/ruby/hyper-spec/multi_level_how_it_works.md @@ -0,0 +1,49 @@ +each level copies instance variables from outer levels +there (seems) to be no way to create a config.before(:all) that works for each level of describe +but it could be done like this: + +```ruby +RSpec.configure do |config| + config.before(:each) do |example| + puts "#{example} - client_loaded: #{example.example_group.metadata[:client_loaded?]}" + unless example.example_group.metadata[:client_loaded?] + example.example_group.metadata[:client_loaded?] = true + # do whatever needs to be done once per context + end + end +end +``` + +```ruby +describe "outer describe" do + before(:all) do + # mount, before_mount, on_client, insert_html, etc all queue up + end + it "spec 1" do + # outer describe before(:all) client stuff executed here + puts "spec 1" + end + describe "inner describe 1" do + before(:all) do + # reload <- will restore client context to state at end of parent before(:all) + end + it "spec 1.1" do + puts "spec 1.1" + end + it "spec 1.2" do + puts "spec 1.2" + end + end + describe "inner describe 2" do + it "spec 2.1" do + puts "spec 2.1" + end + it "spec 1.2" do + puts "spec 2.2" + end + end + it "spec 2" do + puts "spec 2" + end +end +``` diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index a8b30dffe..d1e2af1d6 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -1,12 +1,17 @@ require 'spec_helper' describe 'hyper-spec', js: true do + + it 'can visit a page' do + visit 'test' + end + it 'will mount a component' do mount "SayHello", name: 'Fred' expect(page).to have_content('Hello there Fred') end - it "can the mount a component defined in mounts code block" do + it "can mount a component defined in the mounts code block" do mount 'ShowOff' do class ShowOff include Hyperstack::Component @@ -16,15 +21,49 @@ class ShowOff expect(page).to have_content('Now how cool is that???') end + it 'can add some html code before mounting' do + insert_html <<-HTML +
    insert some code
    + HTML + mount + expect(page).to have_content('insert some code') + end + + it 'can pause the server', skip: 'unreliable' do + # this is pretty ugly with these dead waits, but any attempt to do an evaluate_script "go()" without the + # wait breaks + th = Thread.new { pause('hello') } + sleep 5 + expect(th).to be_alive + evaluate_script "go()" + sleep 1 + expect(th).not_to be_alive + end + context "the client_option method" do - it "can rendered server side only" do + it "can render server side only", :prerendering_on do client_option render_on: :server_only mount 'SayHello', name: 'George' expect(page).to have_content('Hello there George') expect(evaluate_script('typeof React')).to eq('undefined') end + it "can render server side only with code defined in the mount", :prerendering_on do + client_option render_on: :server_only + mount 'SayHello2', name: 'George' do + class SayHello2 + include Hyperstack::Component + param :name + render(DIV) do + "Hello there #{@Name}" + end + end + end + expect(page).to have_content('Hello there George') + expect(evaluate_script('typeof React')).to eq('undefined') + end + it "can use the application's layout" do client_option layout: 'application' mount 'SayHello', name: 'Sam' @@ -69,6 +108,20 @@ def factorial(n) expect(evaluate_ruby('factorial(5)')).to eq(factorial(5)) end + it "can load isomorphic code after loading" do + on_client do + CONSTANT = 1 + end + CONSTANT = 1 + isomorphic do + def factorial(n) + n==CONSTANT ? CONSTANT : n * factorial(n-CONSTANT) + end + nil + end + expect(evaluate_ruby('factorial(5)')).to eq(factorial(5)) + end + context 'promise helpers' do # just to demonstrate a few things: # 1 - You can use methods like mount, isomorphic, on_client in before(:each) blocks @@ -84,8 +137,7 @@ def wait(seconds) it 'evaluate_promise will wait for the promise to resolve' do start = Time.now - answer = - evaluate_promise do + answer = evaluate_promise do wait(DELAY) end expect(answer).to eq(DELAY) @@ -99,6 +151,14 @@ def wait(seconds) end.to eq(DELAY) expect(Time.now-start).to be >= DELAY end + + it "will raise an error if a promise is rejected" do + begin + on_client { Promise.new.reject("foo") } + rescue StandardError => e + expect(e.message).to start_with "javascript error: foo\n" + end + end end context 'event and callback handlers' do @@ -153,7 +213,7 @@ def increment_click end - it "can add classes during testing" do + it "can add style classes during testing" do add_class :some_class, borderStyle: :solid mount 'StyledDiv' do class StyledDiv @@ -195,13 +255,13 @@ class StyledDiv it "will use TimeCop travelling time with scaling" do Timecop.scale 60, Time.now-1.year do - expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(2).of(Time.now.to_i) + expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(3).of(Time.now.to_i) start_time = Time.now sleep 1 # sleep is still in "real time" but Time will move 60 times faster expect(start_time).to be_within(1).of(Time.now-1.minute) - expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(2).of(Time.now.to_i) + expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(3).of(Time.now.to_i) end - expect(evaluate_ruby('Time.now.to_i')).to be_within(2).of(Time.now.to_i+@sync_gap) + expect(evaluate_ruby('Time.now.to_i')).to be_within(3).of(Time.now.to_i+@sync_gap) end it "will advance time along with time cop freezing" do @@ -210,7 +270,7 @@ class StyledDiv Timecop.freeze Time.now-2.years expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(1).of(Time.now.to_i) Timecop.return - expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(2).of(Time.now.to_i+@sync_gap) + expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(3).of(Time.now.to_i+@sync_gap) end it "can temporarily return to true time" do @@ -223,6 +283,217 @@ class StyledDiv expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(1).of(Time.now.to_i+@sync_gap) end end + + context 'the no-reset flag', :no_reset do + it 'will mount the component first' do + mount 'TestComponent' do + class TestComponent + include Hyperstack::Component + include Hyperstack::State::Observable + class << self + state_accessor :title + end + render(DIV) do + TestComponent.title + end + end + end + on_client { TestComponent.title = 'The Title' } + expect(page).to have_content('The Title') + end + it 'but will not mount it again' do + expect(page).to have_content('The Title') + end + end + + context "new style rspec expressions", no_reset: true do + + before(:each) do + @str = 'hello' + end + + let(:another_string) { 'a man a plan a canal panama' } + + it 'can evaluate expressions on the client using the on_client_to method' do + expect { 12 + 12 }.on_client_to eq 24 + end + + it 'with the on_client_to on a new line' do + expect do + 12 + 12 + end + .on_client_to eq 24 + end + + it 'can evaluate expressions on the client using the on_client_not_to method' do + expect { 12 + 12 }.on_client_not_to eq 25 + end + + it 'can use the to_then method to evaluate promises on the client' do + expect do + Promise.new.tap { |p| after(1) { p.resolve('done') } } + end.to_then eq('done') + end + + context 'copying local vars:' do + let!(:memoized_var) { true } + let!(:another_memoized_var) { true } + before(:each) do + on_client do + send(:remove_instance_variable, :@instance_var) rescue nil + send(:remove_instance_variable, :@another_instance_var) rescue nil + end + end + + it 'will copy local vars to the client' do + str = 'hello' + expect { str.reverse }.on_client_to eq str.reverse + end + + it 'will copy instance vars to the client' do + expect { @str.reverse }.on_client_to eq @str.reverse + end + + it 'will copy memoized values to the client' do + expect { another_string.gsub(/\W/, '') }.on_client_to eq another_string.gsub(/\W/, '') + end + + it 'will deal with unserailized local vars, instance vars and memoized values correctly' do + foo_bar = page + expect do + evaluate_ruby { foo_bar } + end.to raise_error(Exception, /foo_bar/) + end + + it 'will ignore unserailized local vars, instance vars and memoized values if not accessed' do + foo_bar = page + good_value = 12 + expect { good_value * 2 }.on_client_to eq good_value * 2 + end + + it 'will allow unserailized local vars, instance vars and memoized values can be redefined on the client' do + foo_bar = page + expect do + foo_bar = 12 + foo_bar * 2 + end.on_client_to eq 24 + end + + context 'the include_vars option' do + [false, nil, []].each do |include_vars| + it "will not copy any vars if the include_vars option is #{include_vars}" do + client_option include_vars: include_vars + @instance_var = true + local_var = true + expect { @instance_var }.on_client_to be_nil + expect { defined?(local_var) }.on_client_to be_falsy + expect { defined?(memoized_var) }.on_client_to be_falsy + end + end + it "will copy all the vars if the include_vars option is a non-array truthy value" do + client_option include_vars: 123 + @instance_var = true + local_var = true + expect { @instance_var }.on_client_to be true + expect { local_var }.on_client_to be true + expect { memoized_var }.on_client_to be true + end + %i[@instance_var memoized_var local_var].each do |var| + it "will copy only a single var if the include_vars option is a name like #{var}" do + client_option include_vars: var + @instance_var = true + local_var = true + expect { @instance_var.nil? }.on_client_to eq(var != :@instance_var) + expect { !!defined?(local_var) }.on_client_to eq(var == :local_var) + expect { !!defined?(memoized_var) }.on_client_to eq(var == :memoized_var) + end + end + it 'will only copy vars listed in the include_vars option' do + client_option include_vars: [:another_memoized_var, :@another_instance_var, :another_local_var] + @instance_var = true + local_var = true + @another_instance_var = true + another_local_var = true + expect { @instance_var }.on_client_to be_nil + expect { defined?(local_var) }.on_client_to be_falsy + expect { defined?(memoized_var) }.on_client_to be_falsy + expect { @another_instance_var }.on_client_to be true + expect { another_local_var }.on_client_to be true + expect { another_memoized_var }.on_client_to be true + end + end + + context 'the exclude_vars option' do + [false, nil, []].each do |exclude_vars| + it "will copy all vars if the exclude_vars option is #{exclude_vars}" do + client_option exclude_vars: exclude_vars + @instance_var = true + local_var = true + expect { @instance_var }.on_client_to be true + expect { local_var }.on_client_to be true + expect { memoized_var }.on_client_to be true + end + end + it "will not copy any vars if the exclude_vars option is a non-array truthy value" do + client_option exclude_vars: 123 + @instance_var = true + local_var = true + expect { @instance_var }.on_client_to be_nil + expect { defined?(local_var) }.on_client_to be_falsy + expect { defined?(memoized_var) }.on_client_to be_falsy + end + %i[@instance_var memoized_var local_var].each do |var| + it "will exclude a single var if the exclude_vars option is a name like #{var}" do + client_option exclude_vars: var + @instance_var = true + local_var = true + expect { @instance_var.nil? }.on_client_to eq(var == :@instance_var) + expect { !!defined?(local_var) }.on_client_to eq(var != :local_var) + expect { !!defined?(memoized_var) }.on_client_to eq(var != :memoized_var) + end + end + it 'will not copy vars listed in the exclude_vars option' do + client_option exclude_vars: [:memoized_var, :@instance_var, :local_var] + @instance_var = true + local_var = true + @another_instance_var = true + another_local_var = true + expect { @instance_var }.on_client_to be_nil + expect { defined?(local_var) }.on_client_to be_falsy + expect { defined?(memoized_var) }.on_client_to be_falsy + expect { @another_instance_var }.on_client_to be true + expect { another_local_var }.on_client_to be true + expect { another_memoized_var }.on_client_to be true + end + end + end + + it 'aliases evaluate_ruby as on_client and c?' do + expect(on_client { 12 % 5 }).to eq(2) + expect(c? { 12 % 5 }).to eq(2) + end + + it 'allows local variables on the client to be set using the with method' do + expect { with_var * 2 }.with(with_var: 4).on_client_to eq(8) + end + + it "works with complex expressions", skip: (Parser::VERSION < "2.7.2.0" ) && "Incompatible with Parser #{Parser::VERSION}" do + expect do + hash = { 'foo' => 1} + hash['foo'] += 1 + hash['foo'] + end.on_client_to eq(2) + end + end + + it "will use the arity_check option" do + # We run the specs with arity_check_enabled so this should default to being on + expect { -> (x, y: 2) { }.parameters }.on_client_to eq [["req", "x"], ["key", "y"]] + client_option arity_check: false + expect { -> (x, y: 2) { }.parameters }.on_client_to eq [] + client_option arity_check: true + expect { -> (x, y: 2) { }.parameters }.on_client_to eq [["req", "x"], ["key", "y"]] + end end RSpec::Steps.steps "will size_window to", js: true do diff --git a/ruby/hyper-spec/spec/spec_helper.rb b/ruby/hyper-spec/spec/spec_helper.rb index aceee18f8..deee6f270 100644 --- a/ruby/hyper-spec/spec/spec_helper.rb +++ b/ruby/hyper-spec/spec/spec_helper.rb @@ -1,6 +1,5 @@ require 'hyper-spec' require 'pry' -require 'opal-browser' ENV["RAILS_ENV"] ||= 'test' require File.expand_path('../test_app/config/environment', __FILE__) @@ -15,27 +14,37 @@ def computed_style(selector, prop) "window.getComputedStyle(document.querySelector('#{selector}'))['#{prop}']" ) end + def calculate_window_restrictions return if @min_width - size_window(100,100) + + size_window(100, 100) @min_width = width - size_window(500,500) - @height_adjust = 500-height + @min_height = height + size_window(500, 500) + @height_adjust = 500 - height size_window(6000, 6000) @max_width = width @max_height = height end + def height evaluate_script('window.innerHeight') end + def width evaluate_script('window.innerWidth') end + def dims [width, height] end + def adjusted(width, height) - [[@max_width, [width, @min_width].max].min, [@max_height, height-@height_adjust].min] + [ + [@max_width, [width, @min_width].max].min, + [@max_height, [height - @height_adjust, @min_height].max].min + ] end end @@ -44,4 +53,15 @@ def adjusted(width, height) # config.after :each do # Rails.cache.clear # end + + config.before :suite do + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end + + config.around(:each, :prerendering_on) do |example| + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) + end end diff --git a/ruby/hyper-spec/spec/test_app/app/assets/config/manifest.js b/ruby/hyper-spec/spec/test_app/app/assets/config/manifest.js new file mode 100644 index 000000000..21a78805c --- /dev/null +++ b/ruby/hyper-spec/spec/test_app/app/assets/config/manifest.js @@ -0,0 +1,2 @@ +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/ruby/hyper-spec/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-spec/spec/test_app/app/assets/javascripts/application.js index 26e4d55ab..43638e739 100644 --- a/ruby/hyper-spec/spec/test_app/app/assets/javascripts/application.js +++ b/ruby/hyper-spec/spec/test_app/app/assets/javascripts/application.js @@ -1,4 +1,9 @@ //= require 'react' //= require 'react_ujs' //= require 'components' -Opal.load('components'); +if (typeof(OpalLoaded)=='undefined') { + Opal.load('components'); +} else { + Opal.loaded(OpalLoaded || []); + Opal.require("components"); +} diff --git a/ruby/hyper-spec/spec/test_app/app/assets/javascripts/server_rendering.js b/ruby/hyper-spec/spec/test_app/app/assets/javascripts/server_rendering.js index 29f5acdd8..df7910ca2 100644 --- a/ruby/hyper-spec/spec/test_app/app/assets/javascripts/server_rendering.js +++ b/ruby/hyper-spec/spec/test_app/app/assets/javascripts/server_rendering.js @@ -1,4 +1,9 @@ //= require 'react-server' //= require 'react_ujs' //= require 'components' -Opal.load('components') \ No newline at end of file +if (typeof(OpalLoaded)=='undefined') { + Opal.load('components'); +} else { + Opal.loaded(OpalLoaded || []); + Opal.require("components"); +} diff --git a/ruby/hyper-spec/spec/test_app/app/views/components.rb b/ruby/hyper-spec/spec/test_app/app/views/components.rb index ed15dfb85..d81c614ca 100644 --- a/ruby/hyper-spec/spec/test_app/app/views/components.rb +++ b/ruby/hyper-spec/spec/test_app/app/views/components.rb @@ -2,6 +2,7 @@ require 'promise' require 'hyper-component' require 'hyper-state' +require 'time' if Hyperstack::Component::IsomorphicHelpers.on_opal_client? require 'browser' require 'browser/delay' diff --git a/ruby/hyper-spec/spec/test_app/app/views/components/show.rb b/ruby/hyper-spec/spec/test_app/app/views/components/show.rb new file mode 100644 index 000000000..fa78d4c01 --- /dev/null +++ b/ruby/hyper-spec/spec/test_app/app/views/components/show.rb @@ -0,0 +1,6 @@ +class Show + include Hyperstack::Component + render(DIV) do + "Hello There From A Controller" + end +end diff --git a/ruby/hyper-spec/spec/test_app/bin/bundle b/ruby/hyper-spec/spec/test_app/bin/bundle old mode 100755 new mode 100644 diff --git a/ruby/hyper-spec/spec/test_app/bin/rails b/ruby/hyper-spec/spec/test_app/bin/rails old mode 100755 new mode 100644 diff --git a/ruby/hyper-spec/spec/test_app/bin/rake b/ruby/hyper-spec/spec/test_app/bin/rake old mode 100755 new mode 100644 diff --git a/ruby/hyper-spec/spec/test_app/bin/setup b/ruby/hyper-spec/spec/test_app/bin/setup old mode 100755 new mode 100644 diff --git a/ruby/hyper-spec/spec/test_app/config/application.rb b/ruby/hyper-spec/spec/test_app/config/application.rb index 6c87017c3..87e81cf44 100644 --- a/ruby/hyper-spec/spec/test_app/config/application.rb +++ b/ruby/hyper-spec/spec/test_app/config/application.rb @@ -26,7 +26,7 @@ class Application < Rails::Application config.assets.paths << ::Rails.root.join('app', 'models').to_s config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check = false + config.opal.arity_check_enabled = true config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true diff --git a/ruby/hyper-state/hyper-state.gemspec b/ruby/hyper-state/hyper-state.gemspec index 900f55aaa..1917a787a 100644 --- a/ruby/hyper-state/hyper-state.gemspec +++ b/ruby/hyper-state/hyper-state.gemspec @@ -9,39 +9,34 @@ Gem::Specification.new do |spec| spec.authors = ['Mitch VanDuyn', 'Adam Creekroad', 'Jan Biedermann'] spec.email = ['mitch@catprint.com', 'jan@kursator.com'] spec.summary = 'Flux Stores and more for Hyperloop' - spec.homepage = 'https://ruby-hyperloop.org' + spec.homepage = 'http://ruby-hyperstack.org' + spec.metadata = { 'documentation_uri' => 'https://docs.hyperstack.org/' } spec.license = 'MIT' - # spec.metadata = { - # "homepage_uri" => 'http://ruby-hyperloop.org', - # "source_code_uri" => 'https://github.com/ruby-hyperloop/hyper-component' - # } - spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(gemfiles|spec)/}) } spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] spec.add_dependency 'hyperstack-config', Hyperstack::State::VERSION - spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-component', Hyperstack::State::VERSION spec.add_development_dependency 'hyper-spec', Hyperstack::State::VERSION spec.add_development_dependency 'listen' - spec.add_development_dependency 'mini_racer', '~> 0.2.4' - spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' - spec.add_development_dependency 'pry-byebug' + # spec.add_development_dependency 'mini_racer', '< 0.4.0' # something is busted with 0.4.0 and its libv8-node dependency, '~> 0.2.4' + # spec.add_development_dependency 'opal-browser', '~> 0.2.0' + spec.add_development_dependency 'opal-rails' spec.add_development_dependency 'pry-rescue' - spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'pry-stack_explorer' + spec.add_development_dependency 'puma', '<= 5.4.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' spec.add_development_dependency 'rspec', '~> 3.7.0' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' - spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'rubocop' #, '~> 0.51.0' + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' - end diff --git a/ruby/hyper-state/lib/ext/object_space.rb b/ruby/hyper-state/lib/ext/object_space.rb new file mode 100644 index 000000000..1fed670ea --- /dev/null +++ b/ruby/hyper-state/lib/ext/object_space.rb @@ -0,0 +1,25 @@ +module ObjectSpace + def self.each_object(target_klass, &block) + klasses = [Object] + i = 0 + loop do + klass = klasses[i] + yield klass + names = `klass.$$const` && `Object.keys(klass.$$const)` + names.each do |name| + begin + k = klass.const_get(name) rescue nil + next unless `k.$$const` + next unless k.respond_to?(:is_a?) + next if klasses.include?(k) + + klasses << k if k.is_a? target_klass + rescue Exception => e + next + end + end if names + i += 1 + break if i >= klasses.length + end + end +end diff --git a/ruby/hyper-state/lib/hyper-state.rb b/ruby/hyper-state/lib/hyper-state.rb index 188382451..c5f3e3224 100644 --- a/ruby/hyper-state/lib/hyper-state.rb +++ b/ruby/hyper-state/lib/hyper-state.rb @@ -12,7 +12,9 @@ require 'hyperstack/state/observer' require 'hyperstack/state/version' -if RUBY_ENGINE != 'opal' +if RUBY_ENGINE == 'opal' + require 'ext/object_space' +else require 'opal' Opal.append_path(File.expand_path('../', __FILE__).untaint) end diff --git a/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb b/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb index 4e77b346a..3b1709c04 100644 --- a/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb +++ b/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb @@ -12,29 +12,30 @@ def self.included(base) def run_callback(name, *args) self.class.callbacks_for(name).flatten.each do |callback| - result = if callback.is_a?(Proc) - instance_exec(*args, &callback) - else - send(callback, *args) - end - args = yield(result) if block_given? + callback = method(callback) unless callback.is_a? Proc + args = self.class.send("_#{name}_before_call_hook", name, self, callback, *args) end args end module ClassMethods - def define_callback(callback_name, &after_define_hook) + def define_callback(callback_name, before_call_hook: nil, after_define_hook: nil) wrapper_name = "_#{callback_name}_callbacks" define_singleton_method(wrapper_name) do Context.set_var(self, "@#{wrapper_name}", force: true) { [] } end + before_call_hook ||= lambda do |_name, sself, proc, *args| + sself.instance_exec(*args, &proc) + args + end + define_singleton_method("_#{callback_name}_before_call_hook", &before_call_hook) define_singleton_method(callback_name) do |*args, &block| args << block if block_given? send(wrapper_name).push args Hotloader.when_file_updates do send(wrapper_name).delete_if { |item| item.equal? args } end - after_define_hook.call(*args, &block) if after_define_hook + after_define_hook.call(self) if after_define_hook end end diff --git a/ruby/hyper-state/lib/hyperstack/internal/state/variable.rb b/ruby/hyper-state/lib/hyperstack/internal/state/variable.rb index c39ad20a1..7d745c1bf 100644 --- a/ruby/hyper-state/lib/hyperstack/internal/state/variable.rb +++ b/ruby/hyper-state/lib/hyperstack/internal/state/variable.rb @@ -13,7 +13,9 @@ def get(obj, name) map_object[0] end - def set(obj, name, value) + def set(obj, name, value, _x = nil) + # _x is some legacy function, which I think queued up state changes to the end + # which is perhaps now the default. map_object = legacy_map[obj][name] map_object[0] = value Hyperstack::Internal::State::Mapper.mutated!(map_object.object_id) diff --git a/ruby/hyper-state/lib/hyperstack/state/observable.rb b/ruby/hyper-state/lib/hyperstack/state/observable.rb index 716600a8f..b623603de 100644 --- a/ruby/hyper-state/lib/hyperstack/state/observable.rb +++ b/ruby/hyper-state/lib/hyperstack/state/observable.rb @@ -12,13 +12,13 @@ def self.included(base) Internal::Receiver.mount(self, *args, &block) end base.send(:"define_#{kind}", :observe) do |*args, &block| - result = block && block.call || args.last + result = block ? block.call : args.last Internal::State::Mapper.observed! self result end base.send(:"define_#{kind}", :mutate) do |*args, &block| # any args will be ignored thus allowing us to say `mutate @foo = 123, @bar[:x] = 7` etc - result = block && block.call || args.last + result = block ? block.call : args.last Internal::State::Mapper.mutated! self result end @@ -97,6 +97,28 @@ def self.included(base) state_writer(*names) end end + # receives is defined at the class an instance level above, but for + # convenience we also define it as singleton_method on the singleton class allowing this: + # class Foo + # class << self + # receives ... + # end + # end + base.singleton_class.define_singleton_method(:receives) do |*args, &block| + Internal::Receiver.mount(base, *args, &block) + end + + unless base.respond_to? :__hyperstack_state_observer_included + base.receives Hyperstack::Application::Boot do + ObjectSpace.each_object(Class) do |klass| + next unless klass <= base + next unless klass.respond_to?(:initialize) + next unless klass.method(:initialize).arity.zero? + klass.initialize + end + end + base.singleton_class.attr_reader :__hyperstack_state_observer_included + end end end end diff --git a/ruby/hyper-state/lib/hyperstack/state/version.rb b/ruby/hyper-state/lib/hyperstack/state/version.rb index dec3f80ac..b16fd1ced 100644 --- a/ruby/hyper-state/lib/hyperstack/state/version.rb +++ b/ruby/hyper-state/lib/hyperstack/state/version.rb @@ -1,5 +1,5 @@ module Hyperstack module State - VERSION = '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' end end diff --git a/ruby/hyper-state/spec/api/class_initialize_spec.rb b/ruby/hyper-state/spec/api/class_initialize_spec.rb new file mode 100644 index 000000000..72b349099 --- /dev/null +++ b/ruby/hyper-state/spec/api/class_initialize_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe 'calling class initialize method', :js do + before(:each) do + isomorphic do + class ClassStore + include Hyperstack::State::Observable + class << self + attr_reader :initialized + def initialize + @initialized = !@initialized && self + end + end + end + + class ClassSubStore < ClassStore + class << self + attr_reader :initialized + def initialize + @initialized = !@initialized && self + end + end + end + + class ClassSubNoInit < ClassStore + end + + class SubGumStore < ClassStore + include Hyperstack::State::Observable + class << self + attr_reader :initialized + def initialize + @initialized = !@initialized && self + end + end + end + end + end + + it "the store will receive a class level initialize (client)" do + expect { ClassStore.initialized }.on_client_to eq(ClassStore.to_s) + expect { ClassSubStore.initialized }.on_client_to eq(ClassSubStore.to_s) + expect { SubGumStore.initialized }.on_client_to eq(SubGumStore.to_s) + end + + it "the store will receive a class level initialize (server)" do + Hyperstack::Application::Boot.run + + expect(ClassStore.initialized).to eq(ClassStore) + expect(ClassSubStore.initialized).to eq(ClassSubStore) + expect(SubGumStore.initialized).to eq(SubGumStore) + end +end diff --git a/ruby/hyper-state/spec/api/observable_spec.rb b/ruby/hyper-state/spec/api/observable_spec.rb index 855d8ee54..6a473b130 100644 --- a/ruby/hyper-state/spec/api/observable_spec.rb +++ b/ruby/hyper-state/spec/api/observable_spec.rb @@ -14,6 +14,11 @@ class Store store.instance_eval { @var = 12 } expect(store.instance_eval { observe { @var } }).to eq(12) end + it "can be passed a block that returns a falsy value" do + expect(Hyperstack::Internal::State::Mapper).to receive(:observed!).with(store) + store.instance_eval { @var = false } + expect(store.instance_eval { observe { @var } }).to eq(false) + end it "can be passed args" do expect(Hyperstack::Internal::State::Mapper).to receive(:observed!).with(store) store.instance_eval { @var = 12 } @@ -30,6 +35,10 @@ class Store expect(Hyperstack::Internal::State::Mapper).to receive(:mutated!).with(store) expect(store.instance_eval { mutate { @var = 12 } }).to eq(12) end + it "can be passed a block that returns a falsy value" do + expect(Hyperstack::Internal::State::Mapper).to receive(:mutated!).with(store) + expect(store.instance_eval { mutate { @var = false } }).to eq(false) + end it "can be passed args" do expect(Hyperstack::Internal::State::Mapper).to receive(:mutated!).with(store) expect(store.instance_eval { mutate @other_var = 13, @var = 12 }).to eq(12) diff --git a/ruby/hyper-state/spec/api/receives_spec.rb b/ruby/hyper-state/spec/api/receives_spec.rb index 4fb2d244c..7cd1fb8f5 100644 --- a/ruby/hyper-state/spec/api/receives_spec.rb +++ b/ruby/hyper-state/spec/api/receives_spec.rb @@ -43,4 +43,11 @@ class Store expect(broadcaster).not_to receive(:on_dispatch) store.receives(broadcaster) end + + it "will attach receives to the singleton class" do + broadcaster = double('Broadcaster') + expect(store.class).to receive(:proc_called).with([1, 2, 3]).once + allow(broadcaster).to receive(:on_dispatch) { |&block| block.call([1, 2, 3])} + Store.singleton_class.receives broadcaster, :proc_called + end end diff --git a/ruby/hyper-state/spec/internal/callbacks_spec.rb b/ruby/hyper-state/spec/internal/callbacks_spec.rb index 21f1dbbd1..04f3d15d6 100644 --- a/ruby/hyper-state/spec/internal/callbacks_spec.rb +++ b/ruby/hyper-state/spec/internal/callbacks_spec.rb @@ -25,48 +25,64 @@ class Foo define_callback :before_dinner before_dinner :wash_hands, :turn_off_laptop - def wash_hands;end - def turn_off_laptop;end + attr_reader :washed_hands + attr_reader :turned_off_laptop + + def wash_hands(*args) + @washed_hands = args + end + def turn_off_laptop(*args) + @turned_off_laptop = args + end end end - expect_evaluate_ruby do - instance = Foo.new - [ instance.respond_to?(:wash_hands), - instance.respond_to?(:turn_off_laptop), - instance.run_callback(:before_dinner, 1, 2, 3) ] - end.to eq([true, true, [1, 2, 3]]) + evaluate_ruby { @instance = Foo.new } + expect { @instance.respond_to?(:wash_hands) }.on_client_to be_truthy + expect { @instance.respond_to?(:turn_off_laptop) }.on_client_to be_truthy + expect { @instance.run_callback(:before_dinner, 1, 2, 3) }.on_client_to eq [1, 2, 3] + expect { @instance.washed_hands }.on_client_to eq [1, 2, 3] + expect { @instance.turned_off_laptop }.on_client_to eq [1, 2, 3] end context 'using Hyperloop::Context.reset!' do - #after(:all) do - # Hyperloop::Context.instance_variable_set(:@context, nil) - #end it 'clears callbacks on Hyperloop::Context.reset!' do on_client do Hyperstack::Context.reset! - class Foo include Hyperstack::Internal::Callbacks define_callback :before_dinner - before_dinner :wash_hands, :turn_off_laptop - def wash_hands;end + attr_reader :washed_hands + attr_reader :turned_off_laptop - def turn_off_laptop;end + def wash_hands(*args) + @washed_hands = args + end + def turn_off_laptop(*args) + @turned_off_laptop = args + end end end - expect_evaluate_ruby do - instance = Foo.new - + evaluate_ruby { @instance = Foo.new } + expect { @instance.run_callback(:before_dinner, 1, 2, 3) }.on_client_to eq [1, 2, 3] + expect { @instance.washed_hands }.on_client_to eq [1, 2, 3] + expect { @instance.turned_off_laptop }.on_client_to eq [1, 2, 3] + evaluate_ruby do + @instance = Foo.new Hyperstack::Context.reset! - + end + expect { @instance.run_callback(:before_dinner, 1, 2, 3) }.on_client_to eq [1, 2, 3] + expect { @instance.washed_hands }.on_client_to be_nil + expect { @instance.turned_off_laptop }.on_client_to be_nil + evaluate_ruby do Foo.class_eval do before_dinner :wash_hands end - - instance.run_callback(:before_dinner, 1, 2, 3) - end.to eq([1, 2, 3]) + end + expect { @instance.run_callback(:before_dinner, 4, 5) }.on_client_to eq [4, 5] + expect { @instance.washed_hands }.on_client_to eq [4, 5] + expect { @instance.turned_off_laptop }.on_client_to be_nil end end diff --git a/ruby/hyper-state/spec/spec_helper.rb b/ruby/hyper-state/spec/spec_helper.rb index f3e8c2480..c8a23b985 100644 --- a/ruby/hyper-state/spec/spec_helper.rb +++ b/ruby/hyper-state/spec/spec_helper.rb @@ -9,7 +9,6 @@ require 'timecop' require 'hyper-spec' require 'hyper-component' -#require 'hyper-store' require 'hyper-state' diff --git a/ruby/hyper-state/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-state/spec/test_app/app/assets/javascripts/application.js index c68d83120..cfbe1d9be 100644 --- a/ruby/hyper-state/spec/test_app/app/assets/javascripts/application.js +++ b/ruby/hyper-state/spec/test_app/app/assets/javascripts/application.js @@ -1,4 +1,9 @@ -//= require 'react' +//= require 'react' //= require 'react_ujs' //= require 'components' -Opal.load('components'); +if (typeof(OpalLoaded)=='undefined') { + Opal.load('components'); +} else { + Opal.loaded(OpalLoaded || []); + Opal.require('components'); +} diff --git a/ruby/hyper-state/spec/test_app/config/application.rb b/ruby/hyper-state/spec/test_app/config/application.rb index 3e63522da..43f4c8d39 100644 --- a/ruby/hyper-state/spec/test_app/config/application.rb +++ b/ruby/hyper-state/spec/test_app/config/application.rb @@ -10,6 +10,7 @@ module TestApp class Application < Rails::Application + config.opal.arity_check_enabled = true # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. diff --git a/ruby/hyper-store/hyper-store.gemspec b/ruby/hyper-store/hyper-store.gemspec index a1955b58a..7fb885a73 100644 --- a/ruby/hyper-store/hyper-store.gemspec +++ b/ruby/hyper-store/hyper-store.gemspec @@ -9,40 +9,36 @@ Gem::Specification.new do |spec| spec.authors = ['Mitch VanDuyn', 'Adam Creekroad', 'Jan Biedermann'] spec.email = ['mitch@catprint.com', 'jan@kursator.com'] spec.summary = 'Flux Stores and more for Hyperloop' - spec.homepage = 'https://ruby-hyperloop.org' + spec.homepage = 'http://ruby-hyperstack.org' + spec.metadata = { 'documentation_uri' => 'https://docs.hyperstack.org/' } spec.license = 'MIT' - # spec.metadata = { - # "homepage_uri" => 'http://ruby-hyperloop.org', - # "source_code_uri" => 'https://github.com/ruby-hyperloop/hyper-component' - # } - spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(gemfiles|spec)/}) } spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_dependency 'hyperstack-config', Hyperstack::Legacy::Store::VERSION spec.add_dependency 'hyper-state', Hyperstack::Legacy::Store::VERSION - spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_dependency 'hyperstack-config', Hyperstack::Legacy::Store::VERSION + + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-component', Hyperstack::Legacy::Store::VERSION spec.add_development_dependency 'hyper-spec', Hyperstack::Legacy::Store::VERSION spec.add_development_dependency 'listen' - spec.add_development_dependency 'mini_racer', '~> 0.2.6' + # spec.add_development_dependency 'mini_racer', '< 0.4.0' # something is busted with 0.4.0 and its libv8-node dependency, '~> 0.2.6' spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' - spec.add_development_dependency 'pry-byebug' + spec.add_development_dependency 'opal-rails' spec.add_development_dependency 'pry-rescue' - spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'pry-stack_explorer' + spec.add_development_dependency 'puma', '<= 5.4.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' spec.add_development_dependency 'rspec', '~> 3.7.0' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' - spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'rubocop' #, '~> 0.51.0' + spec.add_development_dependency 'sqlite3', '~> 1.4.2' spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyper-store/lib/hyperstack/legacy/store/version.rb b/ruby/hyper-store/lib/hyperstack/legacy/store/version.rb index f139568a0..b6ba5bb77 100644 --- a/ruby/hyper-store/lib/hyperstack/legacy/store/version.rb +++ b/ruby/hyper-store/lib/hyperstack/legacy/store/version.rb @@ -1,7 +1,7 @@ module Hyperstack module Legacy module Store - VERSION = '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' end end end diff --git a/ruby/hyper-store/spec/hyper-store/reset_context_spec.rb b/ruby/hyper-store/spec/hyper-store/reset_context_spec.rb index 76cd93b84..8161d2dd0 100644 --- a/ruby/hyper-store/spec/hyper-store/reset_context_spec.rb +++ b/ruby/hyper-store/spec/hyper-store/reset_context_spec.rb @@ -3,7 +3,7 @@ describe "resetting contexts" do it "does not reset any predefined boot receivers", js: true do - on_client do + before_mount do class Store include Hyperstack::Legacy::Store class << self diff --git a/ruby/hyper-store/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-store/spec/test_app/app/assets/javascripts/application.js index c68d83120..cfbe1d9be 100644 --- a/ruby/hyper-store/spec/test_app/app/assets/javascripts/application.js +++ b/ruby/hyper-store/spec/test_app/app/assets/javascripts/application.js @@ -1,4 +1,9 @@ -//= require 'react' +//= require 'react' //= require 'react_ujs' //= require 'components' -Opal.load('components'); +if (typeof(OpalLoaded)=='undefined') { + Opal.load('components'); +} else { + Opal.loaded(OpalLoaded || []); + Opal.require('components'); +} diff --git a/ruby/hyper-trace/Gemfile b/ruby/hyper-trace/Gemfile index ac679c0a8..3ef41b3dd 100644 --- a/ruby/hyper-trace/Gemfile +++ b/ruby/hyper-trace/Gemfile @@ -1,4 +1,5 @@ source 'https://rubygems.org' gem 'hyperstack-config', path: '../hyperstack-config' +gem 'hyper-spec', path: '../hyper-spec' # Specify your gem's dependencies in hyper-trace.gemspec gemspec diff --git a/ruby/hyper-trace/hyper-trace.gemspec b/ruby/hyper-trace/hyper-trace.gemspec index f15f87b0e..0be02035e 100644 --- a/ruby/hyper-trace/hyper-trace.gemspec +++ b/ruby/hyper-trace/hyper-trace.gemspec @@ -10,7 +10,8 @@ Gem::Specification.new do |spec| spec.email = ["mitch@catprint.com"] spec.summary = %q{Method tracing and conditional breakpoints for Opal Ruby} - spec.homepage = "https://github.com/reactrb/hyper-trace" + spec.homepage = 'http://ruby-hyperstack.org' + spec.metadata = { 'documentation_uri' => 'https://docs.hyperstack.org/' } spec.license = "MIT" spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } @@ -19,7 +20,8 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency 'hyperstack-config', HyperTrace::VERSION - spec.add_development_dependency "bundler", ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'hyper-spec', HyperTrace::VERSION + spec.add_development_dependency "bundler" spec.add_development_dependency 'chromedriver-helper' - spec.add_development_dependency "rake", "~> 10.0" + spec.add_development_dependency "rake" end diff --git a/ruby/hyper-trace/lib/hyper_trace/hyper_trace.rb b/ruby/hyper-trace/lib/hyper_trace/hyper_trace.rb index 2bf6ec316..134437a0e 100644 --- a/ruby/hyper-trace/lib/hyper_trace/hyper_trace.rb +++ b/ruby/hyper-trace/lib/hyper_trace/hyper_trace.rb @@ -6,17 +6,7 @@ def hyper_trace(*args, &block) alias hypertrace hyper_trace end -class Method - def parameters - /.*function[^(]*\(([^)]*)\)/ - .match(`#{@method}.toString()`)[1] - .split(',') - .collect { |param| [:req, param.strip.to_sym] } - end -end - module HyperTrace - class Config def initialize(klass, instrument_class, opts, &block) @klass = klass @@ -116,15 +106,11 @@ def exclusions def instrumentation_off(config) if config.instrument_class? config.klass.methods.grep(/^__hyper_trace_pre_.+$/).each do |method| - config.klass.class_eval do - class << self - alias_method method.gsub(/^__hyper_trace_pre_/, ''), method - end - end + config.klass.singleton_class.alias_method method.gsub(/^__hyper_trace_pre_/, ''), method end else config.klass.instance_methods.grep(/^__hyper_trace_pre_.+$/).each do |method| - config.klass.class_eval { alias_method method.gsub(/^__hyper_trace_pre_/, ''), method } + config.klass.alias_method method.gsub(/^__hyper_trace_pre_/, ''), method end end end @@ -145,17 +131,12 @@ def all_methods(config) def instrument_method(method, config) if config.instrument_class? - config.klass.class_eval do - class << self - alias_method "__hyper_trace_pre_#{method}", method unless method_defined? "__pre_hyper_trace_#{method}" - end + unless config.klass.singleton_class.method_defined? "__pre_hyper_trace_#{method}" # is this right? + config.klass.singleton_class.alias_method "__hyper_trace_pre_#{method}", method end - add_hyper_trace_method(method, config) else unless config.klass.method_defined? "__pre_hyper_trace_#{method}" - config.klass.class_eval do - alias_method "__hyper_trace_pre_#{method}", method - end + config.klass.alias_method "__hyper_trace_pre_#{method}", method end end add_hyper_trace_method(method, config) @@ -194,28 +175,77 @@ def instance_tag(instance, prefix = ' - ') end end + def required(arity, actual_length) + if arity.negative? + req_count = - arity - 1 + raise ArgumentError, "missing required arguments: #{actual_length} for #{req_count}" if req_count > actual_length + else + req_count = arity + raise ArgumentError, "incorrect number of arguments, expected #{req_count} got #{actual_length}" if req_count != actual_length + end + req_count + end + + # [["req", "x"], ["opt", "y"], ["rest"], ["keyreq", "z"], ["key", "opt"], ["block", "flop"]] + + def map_parameters(instance, name, actual, format = true) + method = instance.method("__hyper_trace_pre_#{name}") + map_with_specs(method, actual.dup, format) || map_without_specs(actual, format) + end + + def key?(actual, key) + actual&.last&.respond_to?(:key?) && actual.last.key?(key) + end + + def map_with_specs(method, actual, show_blank_rest) + specs = method.parameters + req_count = required(method.arity, actual.length) + args = {} + + specs.each do |type, key| + case type + when :req + args[key] = actual.shift + req_count -= 1 + when :opt + args[key] = actual.shift if actual.length > req_count + when (key || show_blank_rest) && :rest + args[key || '*'] = [*actual[0..-req_count - 1]] + when :keyreq + raise ArgumentError, "missing keyword argument: #{key}" unless key?(actual, key) + + args[key] = actual.last[key] + when key?(actual, key) && :key + args[key] = actual.last[key] + end + end + args + rescue ArgumentError, Interrupt, SignalException => e + raise e + rescue Exception => e + nil + end + + def map_without_specs(actual, format) + args = {} + prefix = 'p' unless format + actual.each_with_index { |value, i| args["#{prefix}#{i + 1}"] = value } + args + end + def format_head(instance, name, args, &block) @formatting = true - method = instance.method("__hyper_trace_pre_#{name}") if args.any? group(" #{name}(...)#{instance_tag(instance)}") do - params = method.parameters - group("args:", collapsed: true) do - params.each_with_index do |param_spec, i| - arg_name = param_spec[1] - if arg_name == '$a_rest' - arg_name = '*' - arg = args[i..-1] - else - arg = args[i] - end - if safe_i(arg).length > 30 || show_js_object(arg) - group "#{arg_name}: #{safe_s(arg)}"[0..29], collapsed: true do - puts safe_i(arg) - log arg if show_js_object(arg) + group("args: (#{args.length})", collapsed: true) do + map_parameters(instance, name, args).each do |arg, value| + if safe_i(value).length > 30 || show_js_object(value) + group "#{arg}: #{safe_s(value)}"[0..29], collapsed: true do + puts safe_i(value) + log value if show_js_object(value) end else - group "#{arg_name}: #{safe_i(arg)}" + group "#{arg}: #{safe_i(value)}" end end end @@ -286,7 +316,10 @@ def format_exception(result) @formatting = true if safe_i(result).length > 40 group "raised: #{safe_s(result)}"[0..40], collapsed: true do - puts safe_i(result) + puts result.backtrace[0] + result.backtrace[1..-1].each do |line| + log line + end end else group "raised: #{safe_i(result)}" @@ -305,24 +338,23 @@ def should_break?(location, config, name, args, instance, result) def breakpoint(location, config, name, args, instance, result = nil) if should_break? location, config, name, args, instance, result - method = instance.method("__hyper_trace_pre_#{name}") + params = map_parameters(instance, name, args, false) fn_def = ['RESULT'] - fn_def += method.parameters.collect { |p| p[1] } + fn_def += params.keys fn_def += ["//break on #{location} of #{name}\nvar self = this;\ndebugger;\n;"] puts "break on #{location} of #{name}" fn = `Function.apply(#{self}, #{fn_def}).bind(#{instance})` - fn.call(result, *args) + fn.call(result, *params.values) end end - def call_original(instance, method, *args, &block) + def call_original(instance, method, args, &block) @formatting = false instance.send "__hyper_trace_pre_#{method}", *args, &block ensure @formatting = true end - def add_hyper_trace_method(method, config) def_method = config.instrument_class? ? :define_singleton_method : :define_method config.klass.send(def_method, method) do |*args, &block| @@ -330,7 +362,7 @@ def add_hyper_trace_method(method, config) if HyperTrace.formatting? begin send "__hyper_trace_pre_#{method}", *args, &block - rescue Exception + rescue StandardError "???" end else @@ -338,11 +370,13 @@ def add_hyper_trace_method(method, config) HyperTrace.format_head(self, method, args) do HyperTrace.format_instance_internal(self) HyperTrace.breakpoint(:enter, config, method, args, self) - result = HyperTrace.call_original self, method, *args, &block + result = HyperTrace.call_original self, method, args, &block HyperTrace.format_result(result) HyperTrace.breakpoint(:exit, config, method, args, self, result) result end + rescue Interrupt, SignalException => e + raise e rescue Exception => e HyperTrace.format_exception(e) debugger unless HyperTrace.exclusions[self.class][:rescue].include? :method diff --git a/ruby/hyper-trace/lib/hyper_trace/version.rb b/ruby/hyper-trace/lib/hyper_trace/version.rb index 3de30d0ba..dff9a4eee 100644 --- a/ruby/hyper-trace/lib/hyper_trace/version.rb +++ b/ruby/hyper-trace/lib/hyper_trace/version.rb @@ -1,3 +1,3 @@ module HyperTrace - VERSION = '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' end diff --git a/ruby/hyperstack-config/Gemfile b/ruby/hyperstack-config/Gemfile index f2521bf0c..7b292c5a3 100644 --- a/ruby/hyperstack-config/Gemfile +++ b/ruby/hyperstack-config/Gemfile @@ -1,3 +1,4 @@ source 'https://rubygems.org' gem 'hyper-spec', path: '../hyper-spec' +# gem 'opal-browser', git: 'https://github.com/opal/opal-browser' gemspec diff --git a/ruby/hyperstack-config/hyperstack-config.gemspec b/ruby/hyperstack-config/hyperstack-config.gemspec index 492126df1..584a25fd2 100644 --- a/ruby/hyperstack-config/hyperstack-config.gemspec +++ b/ruby/hyperstack-config/hyperstack-config.gemspec @@ -8,8 +8,9 @@ Gem::Specification.new do |spec| spec.version = Hyperstack::Config::VERSION spec.authors = ['Mitch VanDuyn', 'Jan Biedermann'] spec.email = ['mitch@catprint.com', 'jan@kursator.com'] - spec.summary = %q{Provides a single point configuration module for hyperstack gems} + spec.summary = 'Provides a single point configuration module for hyperstack gems' spec.homepage = 'http://ruby-hyperstack.org' + spec.metadata = { 'documentation_uri' => 'https://docs.hyperstack.org/' } spec.license = 'MIT' # spec.metadata = { # "homepage_uri" => 'http://ruby-hyperstack.org', @@ -17,28 +18,27 @@ Gem::Specification.new do |spec| # } spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - #spec.bindir = 'exe' - #spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables << 'hyperstack-hotloader' spec.require_paths = ['lib'] - spec.add_dependency 'listen', '~> 3.0' # for hot loader - spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' - spec.add_dependency 'opal-browser', '~> 0.2.0' + spec.add_dependency 'listen', '~> 3.0' # for hot loader + # spec.add_dependency 'mini_racer', '~> 0.2.6' + spec.add_dependency 'opal', ENV['OPAL_VERSION'] || '>= 0.11.0', '< 1.1' + spec.add_dependency 'opal-browser' # this is needed everywhere else so its loaded here spec.add_dependency 'uglifier' spec.add_dependency 'websocket' # for hot loader - - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' - spec.add_development_dependency 'pry' - spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'opal-rails' #, '>= 0.9.4', '< 2.0' + spec.add_development_dependency 'pry-rescue' + spec.add_development_dependency 'pry-stack_explorer' + spec.add_development_dependency 'puma', '<= 5.4.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec', '~> 3.7.0' - spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'rspec-rails' + spec.add_development_dependency 'rubocop' #, '~> 0.51.0' + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyperstack-config/lib/hyperstack-config.rb b/ruby/hyperstack-config/lib/hyperstack-config.rb index b8437736c..f7bca1555 100644 --- a/ruby/hyperstack-config/lib/hyperstack-config.rb +++ b/ruby/hyperstack-config/lib/hyperstack-config.rb @@ -5,6 +5,7 @@ def self.naming_convention end end if RUBY_ENGINE == 'opal' + require 'hyperstack/native_wrapper_compatibility' # needs to be early... require 'hyperstack/deprecation_warning' require 'hyperstack/string' require 'hyperstack/client_stubs' @@ -14,9 +15,14 @@ def self.naming_convention require 'hyperstack/active_support_string_inquirer.rb' require 'hyperstack_env' require 'hyperstack/hotloader/stub' + require 'promise' + # uncommenting these lines breaks prerendering + # require 'opal-browser' else require 'opal' + # because promises and features in opal-browsers are used everywhere we load them here require 'opal-browser' + # We need opal-rails to be loaded for Gem code to be properly included by sprockets. begin require 'opal-rails' if defined? Rails @@ -40,7 +46,13 @@ def self.naming_convention Hyperstack.define_setting :hotloader_ping, nil Hyperstack.define_setting :hotloader_ignore_callback_mapping, false Hyperstack.import 'opal', gem: true + + # because promises and features in opal-browsers are used everywhere we load them here + Hyperstack.import 'promise', client_only: true Hyperstack.import 'browser', client_only: true + Hyperstack.import 'browser/delay', client_only: true + Hyperstack.import 'browser/interval', client_only: true + Hyperstack.import 'hyperstack-config', gem: true Hyperstack.import 'hyperstack/autoloader' Hyperstack.import 'hyperstack/autoloader_starter' diff --git a/ruby/hyperstack-config/lib/hyperstack-loader.js b/ruby/hyperstack-config/lib/hyperstack-loader.js index 6c91117c1..842fd49d0 100644 --- a/ruby/hyperstack-config/lib/hyperstack-loader.js +++ b/ruby/hyperstack-config/lib/hyperstack-loader.js @@ -1,6 +1,8 @@ //= require hyperstack-loader-system-code //= require hyperstack-loader-application //= require hyperstack-hotloader-config -Opal.load('hyperstack-loader-system-code') -Opal.load('hyperstack-loader-application') +Opal.loaded(OpalLoaded || []) +Opal.require('hyperstack-loader-system-code') +Opal.loaded(OpalLoaded || []) +Opal.require('hyperstack-loader-application') Hyperstack.hotloader(Hyperstack.hotloader.port, Hyperstack.hotloader.ping) diff --git a/ruby/hyperstack-config/lib/hyperstack-prerender-loader.js b/ruby/hyperstack-config/lib/hyperstack-prerender-loader.js index 7747f2b47..a8d8f1f7a 100644 --- a/ruby/hyperstack-config/lib/hyperstack-prerender-loader.js +++ b/ruby/hyperstack-config/lib/hyperstack-prerender-loader.js @@ -1,4 +1,6 @@ //= require hyperstack-prerender-loader-system-code //= require hyperstack-prerender-loader-application -Opal.load('hyperstack-prerender-loader-system-code') -Opal.load('hyperstack-prerender-loader-application') +Opal.loaded(OpalLoaded || []) +Opal.require('hyperstack-prerender-loader-system-code') +Opal.loaded(OpalLoaded || []) +Opal.require('hyperstack-prerender-loader-application') diff --git a/ruby/hyperstack-config/lib/hyperstack/config/version.rb b/ruby/hyperstack-config/lib/hyperstack/config/version.rb index 25f21801a..923640f12 100644 --- a/ruby/hyperstack-config/lib/hyperstack/config/version.rb +++ b/ruby/hyperstack-config/lib/hyperstack/config/version.rb @@ -1,5 +1,5 @@ module Hyperstack module Config - VERSION = '1.0.alpha1.5' + VERSION = '1.0.alpha1.8' end end diff --git a/ruby/hyperstack-config/lib/hyperstack/hotloader/socket.rb b/ruby/hyperstack-config/lib/hyperstack/hotloader/socket.rb index 50b06d5e2..4f0644ef1 100644 --- a/ruby/hyperstack-config/lib/hyperstack/hotloader/socket.rb +++ b/ruby/hyperstack-config/lib/hyperstack/hotloader/socket.rb @@ -16,7 +16,7 @@ def self.supported? Browser.supports? :WebSocket end - include Native + include Native::Wrapper include IO::Writable def on(str, &block) diff --git a/ruby/hyperstack-config/lib/hyperstack/imports.rb b/ruby/hyperstack-config/lib/hyperstack/imports.rb index d129f4020..bbcad2745 100644 --- a/ruby/hyperstack-config/lib/hyperstack/imports.rb +++ b/ruby/hyperstack-config/lib/hyperstack/imports.rb @@ -1,6 +1,22 @@ module Hyperstack class << self + + # redefine this method ito avoid logging imports. + # typically in rspec - for example: + # + # if config.formatters.empty? + # module Hyperstack + # def self.log_import(s) + # # turn off import logging + # end + # end + # end + + def log_import(s) + puts s + end + def import_list @import_list ||= [] end @@ -52,16 +68,24 @@ def add_inflections(sys) return [] unless sys ["puts \"require 'config/initializers/inflections.rb'\""] + File.open(Rails.root.join('config', 'initializers', 'inflections.rb'), &:readlines).tap do - puts " require 'config/initializers/inflections.rb'" + log_import " require 'config/initializers/inflections.rb'" end rescue Errno::ENOENT [] end + def add_opal(sys) + return [] unless sys + + log_import " require 'opal'" + ["require 'opal'; puts \"require 'opal'\""] + end + def generate_requires(mode, sys, file) handle_webpack - (import_list.collect do |value, cancelled, render_on_server, render_on_client, kind| + (add_opal(sys) + import_list.collect do |value, cancelled, render_on_server, render_on_client, kind| next if cancelled + next if value == 'opal' next if (sys && kind == :tree) || (!sys && kind != :tree) next if mode == :client && !render_on_client next if mode == :server && !render_on_server @@ -69,7 +93,7 @@ def generate_requires(mode, sys, file) generate_require_tree(value, render_on_server, render_on_client) elsif kind == :gem r = "require '#{value}' #{client_guard(render_on_server, render_on_client)}" - puts " #{r}" + log_import " #{r}" "puts \"#{r}\"; #{r}" else generate_directive(:require, value, file, render_on_server, render_on_client) @@ -85,7 +109,7 @@ def generate_directive(directive, to, file, render_on_server, render_on_client) comp_path.shift end r = "#{directive} '#{(['.'] + ['..'] * gem_path.length + comp_path).join('/')}' #{client_guard(render_on_server, render_on_client)}" - puts " #{r}" + log_import " #{r}" "puts \"#{r}\"; #{r}" end @@ -97,7 +121,7 @@ def generate_require_tree(path, render_on_server, render_on_client) if fname =~ /(\.js$)|(\.rb$)|(\.jsx$)/ fname = fname.gsub(/(\.js$)|(\.rb$)|(\.jsx$)/, '') r = "require '#{fname}' #{client_guard(render_on_server, render_on_client)}" - puts " #{r}" + log_import " #{r}" "puts \"#{r}\"; #{r}" end end.compact.join("\n") diff --git a/ruby/hyperstack-config/lib/hyperstack/native_wrapper_compatibility.rb b/ruby/hyperstack-config/lib/hyperstack/native_wrapper_compatibility.rb new file mode 100644 index 000000000..b8dc25507 --- /dev/null +++ b/ruby/hyperstack-config/lib/hyperstack/native_wrapper_compatibility.rb @@ -0,0 +1,12 @@ +# allows hyperstack to include Native::Wrapper even if running Opal 0.11 +module Native + module Wrapper + def self.includedx(klass) + if Native.instance_methods.include? :to_n + klass.include Native + else + klass.extend Native::Helpers + end + end + end +end diff --git a/ruby/hyperstack-config/lib/hyperstack/rail_tie.rb b/ruby/hyperstack-config/lib/hyperstack/rail_tie.rb index 0b12036e5..acb24120c 100644 --- a/ruby/hyperstack-config/lib/hyperstack/rail_tie.rb +++ b/ruby/hyperstack-config/lib/hyperstack/rail_tie.rb @@ -17,7 +17,7 @@ def delete_first(a, e) def auto_config=(on) Rails.configuration.tap do |config| if [:on, 'on', true].include?(on) - + config.filter_parameters << :hyperstack_secured_json config.eager_load_paths += %W(#{config.root}/app/hyperstack/models) diff --git a/ruby/hyperstack-config/spec/test_app/app/assets/config/manifest.js b/ruby/hyperstack-config/spec/test_app/app/assets/config/manifest.js new file mode 100644 index 000000000..6f71952ea --- /dev/null +++ b/ruby/hyperstack-config/spec/test_app/app/assets/config/manifest.js @@ -0,0 +1 @@ + //= link_directory ../javascripts .js diff --git a/ruby/hyperstack-config/spec/test_app/app/controllers/application_controller.rb b/ruby/hyperstack-config/spec/test_app/app/controllers/application_controller.rb new file mode 100644 index 000000000..09705d12a --- /dev/null +++ b/ruby/hyperstack-config/spec/test_app/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/ruby/hyperstack-config/spec/test_app/app/controllers/test_controller.rb b/ruby/hyperstack-config/spec/test_app/app/controllers/test_controller.rb index f9a0379e2..00ae271ac 100644 --- a/ruby/hyperstack-config/spec/test_app/app/controllers/test_controller.rb +++ b/ruby/hyperstack-config/spec/test_app/app/controllers/test_controller.rb @@ -1,4 +1,4 @@ -class TestController < ActionController::Base +class TestController < ApplicationController def app render inline: 'hello', :layout => "application" end diff --git a/ruby/hyperstack-config/spec/test_app/config/application.rb b/ruby/hyperstack-config/spec/test_app/config/application.rb index 4d3cbf81e..f59d088bc 100644 --- a/ruby/hyperstack-config/spec/test_app/config/application.rb +++ b/ruby/hyperstack-config/spec/test_app/config/application.rb @@ -7,6 +7,7 @@ module TestApp class Application < Rails::Application + config.opal.arity_check_enabled = true # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. diff --git a/ruby/rails-hyperstack/.gitignore b/ruby/rails-hyperstack/.gitignore index 9abccdc3d..24bb217df 100644 --- a/ruby/rails-hyperstack/.gitignore +++ b/ruby/rails-hyperstack/.gitignore @@ -11,6 +11,7 @@ capybara-*.html **.orig rerun.txt pickle-email-*.html +TestApp/ # TODO Comment out these rules if you are OK with secrets being uploaded to the repo config/initializers/secret_token.rb @@ -50,3 +51,8 @@ bower.json # ignore Gemfile.locks https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/ /spec/test_app/Gemfile.lock /Gemfile.lock + +node_modules +package.json +spec/test_app +yarn.lock diff --git a/ruby/rails-hyperstack/Gemfile b/ruby/rails-hyperstack/Gemfile index 01a0028ac..bb2b68734 100644 --- a/ruby/rails-hyperstack/Gemfile +++ b/ruby/rails-hyperstack/Gemfile @@ -6,5 +6,7 @@ gem 'hyper-operation', path: '../hyper-operation' gem 'hyper-model', path: '../hyper-model' gem 'hyper-router', path: '../hyper-router' gem 'hyper-spec', path: '../hyper-spec' -gem 'rails', '~> 5.2' +gem 'webpacker' # TODO: figure out why these two are necessary! +gem 'turbolinks' +gem "selenium-webdriver", '3.142.7' gemspec diff --git a/ruby/rails-hyperstack/Rakefile b/ruby/rails-hyperstack/Rakefile index 845a68c72..9e31d53fd 100644 --- a/ruby/rails-hyperstack/Rakefile +++ b/ruby/rails-hyperstack/Rakefile @@ -1,25 +1,39 @@ require "bundler/gem_tasks" require "rspec/core/rake_task" +require 'pry' RSpec::Core::RakeTask.new(:spec) namespace :spec do task :prepare do + rails_version = `bundle info rails`.match(/\* rails \((.+)\)/)[1] + opal_version = `bundle info opal`.match(/\* opal \((.+)\)/)[1] Dir.chdir('spec') do sh('rm -rf test_app') - sh('bundle exec rails new test_app') - Dir.chdir('test_app') do - sh('bundle exec rails g hyperstack:install') - sh('mv Gemfile _Gemfile_') - sh('bundle exec rails generate model Sample name:string description:text') - sh('mv app/models/sample.rb app/hyperstack/models/sample.rb') - sh('bundle exec rake db:migrate') - sh('bundle exec rails dev:cache') + Bundler.with_unbundled_env do + sh("rails _#{rails_version}_ new test_app -T") + end + Bundler.with_unbundled_env do + Dir.chdir('test_app') do + sh('cat ../gems.rb >> Gemfile') + sh("echo 'gem \"opal\", \"#{opal_version}\"' >> Gemfile") + sh("bundle update") + sh('spring stop') + sh('bundle exec rails g hyperstack:install') + sh('bundle exec rails generate model Sample name:string description:text') + sleep 1 + sh('pwd') + sh('ls app/models') + sh('mv app/models/sample.rb app/hyperstack/models/sample.rb') + sh("cat ../server_side_sample.rb >> app/models/sample.rb") + sh('bundle exec rake db:migrate') + sh('RAILS_ENV=test bundle exec rake db:setup') + # sh('bundle exec rails dev:cache') # not tested yet... + sh('RAILS_ENV=test bundle exec rails webpacker:compile') + end end end end end -task :default do - -end +task :default => :spec diff --git a/ruby/rails-hyperstack/lib/generators/hyper/templates/component_template.rb b/ruby/rails-hyperstack/lib/generators/hyper/templates/component_template.rb index 6324ed064..78d59adec 100644 --- a/ruby/rails-hyperstack/lib/generators/hyper/templates/component_template.rb +++ b/ruby/rails-hyperstack/lib/generators/hyper/templates/component_template.rb @@ -37,7 +37,7 @@ <%=" "* @indent %> before_unmount do <%=" "* @indent %> # cleanup any thing before component is destroyed -<%=" "* @indent %> # note timers are broadcast receivers are cleaned up +<%=" "* @indent %> # note timers and broadcast receivers are cleaned up <%=" "* @indent %> # automatically <%=" "* @indent %> end diff --git a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb index a12a614da..672aabef8 100644 --- a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb +++ b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb @@ -9,17 +9,6 @@ class InstallGenerator < Rails::Generators::Base class_option 'webpack-only', type: :boolean class_option 'hyper-model-only', type: :boolean - def add_component - if skip_adding_component? - # normally this is handled by the hyper:component - # generator, but if we are skipping it we will check it - # now. - insure_hyperstack_loader_installed - else - generate 'hyper:router App --add-route' - end - end - def add_hotloader return if skip_hotloader? unless Hyperstack.imported? 'hyperstack/hotloader' @@ -37,83 +26,32 @@ def add_hotloader end end - def insure_yarn_loaded - return if skip_webpack? - begin - yarn_version = `yarn --version` - raise Errno::ENOENT if yarn_version.blank? - rescue Errno::ENOENT - raise Thor::Error.new("please insure nodejs is installed and the yarn command is available if using webpacker") - end - end - - def add_webpacker_manifests - return if skip_webpack? - create_file 'app/javascript/packs/client_and_server.js', <<-JAVASCRIPT -//app/javascript/packs/client_and_server.js -// these packages will be loaded both during prerendering and on the client -React = require('react'); // react-js library -createReactClass = require('create-react-class'); // backwards compatibility with ECMA5 -History = require('history'); // react-router history library -ReactRouter = require('react-router'); // react-router js library -ReactRouterDOM = require('react-router-dom'); // react-router DOM interface -ReactRailsUJS = require('react_ujs'); // interface to react-rails -// to add additional NPM packages run `yarn add package-name@version` -// then add the require here. - JAVASCRIPT - create_file 'app/javascript/packs/client_only.js', <<-JAVASCRIPT -//app/javascript/packs/client_only.js -// add any requires for packages that will run client side only -ReactDOM = require('react-dom'); // react-js client side code -jQuery = require('jquery'); // remove if you don't need jQuery -// to add additional NPM packages call run yarn add package-name@version -// then add the require here. - JAVASCRIPT - append_file 'config/initializers/assets.rb' do - <<-RUBY -Rails.application.config.assets.paths << Rails.root.join('public', 'packs', 'js').to_s - RUBY - end - inject_into_file 'config/environments/test.rb', before: /^end/ do - <<-RUBY - - # added by hyperstack installer - config.assets.paths << Rails.root.join('public', 'packs-test', 'js').to_s - RUBY - end - end - - def add_webpacks - return if skip_webpack? - yarn 'react', '16' - yarn 'react-dom', '16' - yarn 'react-router', '^5.0.0' - yarn 'react-router-dom', '^5.0.0' - yarn 'react_ujs', '^2.5.0' - yarn 'jquery', '^3.4.1' - yarn 'create-react-class' - end + def install_webpack + return super unless skip_webpack? - def cancel_react_source_import - return if skip_webpack? inject_into_initializer( - "Hyperstack.cancel_import 'react/react-source-browser' "\ - '# bring your own React and ReactRouter via Yarn/Webpacker' + "Hyperstack.import 'react/react-source-browser' "\ + "# bring in hyperstack's copy of react, comment this out "\ + "if you bring it in from webpacker\n" ) end - def install_webpacker - return if skip_webpack? - gem 'webpacker' - Bundler.with_clean_env do - run 'bundle install' + def add_component + # add_component AFTER webpack so component generator webpack check works + if skip_adding_component? + # normally this is handled by the hyper:component + # generator, but if we are skipping it we will check it + # now. + insure_hyperstack_loader_installed + check_javascript_link_directory + else + generate 'hyper:router App --add-route' end - run 'bundle exec rails webpacker:install' end def create_policies_directory return if skip_hyper_model? - policy_file = File.join('app', 'policies', 'application_policy.rb') + policy_file = Rails.root.join('app', 'policies', 'hyperstack', 'application_policy.rb') unless File.exist? policy_file create_file policy_file, <<-RUBY # #{policy_file} @@ -122,38 +60,56 @@ def create_policies_directory # The following policy will open up full access (but only in development) # The policy system is very flexible and powerful. See the documentation # for complete details. - class Hyperstack::ApplicationPolicy - # Allow any session to connect: - always_allow_connection - # Send all attributes from all public models - regulate_all_broadcasts { |policy| policy.send_all } - # Allow all changes to models - allow_change(to: :all, on: [:create, :update, :destroy]) { true } - # allow remote access to all scopes - i.e. you can count or get a list of ids - # for any scope or relationship - ApplicationRecord.regulate_scope :all - end unless Rails.env.production? + module Hyperstack + class ApplicationPolicy + # Allow any session to connect: + always_allow_connection + # Send all attributes from all public models + regulate_all_broadcasts { |policy| policy.send_all } + # Allow all changes to models + allow_change(to: :all, on: [:create, :update, :destroy]) { true } + end unless Rails.env.production? + end RUBY end end def move_and_update_application_record return if skip_hyper_model? - rails_app_record_file = File.join('app', 'models', 'application_record.rb') - hyper_app_record_file = File.join('app', 'hyperstack', 'models', 'application_record.rb') + rails_app_record_file = Rails.root.join('app', 'models', 'application_record.rb') + hyper_app_record_file = Rails.root.join('app', 'hyperstack', 'models', 'application_record.rb') unless File.exist? hyper_app_record_file - empty_directory File.join('app', 'hyperstack', 'models') + empty_directory Rails.root.join('app', 'hyperstack', 'models') `mv #{rails_app_record_file} #{hyper_app_record_file}` - create_file rails_app_record_file, <<-RUBY -# #{rails_app_record_file} -# the presence of this file prevents rails migrations from recreating application_record.rb -# see https://github.com/rails/rails/issues/29407 + inject_into_file hyper_app_record_file, before: /^end/, verbose: false do + " # allow remote access to all scopes - i.e. you can count or get a list of ids\n"\ + " # for any scope or relationship\n"\ + " ApplicationRecord.regulate_scope :all unless Hyperstack.env.production?\n" + end -require 'models/application_record.rb' - RUBY +# create_file rails_app_record_file, <<-RUBY +# # #{rails_app_record_file} +# # the presence of this file prevents rails migrations from recreating application_record.rb +# # see https://github.com/rails/rails/issues/29407 +# +# require 'models/application_record.rb' +# RUBY end end + def turn_on_transport + inject_into_initializer <<-RUBY + +# transport controls how push (websocket) communications are +# implemented. The default is :none. +# Other possibilities are :action_cable, :pusher (see www.pusher.com) +# or :simple_poller which is sometimes handy during system debug. + +Hyperstack.transport = :action_cable # :pusher, :simple_poller or :none + + RUBY + end + def add_engine_route return if skip_hyper_model? route 'mount Hyperstack::Engine => \'/hyperstack\' # this route should be first in the routes file so it always matches' @@ -175,7 +131,7 @@ def report say '👩‍✈️ Basic development policy defined. See app/policies/application_policy.rb 👨🏽‍✈️', :green say '💽 HyperModel installed. Move any Active Record models to the app/hyperstack/models to access them from the client 📀', :green end - if File.exist?(init = File.join('config', 'initializers', 'hyperstack.rb')) + if File.exist?(init = Rails.root.join('config', 'initializers', 'hyperstack.rb')) say "☑️ Check #{init} for other configuration options. ☑️", :green end unless skip_hotloader? @@ -183,6 +139,8 @@ def report end say "\n\n" + + warnings.each { |warning| say "#{warning}", :yellow } end private @@ -206,7 +164,7 @@ def skip_hyper_model? def new_rails_app? # check to see if there are any routes set up and remember it, cause we might add a route in the process @new_rails_app ||= begin - route_file = File.join('config', 'routes.rb') + route_file = Rails.root.join('config', 'routes.rb') count = File.foreach(route_file).inject(0) do |c, line| line = line.strip next c if line.empty? @@ -217,52 +175,5 @@ def new_rails_app? count <= 2 end end - - def inject_into_initializer(s) - file_name = File.join('config', 'initializers', 'hyperstack.rb') - if File.exist?(file_name) - prepend_to_file(file_name) { "#{s}\n" } - else - create_file file_name, <<-RUBY -#{s} -# set the component base class - -Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent' - -# prerendering is default :off, you should wait until your -# application is relatively well debugged before turning on. - -Hyperstack.prerendering = :off # or :on - -# transport controls how push (websocket) communications are -# implemented. The default is :action_cable. -# Other possibilities are :pusher (see www.pusher.com) or -# :simple_poller which is sometimes handy during system debug. - -Hyperstack.transport = :action_cable # or :none, :pusher, :simple_poller - -# add this line if you need jQuery AND ARE NOT USING WEBPACK -# Hyperstack.import 'hyperstack/component/jquery', client_only: true - -# change definition of on_error to control how errors such as validation -# exceptions are reported on the server -module Hyperstack - def self.on_error(operation, err, params, formatted_error_message) - ::Rails.logger.debug( - "\#{formatted_error_message}\\n\\n" + - Pastel.new.red( - 'To further investigate you may want to add a debugging '\\ - 'breakpoint to the on_error method in config/initializers/hyperstack.rb' - ) - ) - end -end if Rails.env.development? - RUBY - end - # whenever we modify the initializer its best to empty the cache, BUT - # we only need to it once per generator execution - run 'rm -rf tmp/cache' unless @cache_emptied_already - @cache_emptied_already = true - end end end diff --git a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb index 7c81806d4..37fc28e74 100644 --- a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb +++ b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb @@ -6,9 +6,15 @@ class Base < Thor::Group protected + def warnings + @warnings ||= [] + end + def create_component_file(template) clear_cache insure_hyperstack_loader_installed + check_javascript_link_directory + added = webpack_check insure_base_component_class_exists @no_help = options.key?('no-help') self.components.each do |component| @@ -17,46 +23,80 @@ def create_component_file(template) @file_name = component_array.last @indent = 0 template template, - File.join('app', 'hyperstack', 'components', + Rails.root.join('app', 'hyperstack', 'components', *@modules.map(&:downcase), "#{@file_name.underscore}.rb") end add_route + return unless added + + say '📦 Webpack integrated with Hyperstack. '\ + 'Add javascript assets to app/javascript/packs/client_only.js and /client_and_server.js 📦', :green end def clear_cache - run 'rm -rf tmp/cache' unless Dir.exist?(File.join('app', 'hyperstack')) + run 'rm -rf tmp/cache' unless Dir.exist?(Rails.root.join('app', 'hyperstack')) end def insure_hyperstack_loader_installed - application_js = File.join( + hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+} + application_js = Rails.root.join( 'app', 'assets', 'javascripts', 'application.js' ) - require_tree = %r{//=\s+require_tree\s+} - hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+} - unless File.foreach(application_js).any? { |l| l =~ hyperstack_loader } - if File.foreach(application_js).any? { |l| l =~ require_tree } - inject_into_file 'app/assets/javascripts/application.js', before: require_tree do - "//= require hyperstack-loader\n" + if File.exist? application_js + unless File.foreach(application_js).any? { |l| l =~ hyperstack_loader } + require_tree = %r{//=\s+require_tree\s+} + if File.foreach(application_js).any? { |l| l =~ require_tree } + inject_into_file 'app/assets/javascripts/application.js', verbose: false, before: require_tree do + "//= require hyperstack-loader\n" + end + else + warnings << + " ***********************************************************\n"\ + " * Could not add `//= require hyperstack-loader` directive *\n"\ + " * to the app/assets/application.js file. *\n"\ + " * Normally this directive is added just before the *\n"\ + " * `//= require_tree .` directive at the end of the file, *\n"\ + " * but no require_tree directive was found. You need to *\n"\ + " * manually add `//= require hyperstack-loader` to the *\n"\ + " * app/assets/application.js file. *\n"\ + " ***********************************************************\n" + end + end + else + create_file application_js, "//= require hyperstack-loader\n" + warnings << + " ***********************************************************\n"\ + " * Could not find the app/assets/application.js file. *\n"\ + " * We created one for you, and added the *\n"\ + " * `<%= javascript_include_tag 'application' %>` to your *\n"\ + " * `html.erb` files immediately after any *\n"\ + " * `<%= javascript_pack 'application' %>` tags we found. *\n"\ + " ***********************************************************\n" + application_pack_tag = + /\s*\<\%\=\s+javascript_pack_tag\s+(\'|\")application(\'|\").*\%\>.*$/ + Dir.glob(Rails.root.join('app', 'views', '**', '*.erb')) do |file| + if File.foreach(file).any? { |l| l =~ application_pack_tag } + inject_into_file file, verbose: false, after: application_pack_tag do + "\n <%= javascript_include_tag 'application' %>" + end end - else - puts " ***********************************************************\n"\ - " * Could not add `//= require hyperstack-loader` directive *\n"\ - " * to the app/assets/application.js file. *\n"\ - " * Normally this directive is added just before the *\n"\ - " * `//= require_tree .` directive at the end of the file, *\n"\ - " * but no require_tree directive was found. You need to *\n"\ - " * manually add `//= require hyperstack-loader` to the *\n"\ - " * app/assets/application.js file. *\n"\ - " ***********************************************************\n" end end end + def check_javascript_link_directory + manifest_js_file = Rails.root.join("app", "assets", "config", "manifest.js") + return unless File.exist? manifest_js_file + return unless File.readlines(manifest_js_file).grep(/javascripts \.js/).empty? + + append_file manifest_js_file, "//= link_directory ../javascripts .js\n", verbose: false + end + def insure_base_component_class_exists @component_base_class = options['base-class'] || Hyperstack.component_base_class - file_name = File.join( + file_name = Rails.root.join( 'app', 'hyperstack', 'components', "#{@component_base_class.underscore}.rb" ) template 'hyper_component_template.rb', file_name unless File.exist? file_name @@ -64,19 +104,20 @@ def insure_base_component_class_exists def add_to_manifest(manifest, &block) if File.exist? "app/javascript/packs/#{manifest}" - append_file "app/javascript/packs/#{manifest}", &block + append_file "app/javascript/packs/#{manifest}", verbose: false, &block else - create_file "app/javascript/packs/#{manifest}", &block + create_file "app/javascript/packs/#{manifest}", verbose: false, &block end end def add_route return unless options['add-route'] if self.components.count > 1 - puts " ***********************************************************\n"\ - " * The add-route option ignored because more than one *\n"\ - " * component is being generated. *\n"\ - " ***********************************************************\n" + warnings << + " ***********************************************************\n"\ + " * The add-route option ignored because more than one *\n"\ + " * component is being generated. *\n"\ + " ***********************************************************\n" return end action_name = (@modules+[@file_name.underscore]).join('__') @@ -94,6 +135,154 @@ def yarn(package, version = nil) return if system("yarn add #{package}#{'@' + version if version}") raise Thor::Error.new("yarn failed to install #{package} with version #{version}") end + + def install_webpack + insure_yarn_loaded + add_webpacker_manifests + add_webpacks + cancel_react_source_import + install_webpacker + end + + def inject_into_initializer(s) + file_name = Rails.root.join('config', 'initializers', 'hyperstack.rb') + if File.exist?(file_name) + prepend_to_file(file_name, verbose: false) { "#{s}\n" } + else + create_file file_name, <<-RUBY +#{s} + +# server_side_auto_require will patch the ActiveSupport Dependencies module +# so that you can define classes and modules with files in both the +# app/hyperstack/xxx and app/xxx directories. For example you can split +# a Todo model into server and client related definitions and place this +# in `app/hyperstack/models/todo.rb`, and place any server only definitions in +# `app/models/todo.rb`. + +require "hyperstack/server_side_auto_require.rb" + +# set the component base class + +Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent' + +# prerendering is default :off, you should wait until your +# application is relatively well debugged before turning on. + +Hyperstack.prerendering = :off # or :on + +# add this line if you need jQuery AND ARE NOT USING WEBPACK +# Hyperstack.import 'hyperstack/component/jquery', client_only: true + +# change definition of on_error to control how errors such as validation +# exceptions are reported on the server +module Hyperstack + def self.on_error(operation, err, params, formatted_error_message) + ::Rails.logger.debug( + "\#{formatted_error_message}\\n\\n" + + Pastel.new.red( + 'To further investigate you may want to add a debugging '\\ + 'breakpoint to the on_error method in config/initializers/hyperstack.rb' + ) + ) + end +end if Rails.env.development? + RUBY + end + # whenever we modify the initializer its best to empty the cache, BUT + # we only need to it once per generator execution + run 'rm -rf tmp/cache' unless @cache_emptied_already + @cache_emptied_already = true + end + + private + + def webpack_check + return unless defined? ::Webpacker + + client_and_server = Rails.root.join("app", "javascript", "packs", "client_only.js") + return if File.exist? client_and_server + + # Dir.chdir(Rails.root.join.to_s) { run 'bundle exec rails hyperstack:install:webpack' } + + # say "warning: you are running webpacker, but the hyperstack webpack files have not been created.\n"\ + # " Suggest you run bundle exec rails hyperstack:install:webpack soon.\n"\ + # " Or to avoid this warning create an empty file named app/javascript/packs/client_only.js", + # :red + install_webpack + true + end + + def insure_yarn_loaded + begin + yarn_version = `yarn --version` + raise Errno::ENOENT if yarn_version.blank? + rescue Errno::ENOENT + raise Thor::Error.new("please insure nodejs is installed and the yarn command is available if using webpacker") + end + end + + def add_webpacker_manifests + create_file 'app/javascript/packs/client_and_server.js', <<-JAVASCRIPT +//app/javascript/packs/client_and_server.js +// these packages will be loaded both during prerendering and on the client +React = require('react'); // react-js library +createReactClass = require('create-react-class'); // backwards compatibility with ECMA5 +History = require('history'); // react-router history library +ReactRouter = require('react-router'); // react-router js library +ReactRouterDOM = require('react-router-dom'); // react-router DOM interface +ReactRailsUJS = require('react_ujs'); // interface to react-rails +// to add additional NPM packages run `yarn add package-name@version` +// then add the require here. + JAVASCRIPT + create_file 'app/javascript/packs/client_only.js', <<-JAVASCRIPT +//app/javascript/packs/client_only.js +// add any requires for packages that will run client side only +ReactDOM = require('react-dom'); // react-js client side code +jQuery = require('jquery'); // remove if you don't need jQuery +// to add additional NPM packages call run yarn add package-name@version +// then add the require here. + JAVASCRIPT + append_file 'config/initializers/assets.rb', verbose: false do + <<-RUBY + Rails.application.config.assets.paths << Rails.root.join('public', 'packs', 'js').to_s + RUBY + end + inject_into_file 'config/environments/test.rb', verbose: false, before: /^end/ do + <<-RUBY + + # added by hyperstack installer + config.assets.paths << Rails.root.join('public', 'packs-test', 'js').to_s + RUBY + end + end + + def add_webpacks + yarn 'react', '16' + yarn 'react-dom', '16' + yarn 'react-router', '^5.0.0' + yarn 'react-router-dom', '^5.0.0' + yarn 'react_ujs', '^2.5.0' + yarn 'jquery', '^3.4.1' + yarn 'create-react-class' + end + + def cancel_react_source_import + inject_into_initializer( + "# Hyperstack.import 'react/react-source-browser' "\ + "# uncomment this line if you want hyperstack to use its copy of react" + ) + end + + def install_webpacker + return if defined?(::Webpacker) + + gem "webpacker" + Bundler.with_unbundled_env do + run "bundle install" + end + `spring stop` + Dir.chdir(Rails.root.join.to_s) { run 'bundle exec rails webpacker:install' } + end end end end diff --git a/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator.rb b/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator.rb index 448b6e8e1..8a1d30a4c 100644 --- a/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator.rb +++ b/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator.rb @@ -214,7 +214,7 @@ def add_gems end def install - Bundler.with_clean_env do + Bundler.with_unbundled_env do run "bundle install" end run 'bundle exec rails webpacker:install' diff --git a/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb b/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb index dd39c8875..d55bc6088 100644 --- a/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb +++ b/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb @@ -6,39 +6,64 @@ class Base < Thor::Group protected + def warnings + @warnings ||= [] + end + def clear_cache - run 'rm -rf tmp/cache' unless Dir.exists?(File.join('app', 'hyperstack')) + run 'rm -rf tmp/cache' unless Dir.exists?(Rails.root.join('app', 'hyperstack')) end def insure_hyperstack_loader_installed - application_js = File.join( + hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+} + application_js = Rails.root.join( 'app', 'assets', 'javascripts', 'application.js' ) - require_tree = %r{//=\s+require_tree\s+} - hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+} - unless File.foreach(application_js).any? { |l| l =~ hyperstack_loader } - if File.foreach(application_js).any? { |l| l =~ require_tree } - inject_into_file 'app/assets/javascripts/application.js', before: require_tree do - "//= require hyperstack-loader\n" + if File.exist? application_js + unless File.foreach(application_js).any? { |l| l =~ hyperstack_loader } + require_tree = %r{//=\s+require_tree\s+} + if File.foreach(application_js).any? { |l| l =~ require_tree } + inject_into_file 'app/assets/javascripts/application.js', before: require_tree do + "//= require hyperstack-loader\n" + end + else + warnings << + " ***********************************************************\n"\ + " * Could not add `//= require hyperstack-loader` directive *\n"\ + " * to the app/assets/application.js file. *\n"\ + " * Normally this directive is added just before the *\n"\ + " * `//= require_tree .` directive at the end of the file, *\n"\ + " * but no require_tree directive was found. You need to *\n"\ + " * manually add `//= require hyperstack-loader` to the *\n"\ + " * app/assets/application.js file. *\n"\ + " ***********************************************************\n" + end + end + else + create_file application_js, "//= require hyperstack-loader\n" + warnings << + " ***********************************************************\n"\ + " * Could not find the app/assets/application.js file. *\n"\ + " * We created one for you, and added the *\n"\ + " * `<%= javascript_include_tag 'application' %>` to your *\n"\ + " * `html.erb` files immediately after any *\n"\ + " * `<%= javascript_pack 'application' %>` tags we found. *\n"\ + " ***********************************************************\n" + application_pack_tag = + /\s*\<\%\=\s+javascript_pack_tag\s+(\'|\")application(\'|\").*\%\>.*$/ + Dir.glob(Rails.root.join('app', 'views', '**', '*.erb')) do |file| + if File.foreach(file).any? { |l| l =~ application_pack_tag } + inject_into_file file, after: application_pack_tag do + "\n <%= javascript_include_tag 'application' %>" + end end - else - puts " ***********************************************************\n"\ - " * Could not add `//= require hyperstack-loader` directive *\n"\ - " * to the app/assets/application.js file. *\n"\ - " * Normally this directive is added just before the *\n"\ - " * `//= require_tree .` directive at the end of the file, *\n"\ - " * but no require_tree directive was found. You need to *\n"\ - " * manually add `//= require hyperstack-loader` to the *\n"\ - " * app/assets/application.js file. *\n"\ - " ***********************************************************\n" end end end - def insure_base_component_class_exists @component_base_class = options['base-class'] - file_name = File.join( + file_name = Rails.root.join( 'app', 'hyperstack', 'components', "#{@component_base_class.underscore}.rb" ) template 'hyper_component_template.rb', file_name unless File.exists? file_name @@ -55,7 +80,8 @@ def add_to_manifest(manifest, &block) def add_route return unless options['add-route'] if self.components.count > 1 - puts " ***********************************************************\n"\ + warnings << + " ***********************************************************\n"\ " * The add-route option ignored because more than one *\n"\ " * component is being generated. *\n"\ " ***********************************************************\n" diff --git a/ruby/rails-hyperstack/lib/hyperstack/server_side_auto_require.rb b/ruby/rails-hyperstack/lib/hyperstack/server_side_auto_require.rb new file mode 100644 index 000000000..ee500361c --- /dev/null +++ b/ruby/rails-hyperstack/lib/hyperstack/server_side_auto_require.rb @@ -0,0 +1,52 @@ +# require "hyperstack/server_side_auto_require.rb" in your hyperstack initializer +# to autoload shadowed server side files that match files +# in the hyperstack directory + +if Rails.configuration.try(:autoloader) == :zeitwerk + Rails.autoloaders.each do |loader| + loader.on_load do |_cpath, _value, abspath| + ActiveSupport::Dependencies.add_server_side_dependency(abspath) do |load_path| + loader.send(:log, "Hyperstack loading server side shadowed file: #{load_path}") if loader&.logger + require("#{load_path}.rb") + end + end + end +end + +module ActiveSupport + module Dependencies + HYPERSTACK_DIR = "hyperstack" + class << self + alias original_require_or_load require_or_load + + # before requiring_or_loading a file, first check if + # we have the same file in the server side directory + # and add that as a dependency + + def require_or_load(file_name, const_path = nil) + add_server_side_dependency(file_name) { |load_path| require_dependency load_path } + original_require_or_load(file_name, const_path) + end + + # search the filename path from the end towards the beginning + # for the HYPERSTACK_DIR directory. If found, remove it from + # the filename, and if a ruby file exists at that location then + # add it as a dependency + + def add_server_side_dependency(file_name, loader = nil) + path = File.expand_path(file_name.chomp(".rb")) + .split(File::SEPARATOR).reverse + hs_index = path.find_index(HYPERSTACK_DIR) + + return unless hs_index # no hyperstack directory here + + new_path = (path[0..hs_index - 1] + path[hs_index + 1..-1]).reverse + load_path = new_path.join(File::SEPARATOR) + + return unless File.exist? "#{load_path}.rb" + + yield load_path + end + end + end +end diff --git a/ruby/rails-hyperstack/lib/hyperstack/version.rb b/ruby/rails-hyperstack/lib/hyperstack/version.rb index 82971b1a0..4c001ab78 100644 --- a/ruby/rails-hyperstack/lib/hyperstack/version.rb +++ b/ruby/rails-hyperstack/lib/hyperstack/version.rb @@ -1,3 +1,3 @@ module Hyperstack - ROUTERVERSION = VERSION = '1.0.alpha1.5' + ROUTERVERSION = VERSION = '1.0.alpha1.8' end diff --git a/ruby/rails-hyperstack/lib/rails-hyperstack.rb b/ruby/rails-hyperstack/lib/rails-hyperstack.rb index 1a356de67..1a167e669 100644 --- a/ruby/rails-hyperstack/lib/rails-hyperstack.rb +++ b/ruby/rails-hyperstack/lib/rails-hyperstack.rb @@ -1,6 +1,5 @@ require 'hyperstack-config' require 'rails/generators' -require 'hyper-state' # remove these once lap29 is released ... Hyperstack.js_import 'react/react-source-browser', client_only: true, defines: ['ReactDOM', 'React'] @@ -21,11 +20,11 @@ require 'opal-rails' require 'hyper-model' require 'hyper-router' + require 'mini_racer' rescue LoadError end require 'react-rails' require 'opal-browser' -require 'mini_racer' require 'hyperstack/version' class RailsHyperstack < Rails::Railtie diff --git a/ruby/rails-hyperstack/rails-hyperstack.gemspec b/ruby/rails-hyperstack/rails-hyperstack.gemspec index 4bd9e0e2f..498118adc 100644 --- a/ruby/rails-hyperstack/rails-hyperstack.gemspec +++ b/ruby/rails-hyperstack/rails-hyperstack.gemspec @@ -11,6 +11,7 @@ Gem::Specification.new do |spec| spec.authors = ['Loic Boutet', 'Adam George', 'Mitch VanDuyn', 'Jan Biedermann'] spec.email = ['loic@boutet.com', 'jan@kursator.com'] spec.homepage = 'http://hyperstack.org' + spec.metadata = { 'documentation_uri' => 'https://docs.hyperstack.org/' } spec.license = 'MIT' # spec.metadata = { # "homepage_uri" => 'http://hyperstack.org', @@ -57,43 +58,34 @@ You can control how much of the stack gets installed as well: spec.add_dependency 'hyper-model', Hyperstack::VERSION spec.add_dependency 'hyper-router', Hyperstack::ROUTERVERSION spec.add_dependency 'hyperstack-config', Hyperstack::VERSION - spec.add_dependency 'opal-rails', '~> 0.9.4' - - spec.add_dependency 'opal-browser', '~> 0.2.0' + spec.add_dependency 'opal-rails' + spec.add_dependency 'opal', ENV['OPAL_VERSION'] || '>= 0.11.0', '< 1.1' spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' - spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'libv8', '~> 7.3.492.27.1' - spec.add_dependency 'rails', '>= 4.0.0' - - - # spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 - # #spec.add_development_dependency 'chromedriver-helper' - # spec.add_development_dependency 'geminabox', '>= 0.13.11' + # spec.add_dependency 'mini_racer', '~> 0.2.6' + # spec.add_dependency 'libv8', '~> 7.3.492.27.1' + spec.add_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' - - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-spec', Hyperstack::VERSION spec.add_development_dependency 'pry' - spec.add_development_dependency 'puma' + spec.add_development_dependency 'puma', '<= 5.4.0' spec.add_development_dependency 'bootsnap' - #spec.add_development_dependency 'rake', '~> 10.0' - spec.add_development_dependency 'rspec' - spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 - spec.add_development_dependency 'sass-rails', '~> 5.0' + spec.add_development_dependency 'rspec-rails' + spec.add_development_dependency 'rubocop' #, '~> 0.51.0' + spec.add_development_dependency 'sqlite3', '~> 1.4' # was 1.3.6 -- see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sass-rails', '>= 5.0' # Use Uglifier as compressor for JavaScript assets spec.add_development_dependency 'uglifier', '>= 1.3.0' # See https://github.com/rails/execjs#readme for more supported runtimes # gem 'mini_racer', platforms: :ruby # Use CoffeeScript for .coffee assets and views - spec.add_development_dependency 'coffee-rails', '~> 4.2' + #spec.add_development_dependency 'coffee-rails', '~> 4.2' # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks spec.add_development_dependency 'turbolinks', '~> 5' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder spec.add_development_dependency 'jbuilder', '~> 2.5' spec.add_development_dependency 'foreman' spec.add_development_dependency 'database_cleaner' - end diff --git a/ruby/rails-hyperstack/spec/gems.rb b/ruby/rails-hyperstack/spec/gems.rb new file mode 100644 index 000000000..2f8d7cba1 --- /dev/null +++ b/ruby/rails-hyperstack/spec/gems.rb @@ -0,0 +1,10 @@ +gem 'rails-hyperstack', path: '../..' +gem 'hyperstack-config', path: '../../../hyperstack-config' +gem 'hyper-state', path: '../../../hyper-state' +gem 'hyper-component', path: '../../../hyper-component' +gem 'hyper-operation', path: '../../../hyper-operation' +gem 'hyper-model', path: '../../../hyper-model' +gem 'hyper-router', path: '../../../hyper-router' +gem 'hyper-spec', path: '../../../hyper-spec' + +gem 'pry' diff --git a/ruby/rails-hyperstack/spec/rails_hyperstack_spec.rb b/ruby/rails-hyperstack/spec/rails_hyperstack_spec.rb index 619c4a8bd..70b9680be 100644 --- a/ruby/rails-hyperstack/spec/rails_hyperstack_spec.rb +++ b/ruby/rails-hyperstack/spec/rails_hyperstack_spec.rb @@ -1,24 +1,30 @@ -require 'spec_helper' +require "spec_helper" -describe 'rails-hyperstack' do - it 'builds a working app', js: true do - visit '/' - expect(page).to have_content('App') +describe "rails-hyperstack" do + it "builds a working app", js: true do + visit "/" + expect(page).to have_content("App") end - it 'installs hyper-model and friends', js: true do - visit '/' - expect_promise do + + it "installs hyper-model and friends", js: true do + visit "/" + expect do Hyperstack::Model.load { Sample.count } - end.to eq(0) - evaluate_ruby do - Sample.create(name: 'sample1', description: 'the first sample') + end.on_client_to eq(0) + on_client do + Sample.create(name: "sample1", description: "the first sample") end wait_for_ajax expect(Sample.count).to eq(1) - expect(Sample.first.name).to eq('sample1') - expect(Sample.first.description).to eq('the first sample') - expect_evaluate_ruby do - Sample.count - end.to eq(1) + expect(Sample.first.name).to eq("sample1") + expect(Sample.first.description).to eq("the first sample") + expect { Sample.count }.on_client_to eq(1) + end + + it "implements server_side_auto_require", js: true do + expect(Sample.super_secret_server_side_method).to be true + expect do + Sample.respond_to? :super_secret_server_side_method + end.on_client_to be_falsy end end diff --git a/ruby/rails-hyperstack/spec/server_side_sample.rb b/ruby/rails-hyperstack/spec/server_side_sample.rb new file mode 100644 index 000000000..556d67f79 --- /dev/null +++ b/ruby/rails-hyperstack/spec/server_side_sample.rb @@ -0,0 +1,5 @@ +class Sample < ApplicationRecord + def self.super_secret_server_side_method + true + end +end diff --git a/upgrade-from-opal-0.11-rails-5.md b/upgrade-from-opal-0.11-rails-5.md new file mode 100644 index 000000000..5258f2338 --- /dev/null +++ b/upgrade-from-opal-0.11-rails-5.md @@ -0,0 +1,286 @@ +### Progress: + ++ hyper-spec: passing ++ hyperstack-config: passing ++ hyper-state: passing ++ hyper-store: passing ++ hyper-router: passing ++ hyper-component: passing ++ hyper-operation: passing ++ hyper-model: first couple of specs pass, suspect more issues because splat operator is now working properly. +--- +### Multiple Dependency Environments Supported + +set env OPAL_VERSION='~ > 0.11' (for example) and then bundle update +set env RAILS_VERSION='~> 5.0' (for example) and then bundle update + +note that when testing hyper-component with OPAL_VERSION 0.11 you must also set the environment +variable to the same when running the specs (because we DONT want to fetch opal-browser from github see below) + +note that Opal 0.11 will NOT work with rails 6.x, so that leaves three test environments +1. no specification = latest opal (1.x) and latest rails (6.x) +2. OPAL_VERSION='~> 0.11' = opal 0.11 and forces rails 5.x +3. RAILS_VERSION='~> 5.0' = rails 5.x and defaults to latest opal (1.0) +--- +### Opal require implementation changed + +replace + +```javascript +Opal.load('xxxx') +``` +with +```javascript +Opal.loaded(OpalLoaded || []); +Opal.require("components"); +``` + +`require 'opal'` must be executed before any ruby code (including other requires.) This was not true in Opal 0.11 + +hyperstack-config semantically figures out the right method, so if you use hyperstack-config consistently to load the opal code, all will be well! + +Typically: + ++ app/assets/javascript/application.js +should have the line: +`//= require hyperstack-loader` ++ add remove other dependencies using Hyperstack.import + +--- + +### @@vars properly implemented + +@@vars: Are now implemented correctly (I guess) so that they are lexically scoped, thus this does **not** work: + +``` +Foo.class_eval do + def self.get_error + @@error + end +end +``` +because its lexically scoped, the compiler looks for an inclosing class definition for @@foo, not what you want. + +in this case simply replace `Foo.class_eval do` with `class Foo` + +--- +### opal-browser must be taken from master to work with 1.0 + +opal-browser not released for opal 1.0 yet, causing deprecation warnings. To work around this we include browser in the hypercomponent Gemfile (not gemspec) unless the OPAL_VERSION environment variable is set to 0.11 + +TODO: Release opal-browser! + +------------ + +### compiler checks for JS undefined, empty objects, and null in more places + +This code will no longer work as expected: + +```ruby +x = `{}` +"foo(#{x})" # <- generates "foo("+x+")" which +# results in the following string "foo([Object Object])" +``` +not sure how this worked before, but it did. + +```ruby +array.each do |e| + # be careful any values e that are `null` will + # get rewritten as `nil` fine I guess most of the time + # but caused some problems in specs where we are + # explicitly trying to iterate over JS values + # see /spec/client_features/component_spec.rb:656 +``` + +------------ + +### back quotes operator now works properly in hyper-spec blocks + +This now works properly: + +```ruby + evaluate_ruby do + ... + foo = `JSON.stringify(x)` + ... + end +``` + +------------ + +### TODO: inconsistent dependency on server_rendering.js + + search around for references to config.react.server_renderer_options and/or "server_rendering.js" + inconsistent... should now default to hyperstack_prerender_loader.js + and more over there is a hyperstack option as well, so the server_renderer file option should match + +------------ + +### Opal splat works properly in 1.0! + +```ruby +foo(*[1, 2], [[3, 4]]) + +def foo(*x) + # 0.9 x == [1, 2, [3, 4]] + # 1.0 x == [1, 2, [[3, 4]]] +end +``` + +is there a fix that works for both 1.0 and 0.9 ? or do we need a SWITCH??? + +Found this first in hyper-model, so in hyper-model +these are going to be marked with # SPLAT BUG with the old code until things get sorted + +------------ + +### Can't inherit directly from ApplicationController::Base in Rails 6.0 ... + +------------ + +### TODO pull in next-gen-hyperspec branch + +------------ + +### TODO figure out how to make libv8 dependency optional + +----------- + +### Patch selenium webdriver + +Hyper-spec currently depends on an older version of selenium webdriver, and so requires this patch be applied to get logging to work: + +```ruby +module Selenium + module WebDriver + module Chrome + module Bridge + COMMANDS = remove_const(:COMMANDS).dup + COMMANDS[:get_log] = [:post, 'session/:session_id/log'] + COMMANDS.freeze + + def log(type) + data = execute :get_log, {}, {type: type.to_s} + + Array(data).map do |l| + begin + LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message') + rescue KeyError + next + end + end + end + end + end + end +end +``` + +TODO: figure out if we have to depend on this old version + +--- + +### BasicObject responds_to the class method + +This means that a `DummyValue` returns `DummyValue` instead of the class its wrapping. Simple fix is to add + +```ruby +def class + notify + @object.class +end +``` + +which is what the method missing would do... + +--- + +### Opal 1.0 has trouble with super and method_missing + +If super is called within a method that is not defined in the super class, the superclasses' method missing +is called, but the method name is not provided. + +This has been worked around, and a bug has been filed. + +--- + +### Object.present? should not have been defined + +The DummyValue system was defining present? in both DummyValue and Object. This should not be necessary as +present? is defined by activesupport. In 0.11 it didnt' matter because the load order defined activesupport's +version AFTER the bogus one in the dummy value system. In 1.0 the order for some reason is reversed, so +the correct version in activesupport was being overwritten. + +I don't believe this will have any effect on application code. + +--- + +### HyperSpec is not using ApplicationController + +HyperModel had its own version of HyperSpec. In that version the test controller was subclassed from ApplicationController. In the new version its subclassed from ActionController::Base. + +In some HyperModel specs it was setting up mocks in ApplicationController, and these were not being used. + +so now hyper-spec tries to use ApplicationController and falls back to ::ActionController::Base. + +TODO: verify this works in all cases... + +--- + +### HyperSpec ApplicationController fix breaks client_driver.rb + +A couple of specs were setting `ApplicationController.acting_user = true`. But HyperSpec was not +using the ApplicationController when mounting (see above problem fix) so acting_user remained nil +as far as the client code was concerned. + +Once the above fix was made however client_driver was attempting to do a `controller.acting_user.id` +where acting_user was true, causing a method missing. The code in client_driver was checking for +acting_user being nil, but is now changed to check for `acting_user.respond_to? :id` + +--- + +### Trying to set window size outside of a JS spec no longer works + +Who knows why, but for now we just rescue any failure, and keep going. + +--- + +### page.evaluate_ruby no longer works but plain evaluate_ruby does. + +Only one spec files was doing that, so just upgraded. +You can sort of fix it by doing an include instead of config.include in the hyper_spec.rb file +but this causes other problems, including a warning from rspec to not do it. + +Probably will require a general cleanup of application code changing page.evaluate_ruby to just +evaluate_ruby. + +Related to this there was a method called attributes_on_client that was being added to +ActiveRecord::Base. But in order for it to work the "page" was being passed so that you could do +a page.evaluate_ruby, but the whole method was backwards. It should be a capybara helper method that takes a active record model. This is fixed. + +--- + +### Hyperspec changes behavior of on_client + +New hyperspec aliases evaluate_ruby as on_client for readability, the old on_client is now called before_mount. + +You can either update calls to on_client to be before_mount, or + +You can get legacy behavior by executing this line in the spec_helper: +```ruby +HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount +``` +--- + +### HyperSpec todos + +TODO: Hyperspec is dependent on HyperComponent for mounting components, and initializing the system. + +However we want to make sure you can use HyperSpec without HyperComponent + +TODO: Investigate why we need before_mount... + +TODO: add_locals method only works on simple cases. For now make it experimental (i.e. make the method called + experimental_add_locals, which you can alias, or include a module that switches on the behavior.) + +---