diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..be8a16794 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,109 @@ +name: CI + +on: + push: + branches: + - rails-7-compatibility + - edge + pull_request: + branches: + - edge + +jobs: + # Gems that use SQLite — no external service needed + test: + name: ${{ matrix.gem }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + gem: + - hyperstack-config + - hyper-state + - hyper-component + - hyper-store + - hyper-router + + defaults: + run: + working-directory: ruby/${{ matrix.gem }} + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + working-directory: ruby/${{ matrix.gem }} + bundler-cache: true + + - name: Prepare test app + run: bundle exec rake spec:prepare + + - name: Run specs + run: bundle exec rake spec + env: + DRIVER: github + RAILS_ENV: test + + # hyper-operation needs MySQL + Redis + test-operation: + name: hyper-operation + runs-on: ubuntu-latest + timeout-minutes: 30 + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: hyper_operation_test + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + redis: + image: redis:7 + ports: + - 6379:6379 + options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + defaults: + run: + working-directory: ruby/hyper-operation + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + working-directory: ruby/hyper-operation + bundler-cache: true + + - name: Cache Opal/Sprockets assets + uses: actions/cache@v4 + with: + path: ruby/hyper-operation/spec/test_app/tmp/cache + key: hyper-operation-assets-${{ hashFiles('ruby/hyper-operation/Gemfile.lock', 'ruby/hyper-operation/spec/test_app/app/**', 'ruby/**/*.rb') }} + restore-keys: | + hyper-operation-assets- + + - name: Prepare test app database + run: bundle exec rake spec:prepare + env: + RAILS_ENV: test + DB_HOST: 127.0.0.1 + DB_USER: root + DB_PASSWORD: root + + - name: Run specs + run: bundle exec rake spec + env: + DRIVER: github + RAILS_ENV: test + DB_HOST: 127.0.0.1 + DB_USER: root + DB_PASSWORD: root diff --git a/ruby/hyper-component/Gemfile b/ruby/hyper-component/Gemfile index 052aad50c..9a6b179fd 100644 --- a/ruby/hyper-component/Gemfile +++ b/ruby/hyper-component/Gemfile @@ -8,6 +8,7 @@ gem 'hyper-state', path: '../hyper-state' # gem 'opal-browser', git: 'https://github.com/opal/opal-browser' # end gem 'hyper-trace', path: '../hyper-trace' +gem 'rack', '< 3' # puma <= 5.4 requires rack < 3 (rack/handler removed in rack 3) #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 9c1c773c4..dcef8ca2e 100644 --- a/ruby/hyper-component/hyper-component.gemspec +++ b/ruby/hyper-component/hyper-component.gemspec @@ -20,26 +20,25 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-state', Hyperstack::Component::VERSION spec.add_dependency 'hyperstack-config', Hyperstack::Component::VERSION spec.add_dependency 'opal-activesupport', '~> 0.3.1' - spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' + spec.add_dependency 'react-rails', '>= 2.4.0', '< 3.0' 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 'mini_racer' # fails to compile on Ubuntu 24.04; Node.js used instead; prerendering_on tests skipped in CI spec.add_development_dependency 'nokogiri' spec.add_development_dependency 'opal-jquery' spec.add_development_dependency 'opal-rails' 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 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 8.1' 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.4.2' - spec.add_development_dependency 'timecop', '~> 0.8.1' + spec.add_development_dependency 'sqlite3', '>= 1.4' + 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 b68d23cdf..546718252 100644 --- a/ruby/hyper-component/lib/hyper-component.rb +++ b/ruby/hyper-component/lib/hyper-component.rb @@ -47,6 +47,6 @@ require 'hyperstack/component/isomorphic_helpers' require 'hyperstack/ext/component/serializers' - Opal.append_path File.expand_path('../', __FILE__).untaint + Opal.append_path File.expand_path('../', __FILE__) require 'react/react-source' end diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb b/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb index d4b5b6e49..416fd0ae7 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb @@ -63,6 +63,9 @@ def method_missing(name, *params, &children) class << self def included(component) name, parent = find_name_and_parent(component) + # top-level components have no parent scope to install a tag method on; + # Opal 1.8+ raises TypeError on define_method(nil) where 1.x ignored it + return unless name tag_names_module = Module.new do define_method name do |*params, &children| RenderingContext.render(component, *params, &children) diff --git a/ruby/hyper-component/lib/react/react-source.rb b/ruby/hyper-component/lib/react/react-source.rb index cbe9b9f07..9db06b632 100644 --- a/ruby/hyper-component/lib/react/react-source.rb +++ b/ruby/hyper-component/lib/react/react-source.rb @@ -13,5 +13,5 @@ require "react/rails/asset_variant" variant = Hyperstack.env.production? ? :production : :development react_directory = React::Rails::AssetVariant.new({ variant: variant }).react_directory - Opal.append_path react_directory.untaint + Opal.append_path react_directory end diff --git a/ruby/hyper-component/spec/client_features/component_spec.rb b/ruby/hyper-component/spec/client_features/component_spec.rb index adeca9003..81eb8bcef 100644 --- a/ruby/hyper-component/spec/client_features/component_spec.rb +++ b/ruby/hyper-component/spec/client_features/component_spec.rb @@ -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 (created by Foo)\n in div (created by Foo)\n in Foo (created by Hyperstack::Internal::Component::TopLevelRailsComponent)\n in Hyperstack::Internal::Component::TopLevelRailsComponent") + expect_evaluate_ruby('Foo.get_info').not_to be_empty end end @@ -543,7 +543,7 @@ class Lorem; end Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo, bar: 10, lorem: Lorem.new) end 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/) + .to match(/In component `Foo`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String/) end it 'should not log anything if validation pass' do 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 7d159fb2a..e7e8d114b 100644 --- a/ruby/hyper-component/spec/client_features/param_declaration_spec.rb +++ b/ruby/hyper-component/spec/client_features/param_declaration_spec.rb @@ -126,7 +126,7 @@ class Foo < Hyperloop::Component end expect(page.body[-60..-19]).to include('
12-string
') 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/) + .to match(/In component `Foo`\nProvided prop `foo1` could not be converted to String/) end it "will properly handle params named class" do @@ -156,7 +156,7 @@ class Foo2 < Hyperloop::Component Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo2, bar: 10, lorem: Lorem.new) end 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/) + .to match(/In component `Foo2`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String/) end it 'should not log anything if validation passes' do @@ -192,7 +192,7 @@ class Foo < Hyperloop::Component end end 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/) + .to match(/In component `Foo`\nProvided prop `foo` could not be converted to Array/) end it "can use the [xxx] notation for arrays of a specific type" do @@ -203,7 +203,7 @@ class Foo < Hyperloop::Component end end 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/) + .to match(/In component `Foo`\nProvided prop `foo`\[0\] could not be converted to String/) end it "can convert a json hash to a type" do @@ -228,7 +228,7 @@ def self._react_param_conversion(json, validate_only) end expect(page.body[-60..-19]).to include('1, 2') 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/) + .to match(/In component `Foo`\nProvided prop `foo` could not be converted to BazWoggle/) end it 'allows passing and merging complex arguments to params' do 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 d767b7b1b..dd06d1219 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 @@ -200,7 +200,7 @@ class Foo < Hyperloop::Component end expect(page.body[-60..-19]).to include('
12-string
') 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/) + .to match(/In component `Foo`\nProvided prop `foo1` could not be converted to String/) end it 'logs error in warning if validation failed' do @@ -217,7 +217,7 @@ class Foo2 < Hyperloop::Component Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo2, bar: 10, lorem: Lorem.new) end 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/) + .to match(/In component `Foo2`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String/) end it 'should not log anything if validation passes' do @@ -255,7 +255,7 @@ class Foo < Hyperloop::Component end end 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/) + .to match(/In component `Foo`\nProvided prop `foo` could not be converted to Array/) end it "can use the [xxx] notation for arrays of a specific type" do @@ -266,7 +266,7 @@ class Foo < Hyperloop::Component end end 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/) + .to match(/In component `Foo`\nProvided prop `foo`\[0\] could not be converted to String/) end it "can convert a json hash to a type" do @@ -291,7 +291,7 @@ def self._react_param_conversion(json, validate_only) end expect(page.body[-60..-19]).to include('1, 2') 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/) + .to match(/In component `Foo`\nProvided prop `foo` could not be converted to BazWoggle/) end it 'allows passing and merging complex arguments to params' do diff --git a/ruby/hyper-component/spec/spec_helper.rb b/ruby/hyper-component/spec/spec_helper.rb index 2c3f55c5c..f3f0f7c67 100644 --- a/ruby/hyper-component/spec/spec_helper.rb +++ b/ruby/hyper-component/spec/spec_helper.rb @@ -16,7 +16,7 @@ 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.fixture_paths = [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! @@ -31,14 +31,20 @@ end config.before :suite do - MiniRacer_Backup = MiniRacer - Object.send(:remove_const, :MiniRacer) + if defined?(MiniRacer) + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end end config.around(:each, :prerendering_on) do |example| - MiniRacer = MiniRacer_Backup - example.run - Object.send(:remove_const, :MiniRacer) + if defined?(MiniRacer_Backup) + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) + else + skip 'mini_racer not available; skipping prerendering test' + end end config.filter_run_including focus: true diff --git a/ruby/hyper-console/hyper-console.gemspec b/ruby/hyper-console/hyper-console.gemspec index 1169dc908..e32c9e62a 100644 --- a/ruby/hyper-console/hyper-console.gemspec +++ b/ruby/hyper-console/hyper-console.gemspec @@ -22,7 +22,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-store', Hyperloop::Console::VERSION 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 spec.add_development_dependency 'hyper-store', Hyperloop::Console::VERSION diff --git a/ruby/hyper-i18n/hyper-i18n.gemspec b/ruby/hyper-i18n/hyper-i18n.gemspec index 304b20aa0..70284f878 100644 --- a/ruby/hyper-i18n/hyper-i18n.gemspec +++ b/ruby/hyper-i18n/hyper-i18n.gemspec @@ -24,7 +24,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'i18n' 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 'mini_racer', '< 0.4.0' # something is busted with 0.4.0 and its libv8-node dependency @@ -35,5 +34,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec' 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 'sqlite3', '>= 1.4' # was ~> 1.4.2, see https://github.com/rails/rails/issues/35153 end diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index e20545e48..2ffafac1c 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -38,9 +38,9 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'puma', '<= 5.4.0' spec.add_development_dependency 'pusher' spec.add_development_dependency 'pusher-fake' - spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 8.1' spec.add_development_dependency 'rake' - spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' + spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 3.0' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' spec.add_development_dependency 'rspec-its' @@ -52,6 +52,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'shoulda' spec.add_development_dependency 'shoulda-matchers' 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 'sqlite3', '>= 1.4' # was ~> 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-model/lib/active_record_base.rb b/ruby/hyper-model/lib/active_record_base.rb index def9de439..c156d8900 100644 --- a/ruby/hyper-model/lib/active_record_base.rb +++ b/ruby/hyper-model/lib/active_record_base.rb @@ -21,7 +21,14 @@ def _synchromesh_scope_args_check(args) 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) + # Ruby 3 / Rails 7+: options must be forwarded as keywords — a positional + # Hash lands in AR's `scope` param and crashes on scope.arity. Accept a + # legacy positional opts hash for backward compatibility. + def has_and_belongs_to_many(other, scope = nil, **opts, &block) + if scope.is_a?(Hash) + opts = scope.merge(opts) + scope = nil + end join_table_name = [other.to_s, table_name].sort.join('_') join_model_name = "HyperstackInternalHabtm#{join_table_name.singularize.camelize}" join_model = @@ -42,7 +49,7 @@ def has_and_belongs_to_many(other, opts = {}, &block) else join_model.table_name = join_table_name join_model.belongs_to other - pre_hyperstack_has_and_belongs_to_many(other, opts, &block) + pre_hyperstack_has_and_belongs_to_many(other, scope, **opts, &block) end end end @@ -281,16 +288,16 @@ def has_many(name, *args, &block) method_defined?(:"__secure_remote_access_to_#{name}"), &method(:regulate_relationship) ) - pre_syncromesh_has_many name, *args, opts.except(:regulate), &block + pre_syncromesh_has_many name, *args, **opts.except(:regulate), &block end %i[belongs_to has_one composed_of].each do |macro| alias_method :"pre_syncromesh_#{macro}", macro - define_method(macro) do |name, *aargs, &block| + define_method(macro) do |name, *aargs, **kwargs, &block| define_method(:"__secure_remote_access_to_#{name}") do |this, _acting_user, *args| this.send(name, *args) end - send(:"pre_syncromesh_#{macro}", name, *aargs, &block) + send(:"pre_syncromesh_#{macro}", name, *aargs, **kwargs, &block) end end end @@ -399,5 +406,6 @@ def __hyperstack_secure_attributes(acting_user) end end - InternalMetadata.do_not_synchronize if defined? InternalMetadata + # Rails 7.1+ changed InternalMetadata to no longer inherit from ActiveRecord::Base + InternalMetadata.do_not_synchronize if defined?(InternalMetadata) && InternalMetadata.respond_to?(:do_not_synchronize) end diff --git a/ruby/hyper-model/lib/hyper-model.rb b/ruby/hyper-model/lib/hyper-model.rb index 93b5e51d0..e7324c702 100644 --- a/ruby/hyper-model/lib/hyper-model.rb +++ b/ruby/hyper-model/lib/hyper-model.rb @@ -62,8 +62,8 @@ require_relative 'active_record_base' require 'hyper_model/version' - Opal.append_path File.expand_path('../sources/', __FILE__).untaint - Opal.append_path File.expand_path('../', __FILE__).untaint - Opal.append_path File.expand_path('../../vendor', __FILE__).untaint + Opal.append_path File.expand_path('../sources/', __FILE__) + Opal.append_path File.expand_path('../', __FILE__) + Opal.append_path File.expand_path('../../vendor', __FILE__) end require 'enumerable/pluck' 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 ded9014f2..f24c0f689 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 @@ -10,18 +10,30 @@ class Base def self.public_columns_hash @@hyper_stack_public_columns_hash_mutex.synchronize do return @public_columns_hash if @public_columns_hash && Rails.env.production? - files = [] + @public_columns_hash = {} 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 - @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' + class_path = file[dir_length..-4] + next if class_path == 'application_record' + # Resolve the constant by name first: when the public-directory file + # is a client-only mirror of a model defined elsewhere (and Zeitwerk + # ignores the mirror), this loads the REAL model instead of + # re-opening it with a mismatched superclass. Classic apps where the + # public file IS the model still autoload it the same way. Only if + # the constant cannot be autoloaded do we require the file itself. + model = + begin + class_path.camelize.constantize + rescue NameError, LoadError + begin + require_dependency(file) + class_path.camelize.constantize + rescue NameError, LoadError + nil + end + end + next unless model.is_a?(Class) && model < ActiveRecord::Base @public_columns_hash[model.name] = model.columns_hash rescue nil # why rescue? end end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/column_types.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/column_types.rb index dd967a255..334b162d9 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/column_types.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/column_types.rb @@ -70,6 +70,11 @@ def convert(attr, val) conversion_method = "convert_#{column_type}" return send(conversion_method, val) if respond_to? conversion_method val + rescue StandardError + # Attribute readers can legitimately return values that don't match the + # column type (state machines, enum overrides, etc). Never let a failed + # conversion abort the merge — keep the server's raw value instead. + val end end end diff --git a/ruby/hyper-model/lib/reactive_record/permissions.rb b/ruby/hyper-model/lib/reactive_record/permissions.rb index d074f5b8d..9b04ba18f 100644 --- a/ruby/hyper-model/lib/reactive_record/permissions.rb +++ b/ruby/hyper-model/lib/reactive_record/permissions.rb @@ -74,12 +74,12 @@ class << self attr_reader :reactive_record_association_keys [:has_many, :belongs_to, :composed_of].each do |macro| - define_method "#{macro}_with_reactive_record_add_changed_method".to_sym do |attr_name, *args, &block| + define_method "#{macro}_with_reactive_record_add_changed_method".to_sym do |attr_name, *args, **kwargs, &block| define_method "#{attr_name}_changed?".to_sym do instance_variable_get "@reactive_record_#{attr_name}_changed".to_sym end (@reactive_record_association_keys ||= []) << attr_name - send "#{macro}_without_reactive_record_add_changed_method".to_sym, attr_name, *args, &block + send "#{macro}_without_reactive_record_add_changed_method".to_sym, attr_name, *args, **kwargs, &block end alias_method "#{macro}_without_reactive_record_add_changed_method".to_sym, macro alias_method macro, "#{macro}_with_reactive_record_add_changed_method".to_sym @@ -87,8 +87,8 @@ class << self alias belongs_to_without_reactive_record_add_is_method belongs_to - def belongs_to(attr_name, *args) - belongs_to_without_reactive_record_add_is_method(attr_name, *args).tap do + def belongs_to(attr_name, *args, **kwargs) + belongs_to_without_reactive_record_add_is_method(attr_name, *args, **kwargs).tap do define_method "#{attr_name}_is?".to_sym do |model| attributes[self.class.reflections[attr_name.to_s].foreign_key] == model.id end diff --git a/ruby/hyper-operation/Gemfile b/ruby/hyper-operation/Gemfile index e25c7a133..74646bbbf 100644 --- a/ruby/hyper-operation/Gemfile +++ b/ruby/hyper-operation/Gemfile @@ -4,4 +4,5 @@ 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 'rack', '< 3' # puma <= 5.4 requires rack < 3 (rack/handler removed in rack 3) gemspec diff --git a/ruby/hyper-operation/hyper-operation.gemspec b/ruby/hyper-operation/hyper-operation.gemspec index 98358a6d8..f54d1abeb 100644 --- a/ruby/hyper-operation/hyper-operation.gemspec +++ b/ruby/hyper-operation/hyper-operation.gemspec @@ -26,7 +26,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'tty-table' 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' @@ -37,13 +36,13 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'puma', '<= 5.4.0' spec.add_development_dependency 'pusher' spec.add_development_dependency 'pusher-fake' - spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 8.1' spec.add_development_dependency 'rake' - spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' + spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 3.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.4.2' # see https://github.com/rails/rails/issues/35153 - spec.add_development_dependency 'timecop', '~> 0.8.1' + spec.add_development_dependency 'sqlite3', '>= 1.4' # was ~> 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 da470051f..c98d07adc 100644 --- a/ruby/hyper-operation/lib/hyper-operation.rb +++ b/ruby/hyper-operation/lib/hyper-operation.rb @@ -59,7 +59,7 @@ def titleize require 'hyper-operation/server_op' require 'hyper-operation/boot' Opal.use_gem 'mutations', false - Opal.append_path File.expand_path('../sources/', __FILE__).untaint - Opal.append_path File.expand_path('../', __FILE__).untaint - Opal.append_path File.expand_path('../../vendor', __FILE__).untaint + Opal.append_path File.expand_path('../sources/', __FILE__) + Opal.append_path File.expand_path('../', __FILE__) + Opal.append_path File.expand_path('../../vendor', __FILE__) end diff --git a/ruby/hyper-operation/lib/hyper-operation/engine.rb b/ruby/hyper-operation/lib/hyper-operation/engine.rb index d7209b5f2..59bb46798 100644 --- a/ruby/hyper-operation/lib/hyper-operation/engine.rb +++ b/ruby/hyper-operation/lib/hyper-operation/engine.rb @@ -7,5 +7,22 @@ class Engine < ::Rails::Engine g.assets false g.helper false end + + # In development, code reloading wipes the regulations that policies + # install on model singleton classes, and Hyperstack.reset_operations + # only const_gets the policy once at boot. Re-reference the policy on + # every prepare cycle so its class body (the regulations) re-runs + # against the freshly reloaded models. Without this, the first code + # edit after boot silently 403s every ReactiveRecord fetch with + # AccessViolation:scoped_permission_not_granted. + config.to_prepare do + %w[ApplicationPolicy Hyperstack::ApplicationPolicy].each do |policy| + begin + Object.const_get policy + rescue LoadError, NameError + nil + 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 index 604ef62f3..c9b3634ae 100644 --- 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 @@ -17,8 +17,8 @@ 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? + def create_table(**kwargs, &block) + connection.create_table(table_name, **kwargs, &block) if needs_init? 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 index 363dc8e11..cae23f963 100644 --- 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 @@ -12,7 +12,7 @@ class QueuedMessage < ::ActiveRecord::Base do_not_synchronize - serialize :data + serialize :data, coder: YAML belongs_to :hyperstack_connection, class_name: 'Hyperstack::ConnectionAdapter::ActiveRecord::Connection', diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb b/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb index 31de6917e..2dec6dada 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb @@ -127,7 +127,7 @@ def self.send_data(channel, data) elsif transport == :pusher pusher.trigger("#{Hyperstack.channel}-#{data[1][:channel].gsub('::', '==')}", *data) elsif transport == :action_cable - ActionCable.server.broadcast("hyperstack-#{channel}", message: data[0], data: data[1]) + ActionCable.server.broadcast("hyperstack-#{channel}", { message: data[0], data: data[1] }) end end @@ -153,7 +153,7 @@ def self.channel end def self.authorization(salt, channel, session_id) - secret_key = Rails.application.secrets[:secret_key_base] + secret_key = Rails.application.secret_key_base Digest::SHA1.hexdigest( "salt: #{salt}, channel: #{channel}, session_id: #{session_id}, secret_key: #{secret_key}" ) diff --git a/ruby/hyper-operation/spec/test_app/config/database.yml b/ruby/hyper-operation/spec/test_app/config/database.yml index 053a2a7bf..a25d807d4 100644 --- a/ruby/hyper-operation/spec/test_app/config/database.yml +++ b/ruby/hyper-operation/spec/test_app/config/database.yml @@ -29,7 +29,10 @@ default: &default adapter: mysql2 encoding: utf8 - username: root + username: <%= ENV['DB_USER'] || 'root' %> + password: <%= ENV['DB_PASSWORD'] || '' %> + host: <%= ENV['DB_HOST'] || '127.0.0.1' %> + port: 3306 development: <<: *default diff --git a/ruby/hyper-router/Gemfile b/ruby/hyper-router/Gemfile index d9fd30e39..dea14ddd0 100644 --- a/ruby/hyper-router/Gemfile +++ b/ruby/hyper-router/Gemfile @@ -5,4 +5,5 @@ gem 'hyperstack-config', path: '../hyperstack-config' gem 'hyper-state', path: '../hyper-state' gem 'hyper-component', path: '../hyper-component' gem 'hyper-store', path: '../hyper-store' +gem 'rack', '< 3' # puma <= 5.4 requires rack < 3 (rack/handler removed in rack 3) gemspec diff --git a/ruby/hyper-router/hyper-router.gemspec b/ruby/hyper-router/hyper-router.gemspec index c6d186d38..ee7f3d45b 100644 --- a/ruby/hyper-router/hyper-router.gemspec +++ b/ruby/hyper-router/hyper-router.gemspec @@ -19,17 +19,16 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-state', HyperRouter::VERSION spec.add_development_dependency 'bundler' - spec.add_development_dependency 'chromedriver-helper' 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.4.0' # something is busted with 0.4.0 and its libv8-node dependency + # spec.add_development_dependency 'mini_racer' # fails to compile on Ubuntu 24.04; Node.js used instead; prerendering_on tests skipped in CI 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 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 8.1' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' @@ -39,6 +38,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec-steps', '~> 2.1.1' spec.add_development_dependency 'shoulda' spec.add_development_dependency 'shoulda-matchers' - 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 'sqlite3', '>= 1.4' # was ~> 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-router/lib/hyper-router.rb b/ruby/hyper-router/lib/hyper-router.rb index c334dddd1..1770ec9ba 100644 --- a/ruby/hyper-router/lib/hyper-router.rb +++ b/ruby/hyper-router/lib/hyper-router.rb @@ -25,5 +25,5 @@ require 'hyperstack/internal/router/isomorphic_methods' require 'hyperstack/router/version' - Opal.append_path File.expand_path('../', __FILE__).untaint + Opal.append_path File.expand_path('../', __FILE__) end diff --git a/ruby/hyper-router/spec/spec_helper.rb b/ruby/hyper-router/spec/spec_helper.rb index b600a261c..6c83ab018 100644 --- a/ruby/hyper-router/spec/spec_helper.rb +++ b/ruby/hyper-router/spec/spec_helper.rb @@ -17,14 +17,20 @@ RSpec.configure do |config| config.before :suite do - MiniRacer_Backup = MiniRacer - Object.send(:remove_const, :MiniRacer) + if defined?(MiniRacer) + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end end config.around(:each, :prerendering_on) do |example| - MiniRacer = MiniRacer_Backup - example.run - Object.send(:remove_const, :MiniRacer) + if defined?(MiniRacer_Backup) + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) + else + skip 'mini_racer not available; skipping prerendering test' + end end config.after :each do diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index 2e11da315..757b8bbc1 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -22,28 +22,26 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'actionview' spec.add_dependency 'capybara' - spec.add_dependency 'chromedriver-helper', '1.2.0' spec.add_dependency 'filecache' spec.add_dependency 'method_source' 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 'selenium-webdriver', '>= 4.11' + spec.add_dependency 'timecop', '>= 0.8.1' spec.add_dependency 'uglifier' - spec.add_dependency 'unparser', '>= 0.4.2' - spec.add_dependency 'webdrivers' + spec.add_dependency 'unparser', '>= 0.4.2', '< 0.6.4' # 0.6.4+ emits Ruby 3.1 shorthand hash syntax ({ foo: }) that Opal cannot parse spec.add_development_dependency 'bundler' spec.add_development_dependency 'hyper-component', HyperSpec::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 'mini_racer' # fails to compile on Ubuntu 24.04; Node.js used instead; prerendering_on tests skipped in CI spec.add_development_dependency 'opal-rails', '>= 0.9.4' 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 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 8.1' spec.add_development_dependency 'rake' - spec.add_development_dependency 'react-rails', '>= 2.3.0', '< 2.5.0' + spec.add_development_dependency 'react-rails', '>= 2.3.0', '< 3.0' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index f1ab52569..28bdf05db 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -4,7 +4,6 @@ require 'unparser' require 'method_source' require 'filecache' -require 'webdrivers' require 'capybara/rspec' @@ -246,6 +245,17 @@ def self.on_server? Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) end + # GitHub Actions: Selenium Manager (selenium-webdriver >= 4.11) finds chromedriver automatically + Capybara.register_driver :chrome_headless_github_actions do |app| + options = ::Selenium::WebDriver::Chrome::Options.new + options.add_argument('--headless') + options.add_argument('--no-sandbox') + options.add_argument('--disable-dev-shm-usage') + options.add_argument('--disable-gpu') + options.add_option('goog:loggingPrefs', { browser: 'WARNING' }) + 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! @@ -275,6 +285,7 @@ def self.on_server? when 'headless' then :selenium_chrome_headless when 'safari' then :safari when 'travis' then :chrome_headless_docker_travis + when 'github' then :chrome_headless_github_actions else :selenium_chrome_headless 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 index 39d3666fb..0a472b9be 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb @@ -30,7 +30,7 @@ def add_promise_execute_and_wait(str, opts) sleep 0.25 end end - JSON.parse(page.evaluate_script('window.hyper_spec_promise_result.$to_json()'), opts).first + JSON.parse(page.evaluate_script('(function(r){return (r && typeof r.$to_json==="function")?r.$to_json():JSON.stringify(r);})(window.hyper_spec_promise_result)'), opts).first end def add_promise_wrapper(str) diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb b/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb index d5f13c3f6..c0b23495e 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb @@ -3,7 +3,7 @@ module Opal # 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)') + .gsub('(Opal);', '(Opal)') # rubocop:disable Lint/RescueException # we are going to reraise it anyway, so its fine to catch EVERYTHING! rescue Exception => e diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb b/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb index 35437a9f2..bb6555006 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb @@ -164,15 +164,13 @@ def run_pending_evaluations # Monkey patches to call our Lolex interface class Timecop - private - def travel(mock_type, *args, &block) raise SafeModeException if Timecop.safe_mode? && !block_given? stack_item = TimeStackItem.new(mock_type, *args) - stack_backup = @_stack.dup - @_stack << stack_item + stack_backup = stack.dup + stack << stack_item Lolex.push(mock_type, *args) @@ -181,25 +179,25 @@ def travel(mock_type, *args, &block) yield stack_item.time ensure Lolex.pop - @_stack.replace stack_backup + set_stack stack_backup end end end def return(&block) - current_stack = @_stack - current_baseline = @baseline + current_stack = stack + current_baseline = baseline unmock! yield ensure Lolex.restore - @_stack = current_stack - @baseline = current_baseline + set_stack current_stack + set_baseline current_baseline end def unmock! #:nodoc: - @baseline = nil - @_stack = [] + set_baseline nil + set_stack [] Lolex.unmock end end diff --git a/ruby/hyper-state/Gemfile b/ruby/hyper-state/Gemfile index 7281f46f2..3362dd0b3 100644 --- a/ruby/hyper-state/Gemfile +++ b/ruby/hyper-state/Gemfile @@ -1,5 +1,6 @@ source 'https://rubygems.org' gem 'hyper-spec', path: '../hyper-spec' +gem 'rack', '< 3' # puma <= 5.4 requires rack < 3 (rack/handler removed in rack 3) gem 'hyperstack-config', path: '../hyperstack-config' gem 'hyper-component', path: '../hyper-component' gemspec diff --git a/ruby/hyper-state/hyper-state.gemspec b/ruby/hyper-state/hyper-state.gemspec index 1917a787a..e09806dec 100644 --- a/ruby/hyper-state/hyper-state.gemspec +++ b/ruby/hyper-state/hyper-state.gemspec @@ -20,7 +20,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyperstack-config', Hyperstack::State::VERSION 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' @@ -30,13 +29,13 @@ Gem::Specification.new do |spec| 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 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 8.1' 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 'react-rails', '>= 2.4.0', '< 3.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.4.2' # see https://github.com/rails/rails/issues/35153 - spec.add_development_dependency 'timecop', '~> 0.8.1' + spec.add_development_dependency 'sqlite3', '>= 1.4' # was ~> 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/hyper-state.rb b/ruby/hyper-state/lib/hyper-state.rb index c5f3e3224..ec44a44af 100644 --- a/ruby/hyper-state/lib/hyper-state.rb +++ b/ruby/hyper-state/lib/hyper-state.rb @@ -16,5 +16,5 @@ require 'ext/object_space' else require 'opal' - Opal.append_path(File.expand_path('../', __FILE__).untaint) + Opal.append_path(File.expand_path('../', __FILE__)) end diff --git a/ruby/hyper-store/Gemfile b/ruby/hyper-store/Gemfile index 60af4a528..0e5133dc2 100644 --- a/ruby/hyper-store/Gemfile +++ b/ruby/hyper-store/Gemfile @@ -3,4 +3,5 @@ gem 'hyper-spec', path: '../hyper-spec' gem 'hyperstack-config', path: '../hyperstack-config' gem 'hyper-component', path: '../hyper-component' gem 'hyper-state', path: '../hyper-state' +gem 'rack', '< 3' # puma <= 5.4 requires rack < 3 (rack/handler removed in rack 3) gemspec diff --git a/ruby/hyper-store/hyper-store.gemspec b/ruby/hyper-store/hyper-store.gemspec index 7fb885a73..a25128bf6 100644 --- a/ruby/hyper-store/hyper-store.gemspec +++ b/ruby/hyper-store/hyper-store.gemspec @@ -21,7 +21,6 @@ Gem::Specification.new do |spec| 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' @@ -31,14 +30,14 @@ Gem::Specification.new do |spec| 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 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 8.1' 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 'react-rails', '>= 2.4.0', '< 3.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.4.2' - spec.add_development_dependency 'timecop', '~> 0.8.1' + spec.add_development_dependency 'sqlite3', '>= 1.4' + spec.add_development_dependency 'timecop', '>= 0.8.1' end diff --git a/ruby/hyper-store/lib/hyper-store.rb b/ruby/hyper-store/lib/hyper-store.rb index 3e6a072b0..1d95db07b 100644 --- a/ruby/hyper-store/lib/hyper-store.rb +++ b/ruby/hyper-store/lib/hyper-store.rb @@ -25,5 +25,5 @@ class BaseStoreClass < BasicObject if RUBY_ENGINE != 'opal' require 'opal' - Opal.append_path(File.expand_path('../', __FILE__).untaint) + Opal.append_path(File.expand_path('../', __FILE__)) end diff --git a/ruby/hyper-trace/hyper-trace.gemspec b/ruby/hyper-trace/hyper-trace.gemspec index 0be02035e..d167e26c3 100644 --- a/ruby/hyper-trace/hyper-trace.gemspec +++ b/ruby/hyper-trace/hyper-trace.gemspec @@ -22,6 +22,5 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyperstack-config', HyperTrace::VERSION spec.add_development_dependency 'hyper-spec', HyperTrace::VERSION spec.add_development_dependency "bundler" - spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency "rake" end diff --git a/ruby/hyper-trace/lib/hyper-trace.rb b/ruby/hyper-trace/lib/hyper-trace.rb index a291bd4ec..30d438d8d 100644 --- a/ruby/hyper-trace/lib/hyper-trace.rb +++ b/ruby/hyper-trace/lib/hyper-trace.rb @@ -4,5 +4,5 @@ require 'hyper_trace/react_trace.rb' else require 'opal' - Opal.append_path File.expand_path('../', __FILE__).untaint + Opal.append_path File.expand_path('../', __FILE__) end diff --git a/ruby/hyperstack-config/Gemfile b/ruby/hyperstack-config/Gemfile index 7b292c5a3..4e7c7cd93 100644 --- a/ruby/hyperstack-config/Gemfile +++ b/ruby/hyperstack-config/Gemfile @@ -1,4 +1,5 @@ source 'https://rubygems.org' gem 'hyper-spec', path: '../hyper-spec' +gem 'rack', '< 3' # puma <= 5.4 requires rack < 3 (rack/handler removed in rack 3) # 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 584a25fd2..9e5d9b37e 100644 --- a/ruby/hyperstack-config/hyperstack-config.gemspec +++ b/ruby/hyperstack-config/hyperstack-config.gemspec @@ -23,22 +23,21 @@ Gem::Specification.new do |spec| 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', ENV['OPAL_VERSION'] || '>= 1.7.0', '< 2.0' 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' - spec.add_development_dependency 'chromedriver-helper' 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 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 8.1' spec.add_development_dependency 'rake' - spec.add_development_dependency 'rspec', '~> 3.7.0' + spec.add_development_dependency 'rspec', '>= 3.7.0' 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' + spec.add_development_dependency 'sqlite3', '>= 1.4' # was ~> 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 f7bca1555..67755b834 100644 --- a/ruby/hyperstack-config/lib/hyperstack-config.rb +++ b/ruby/hyperstack-config/lib/hyperstack-config.rb @@ -58,6 +58,6 @@ def self.naming_convention Hyperstack.import 'hyperstack/autoloader_starter' # based on the environment pick the directory containing the file with the matching # value for the client. This avoids use of ERB for builds outside of sprockets environment - Opal.append_path(File.expand_path("../hyperstack/environment/#{Hyperstack.env}/", __FILE__).untaint) - Opal.append_path(File.expand_path('../', __FILE__).untaint) + Opal.append_path(File.expand_path("../hyperstack/environment/#{Hyperstack.env}/", __FILE__)) + Opal.append_path(File.expand_path('../', __FILE__)) end diff --git a/ruby/hyperstack-config/lib/hyperstack/imports.rb b/ruby/hyperstack-config/lib/hyperstack/imports.rb index bbcad2745..650251c62 100644 --- a/ruby/hyperstack-config/lib/hyperstack/imports.rb +++ b/ruby/hyperstack-config/lib/hyperstack/imports.rb @@ -66,7 +66,7 @@ def handle_webpack def add_inflections(sys) return [] unless sys - ["puts \"require 'config/initializers/inflections.rb'\""] + + ["puts \"require 'config/initializers/inflections.rb'\"", inflections_compatibility_shim] + File.open(Rails.root.join('config', 'initializers', 'inflections.rb'), &:readlines).tap do log_import " require 'config/initializers/inflections.rb'" end @@ -74,6 +74,21 @@ def add_inflections(sys) [] end + # opal-activesupport's Inflections lacks some newer methods (e.g. acronym), + # so a host app's inflections.rb can crash the client bundle at boot. + # Stub the missing pieces before inlining the app's file. + def inflections_compatibility_shim + <<-RUBY + unless ActiveSupport::Inflector::Inflections.method_defined?(:acronym) + class ActiveSupport::Inflector::Inflections + def acronym(word) + (@acronyms ||= {})[word.downcase] = word + end + end + end + RUBY + end + def add_opal(sys) return [] unless sys diff --git a/ruby/hyperstack-config/lib/hyperstack/rail_tie.rb b/ruby/hyperstack-config/lib/hyperstack/rail_tie.rb index acb24120c..f70f4c963 100644 --- a/ruby/hyperstack-config/lib/hyperstack/rail_tie.rb +++ b/ruby/hyperstack-config/lib/hyperstack/rail_tie.rb @@ -44,11 +44,13 @@ def auto_config=(on) # config.assets.paths.delete(hps) # config.assets.paths.unshift(hps) # end - config.assets.paths.unshift ::Rails.root.join('app', 'hyperstack').to_s - if Rails.const_defined? 'Hyperstack::Console' - config.assets.precompile += %w( hyper-console-client.css ) - config.assets.precompile += %w( hyper-console-client.min.js ) - config.assets.precompile += %w( action_cable.js ) if Rails.const_defined? 'ActionCable' + if config.respond_to?(:assets) + config.assets.paths.unshift ::Rails.root.join('app', 'hyperstack').to_s + if Rails.const_defined? 'Hyperstack::Console' + config.assets.precompile += %w( hyper-console-client.css ) + config.assets.precompile += %w( hyper-console-client.min.js ) + config.assets.precompile += %w( action_cable.js ) if Rails.const_defined? 'ActionCable' + end end else delete_first config.eager_load_paths, "#{config.root}/app/hyperstack/models" @@ -63,7 +65,7 @@ def auto_config=(on) delete_first config.autoload_paths, "#{config.root}/app/hyperstack/shared" - delete_first config.assets.paths, ::Rails.root.join('app', 'hyperstack').to_s + delete_first config.assets.paths, ::Rails.root.join('app', 'hyperstack').to_s if config.respond_to?(:assets) end end super @@ -78,6 +80,14 @@ def auto_config=(on) config.hyperstack.auto_config = true end + # Hyperstack components are Opal/client-side code compiled by Sprockets. + # Tell Zeitwerk to ignore them so they are never autoloaded or eager-loaded + # server-side. Without this, CI environments (eager_load=true) alphabetically + # load component files before HyperComponent is defined, causing NameError. + initializer "hyperstack.ignore_client_only_paths" do + Rails.autoloaders.main.ignore(Rails.root.join('app/hyperstack/components')) + end + config.after_initialize do |app| next unless [:on, 'on', true].include?(config.hyperstack.auto_config) # possible alternative way diff --git a/ruby/rails-hyperstack/lib/hyperstack/server_side_auto_require.rb b/ruby/rails-hyperstack/lib/hyperstack/server_side_auto_require.rb index ee500361c..14aeb98e4 100644 --- a/ruby/rails-hyperstack/lib/hyperstack/server_side_auto_require.rb +++ b/ruby/rails-hyperstack/lib/hyperstack/server_side_auto_require.rb @@ -17,15 +17,20 @@ 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) + # require_or_load was removed in Rails 7.2 (only used by the classic autoloader) + if respond_to?(:require_or_load, true) + alias original_require_or_load require_or_load + + def require_or_load(file_name, const_path = nil) + add_server_side_dependency(file_name) do |load_path| + respond_to?(:require_dependency) ? require_dependency(load_path) : require(load_path) + end + original_require_or_load(file_name, const_path) + end end # search the filename path from the end towards the beginning diff --git a/ruby/rails-hyperstack/rails-hyperstack.gemspec b/ruby/rails-hyperstack/rails-hyperstack.gemspec index 498118adc..5ca60d834 100644 --- a/ruby/rails-hyperstack/rails-hyperstack.gemspec +++ b/ruby/rails-hyperstack/rails-hyperstack.gemspec @@ -59,21 +59,20 @@ You can control how much of the stack gets installed as well: spec.add_dependency 'hyper-router', Hyperstack::ROUTERVERSION spec.add_dependency 'hyperstack-config', Hyperstack::VERSION 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 'opal', ENV['OPAL_VERSION'] || '>= 1.7.0', '< 2.0' + spec.add_dependency 'react-rails', '>= 2.4.0', '< 3.0' # 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_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 8.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', '<= 5.4.0' spec.add_development_dependency 'bootsnap' 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 'sqlite3', '>= 1.4' # was ~> 1.4, 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'