diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 988cb8083a..1199051de2 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -130,7 +130,7 @@ jobs: echo "Node version: "; node -v echo "Yarn version: "; yarn --version echo "Bundler version: "; bundle --version - - name: run conversion script to support shakapacker v6 + - name: Run conversion script for older Node compatibility if: matrix.dependency-level == 'minimum' run: script/convert - name: Save root ruby gems to cache @@ -172,8 +172,12 @@ jobs: - name: Set packer version environment variable run: | echo "CI_DEPENDENCY_LEVEL=${{ matrix.dependency-level }}" >> $GITHUB_ENV - - name: Main CI - run: cd react_on_rails && bundle exec rake run_rspec:shakapacker_examples + - name: Main CI - Latest version examples + if: matrix.dependency-level == 'latest' + run: cd react_on_rails && bundle exec rake run_rspec:shakapacker_examples_latest + - name: Main CI - Minimum version examples + if: matrix.dependency-level == 'minimum' + run: cd react_on_rails && bundle exec rake run_rspec:shakapacker_examples_minimum - name: Store test results uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 470bba70c8..ef0e85d076 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -132,7 +132,7 @@ jobs: echo "Node version: "; node -v echo "Yarn version: "; yarn --version echo "Bundler version: "; bundle --version - - name: run conversion script to support shakapacker v6 + - name: Run conversion script for older Node compatibility if: matrix.dependency-level == 'minimum' run: script/convert - name: Install Node modules with Yarn for renderer package @@ -211,7 +211,7 @@ jobs: echo "Node version: "; node -v echo "Yarn version: "; yarn --version echo "Bundler version: "; bundle --version - - name: run conversion script to support shakapacker v6 + - name: Run conversion script for older Node compatibility if: matrix.dependency-level == 'minimum' run: script/convert - name: Save root ruby gems to cache diff --git a/package.json b/package.json index b73cf95234..9585ab1e4e 100644 --- a/package.json +++ b/package.json @@ -55,8 +55,8 @@ "prettier": "^3.5.2", "prop-types": "^15.8.1", "publint": "^0.3.4", - "react": "18.0.0", - "react-dom": "18.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-on-rails-rsc": "19.0.2", "redux": "^4.2.1", "stylelint": "^16.14.0", diff --git a/react_on_rails/rakelib/example_type.rb b/react_on_rails/rakelib/example_type.rb index 800889884a..33de6f8a86 100644 --- a/react_on_rails/rakelib/example_type.rb +++ b/react_on_rails/rakelib/example_type.rb @@ -13,12 +13,22 @@ def self.all @all ||= { shakapacker_examples: [] } end - attr_reader :packer_type, :name, :generator_options + # Minimum supported versions for compatibility testing + MINIMUM_REACT_VERSION = "18.0.0" + MINIMUM_SHAKAPACKER_VERSION = "8.2.0" - def initialize(packer_type: nil, name: nil, generator_options: nil) + attr_reader :packer_type, :name, :generator_options, :minimum_versions + + # Ruby convention: predicate method with ? suffix for boolean checks + def minimum_versions? + minimum_versions + end + + def initialize(packer_type: nil, name: nil, generator_options: nil, minimum_versions: false) @packer_type = packer_type @name = name @generator_options = generator_options + @minimum_versions = minimum_versions self.class.all[packer_type.to_sym] << self end diff --git a/react_on_rails/rakelib/examples_config.yml b/react_on_rails/rakelib/examples_config.yml index 731c9d9725..ec6a11b3cf 100644 --- a/react_on_rails/rakelib/examples_config.yml +++ b/react_on_rails/rakelib/examples_config.yml @@ -7,3 +7,10 @@ example_type_data: generator_options: --redux - name: redux-server-rendering generator_options: --redux --example-server-rendering + # Minimum version compatibility tests - tests React 18 and Shakapacker 8.2.0 + - name: basic-minimum + generator_options: '' + minimum_versions: true + - name: basic-server-rendering-minimum + generator_options: --example-server-rendering + minimum_versions: true diff --git a/react_on_rails/rakelib/run_rspec.rake b/react_on_rails/rakelib/run_rspec.rake index 88004f2073..5f45682284 100644 --- a/react_on_rails/rakelib/run_rspec.rake +++ b/react_on_rails/rakelib/run_rspec.rake @@ -90,6 +90,25 @@ namespace :run_rspec do ExampleType.all[:shakapacker_examples].each { |example_type| Rake::Task[example_type.rspec_task_name].invoke } end + # Helper methods for filtering examples + def latest_examples + ExampleType.all[:shakapacker_examples].reject(&:minimum_versions?) + end + + def minimum_examples + ExampleType.all[:shakapacker_examples].select(&:minimum_versions?) + end + + desc "Runs Rspec for latest version example apps only (excludes minimum version tests)" + task shakapacker_examples_latest: latest_examples.map(&:gen_task_name) do + latest_examples.each { |example_type| Rake::Task[example_type.rspec_task_name].invoke } + end + + desc "Runs Rspec for minimum version example apps only (React 18, Shakapacker 8.2.0)" + task shakapacker_examples_minimum: minimum_examples.map(&:gen_task_name) do + minimum_examples.each { |example_type| Rake::Task[example_type.rspec_task_name].invoke } + end + Coveralls::RakeTask.new if ENV["USE_COVERALLS"] == "TRUE" desc "run all tests no examples" diff --git a/react_on_rails/rakelib/shakapacker_examples.rake b/react_on_rails/rakelib/shakapacker_examples.rake index 5f5f1bb864..3d121e0a1e 100644 --- a/react_on_rails/rakelib/shakapacker_examples.rake +++ b/react_on_rails/rakelib/shakapacker_examples.rake @@ -8,6 +8,7 @@ require "yaml" require "rails/version" require "pathname" +require "json" require_relative "example_type" require_relative "task_helpers" @@ -15,8 +16,64 @@ require_relative "task_helpers" namespace :shakapacker_examples do # rubocop:disable Metrics/BlockLength include ReactOnRails::TaskHelpers + # Updates package.json and Gemfile to use minimum supported versions for compatibility testing + # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity + def apply_minimum_versions(dir) + # Update package.json + package_json_path = File.join(dir, "package.json") + if File.exist?(package_json_path) + begin + package_json = JSON.parse(File.read(package_json_path)) + rescue JSON::ParserError => e + puts " ERROR: Failed to parse package.json in #{dir}: #{e.message}" + raise + end + + deps = package_json["dependencies"] + dev_deps = package_json["devDependencies"] + + # Update React versions to minimum supported + if deps + deps["react"] = ExampleType::MINIMUM_REACT_VERSION + deps["react-dom"] = ExampleType::MINIMUM_REACT_VERSION + # Shakapacker 8.2.0 requires webpack-assets-manifest ^5.x + deps["webpack-assets-manifest"] = "^5.0.6" if deps.key?("webpack-assets-manifest") + end + + # Shakapacker 8.2.0 requires webpack-assets-manifest ^5.x (check devDependencies too) + dev_deps["webpack-assets-manifest"] = "^5.0.6" if dev_deps&.key?("webpack-assets-manifest") + + # Update Shakapacker to minimum supported version in package.json + if dev_deps&.key?("shakapacker") + dev_deps["shakapacker"] = ExampleType::MINIMUM_SHAKAPACKER_VERSION + elsif deps&.key?("shakapacker") + deps["shakapacker"] = ExampleType::MINIMUM_SHAKAPACKER_VERSION + end + + File.write(package_json_path, "#{JSON.pretty_generate(package_json)}\n") + end + + # Update Gemfile to pin shakapacker to minimum version + # (must match the npm package version exactly) + gemfile_path = File.join(dir, "Gemfile") + if File.exist?(gemfile_path) + gemfile_content = File.read(gemfile_path) + # Replace any shakapacker gem line with exact version pin + gemfile_content = gemfile_content.gsub( + /gem ['"]shakapacker['"].*$/, + "gem 'shakapacker', '#{ExampleType::MINIMUM_SHAKAPACKER_VERSION}'" + ) + File.write(gemfile_path, gemfile_content) + end + + puts " Updated package.json with minimum versions:" + puts " React: #{ExampleType::MINIMUM_REACT_VERSION}" + puts " Shakapacker: #{ExampleType::MINIMUM_SHAKAPACKER_VERSION}" + end + # rubocop:enable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity + # Define tasks for each example type - ExampleType.all[:shakapacker_examples].each do |example_type| + ExampleType.all[:shakapacker_examples].each do |example_type| # rubocop:disable Metrics/BlockLength relative_gem_root = Pathname(gem_root).relative_path_from(Pathname(example_type.dir)) # CLOBBER desc "Clobbers (deletes) #{example_type.name_pretty}" @@ -46,10 +103,28 @@ namespace :shakapacker_examples do # rubocop:disable Metrics/BlockLength "REACT_ON_RAILS_SKIP_VALIDATION=true #{cmd}" end sh_in_dir(example_type.dir, generator_commands) + + # Apply minimum versions for compatibility testing examples + if example_type.minimum_versions + apply_minimum_versions(example_type.dir) + # Re-run bundle install since Gemfile was updated with pinned shakapacker version + bundle_install_in(example_type.dir) + end + sh_in_dir(example_type.dir, "npm install") # Generate the component packs after running the generator to ensure all - # auto-bundled components have corresponding pack files created - sh_in_dir(example_type.dir, "bundle exec rake react_on_rails:generate_packs") + # auto-bundled components have corresponding pack files created. + if example_type.minimum_versions + # For minimum version examples, use BUNDLE_GEMFILE to ensure bundler uses + # the generated app's Gemfile and not any parent workspace's bundle, + # which could have different gem versions. Also use BUNDLE_FROZEN=false + # to allow the lockfile to be updated. + gemfile_path = File.join(example_type.dir, "Gemfile") + sh_in_dir(example_type.dir, + "BUNDLE_GEMFILE=#{gemfile_path} BUNDLE_FROZEN=false bundle exec rake react_on_rails:generate_packs") + else + sh_in_dir(example_type.dir, "bundle exec rake react_on_rails:generate_packs") + end end end diff --git a/react_on_rails/spec/dummy/Gemfile.lock b/react_on_rails/spec/dummy/Gemfile.lock index eb919c4013..ce0a77dffb 100644 --- a/react_on_rails/spec/dummy/Gemfile.lock +++ b/react_on_rails/spec/dummy/Gemfile.lock @@ -345,7 +345,7 @@ GEM rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) semantic_range (3.1.0) - shakapacker (9.3.0) + shakapacker (9.4.0) activesupport (>= 5.2) package_json rack-proxy (>= 0.6.1) @@ -461,7 +461,7 @@ DEPENDENCIES sass-rails (~> 6.0) sdoc selenium-webdriver (= 4.9.0) - shakapacker (= 9.3.0) + shakapacker (= 9.4.0) spring (~> 4.0) sprockets (~> 4.0) sqlite3 (~> 1.6) diff --git a/react_on_rails/spec/dummy/client/app-react16/startup/ReduxApp.client.jsx b/react_on_rails/spec/dummy/client/app-react16/startup/ReduxApp.client.jsx index b7ab770286..a96f624be7 100644 --- a/react_on_rails/spec/dummy/client/app-react16/startup/ReduxApp.client.jsx +++ b/react_on_rails/spec/dummy/client/app-react16/startup/ReduxApp.client.jsx @@ -5,7 +5,7 @@ import React from 'react'; import { combineReducers, applyMiddleware, createStore } from 'redux'; import { Provider } from 'react-redux'; -import thunkMiddleware from 'redux-thunk'; +import { thunk } from 'redux-thunk'; import ReactDOM from 'react-dom'; import reducers from '../../app/reducers/reducersIndex'; @@ -29,7 +29,7 @@ export default (props, railsContext, domNodeId) => { // This is where we'll put in the middleware for the async function. Placeholder. // store will have helloWorldData as a top level property - const store = createStore(combinedReducer, combinedProps, applyMiddleware(thunkMiddleware)); + const store = createStore(combinedReducer, combinedProps, applyMiddleware(thunk)); // renderApp is a function required for hot reloading. see // https://github.com/retroalgic/react-on-rails-hot-minimal/blob/master/client/src/entry.js diff --git a/react_on_rails/spec/dummy/client/app/components/ReactHelmet.jsx b/react_on_rails/spec/dummy/client/app/components/ReactHelmet.jsx index bba49492df..ca82611935 100644 --- a/react_on_rails/spec/dummy/client/app/components/ReactHelmet.jsx +++ b/react_on_rails/spec/dummy/client/app/components/ReactHelmet.jsx @@ -1,7 +1,10 @@ import React from 'react'; -import { Helmet } from 'react-helmet'; +import { Helmet } from '@dr.pogodin/react-helmet'; import HelloWorld from '../startup/HelloWorld'; +// Note: This component expects to be wrapped in a HelmetProvider by its parent. +// For client-side rendering, wrap in HelmetProvider at the app root. +// For server-side rendering, the server entry point provides the HelmetProvider. const ReactHelmet = (props) => (
diff --git a/react_on_rails/spec/dummy/client/app/startup/ReactHelmetApp.client.jsx b/react_on_rails/spec/dummy/client/app/startup/ReactHelmetApp.client.jsx index 2c5f342d92..4dd6c26570 100644 --- a/react_on_rails/spec/dummy/client/app/startup/ReactHelmetApp.client.jsx +++ b/react_on_rails/spec/dummy/client/app/startup/ReactHelmetApp.client.jsx @@ -1,11 +1,17 @@ // Top level component for simple client side only rendering import React from 'react'; +import { HelmetProvider } from '@dr.pogodin/react-helmet'; import ReactHelmet from '../components/ReactHelmet'; // This works fine, React functional component: // export default (props) => ; -export default (props) => ; +// HelmetProvider is required by @dr.pogodin/react-helmet for both client and server rendering +export default (props) => ( + + + +); // Note, the server side has to be a Render-Function diff --git a/react_on_rails/spec/dummy/client/app/startup/ReactHelmetApp.server.jsx b/react_on_rails/spec/dummy/client/app/startup/ReactHelmetApp.server.jsx index f7c77e8a73..6546457bca 100644 --- a/react_on_rails/spec/dummy/client/app/startup/ReactHelmetApp.server.jsx +++ b/react_on_rails/spec/dummy/client/app/startup/ReactHelmetApp.server.jsx @@ -1,8 +1,8 @@ // Top level component for simple client side only rendering import React from 'react'; import { renderToString } from 'react-dom/server'; -import { Helmet } from 'react-helmet'; -import ReactHelmet from '../components/ReactHelmet'; +import { Helmet, HelmetProvider } from '@dr.pogodin/react-helmet'; +import HelloWorld from './HelloWorld'; /* * Export a function that takes the props and returns an object with { renderedHtml } @@ -16,12 +16,27 @@ import ReactHelmet from '../components/ReactHelmet'; * the function could get the property of `.renderFunction = true` added to it. */ export default (props, _railsContext) => { - const componentHtml = renderToString(); - const helmet = Helmet.renderStatic(); + // For server-side rendering with @dr.pogodin/react-helmet, we pass a context object + // to HelmetProvider to capture the helmet data per-request (thread-safe) + const helmetContext = {}; + + const componentHtml = renderToString( + +
+ + Custom page title + + Props: {JSON.stringify(props)} + +
+
, + ); + + const { helmet } = helmetContext; const renderedHtml = { componentHtml, - title: helmet.title.toString(), + title: helmet ? helmet.title.toString() : '', }; // Note that this function returns an Object for server rendering. diff --git a/react_on_rails/spec/dummy/client/app/startup/ReactHelmetAppBroken.client.jsx b/react_on_rails/spec/dummy/client/app/startup/ReactHelmetAppBroken.client.jsx index 2c5f342d92..4dd6c26570 100644 --- a/react_on_rails/spec/dummy/client/app/startup/ReactHelmetAppBroken.client.jsx +++ b/react_on_rails/spec/dummy/client/app/startup/ReactHelmetAppBroken.client.jsx @@ -1,11 +1,17 @@ // Top level component for simple client side only rendering import React from 'react'; +import { HelmetProvider } from '@dr.pogodin/react-helmet'; import ReactHelmet from '../components/ReactHelmet'; // This works fine, React functional component: // export default (props) => ; -export default (props) => ; +// HelmetProvider is required by @dr.pogodin/react-helmet for both client and server rendering +export default (props) => ( + + + +); // Note, the server side has to be a Render-Function diff --git a/react_on_rails/spec/dummy/client/app/startup/ReactHelmetAppBroken.server.jsx b/react_on_rails/spec/dummy/client/app/startup/ReactHelmetAppBroken.server.jsx index d1c72af50d..f0d68d3e18 100644 --- a/react_on_rails/spec/dummy/client/app/startup/ReactHelmetAppBroken.server.jsx +++ b/react_on_rails/spec/dummy/client/app/startup/ReactHelmetAppBroken.server.jsx @@ -3,7 +3,7 @@ // function. The point of this is to provide a good error. import React from 'react'; import { renderToString } from 'react-dom/server'; -import { Helmet } from 'react-helmet'; +import { HelmetProvider } from '@dr.pogodin/react-helmet'; import ReactHelmet from '../components/ReactHelmet'; /* @@ -18,12 +18,17 @@ import ReactHelmet from '../components/ReactHelmet'; * Alternately, the function could get the property of `.renderFunction = true` added to it. */ export default (props) => { - const componentHtml = renderToString(); - const helmet = Helmet.renderStatic(); + const helmetContext = {}; + const componentHtml = renderToString( + + + , + ); + const { helmet } = helmetContext; const renderedHtml = { componentHtml, - title: helmet.title.toString(), + title: helmet ? helmet.title.toString() : '', }; return { renderedHtml }; }; diff --git a/react_on_rails/spec/dummy/client/app/startup/ReduxApp.client.jsx b/react_on_rails/spec/dummy/client/app/startup/ReduxApp.client.jsx index 3e8ddc211b..cfe9e24a93 100644 --- a/react_on_rails/spec/dummy/client/app/startup/ReduxApp.client.jsx +++ b/react_on_rails/spec/dummy/client/app/startup/ReduxApp.client.jsx @@ -5,7 +5,7 @@ import React from 'react'; import { combineReducers, applyMiddleware, createStore } from 'redux'; import { Provider } from 'react-redux'; -import thunkMiddleware from 'redux-thunk'; +import { thunk } from 'redux-thunk'; import ReactDOMClient from 'react-dom/client'; import reducers from '../reducers/reducersIndex'; @@ -34,7 +34,7 @@ export default (props, railsContext, domNodeId) => { // This is where we'll put in the middleware for the async function. Placeholder. // store will have helloWorldData as a top level property - const store = createStore(combinedReducer, combinedProps, applyMiddleware(thunkMiddleware)); + const store = createStore(combinedReducer, combinedProps, applyMiddleware(thunk)); // renderApp is a function required for hot reloading. see // https://github.com/retroalgic/react-on-rails-hot-minimal/blob/master/client/src/entry.js diff --git a/react_on_rails/spec/dummy/client/app/startup/ReduxApp.server.jsx b/react_on_rails/spec/dummy/client/app/startup/ReduxApp.server.jsx index bdcc317962..a26b2969f0 100644 --- a/react_on_rails/spec/dummy/client/app/startup/ReduxApp.server.jsx +++ b/react_on_rails/spec/dummy/client/app/startup/ReduxApp.server.jsx @@ -6,7 +6,7 @@ import React from 'react'; import { combineReducers, applyMiddleware, createStore } from 'redux'; import { Provider } from 'react-redux'; -import middleware from 'redux-thunk'; +import { thunk } from 'redux-thunk'; // Uses the index import reducers from '../reducers/reducersIndex'; @@ -28,7 +28,7 @@ export default (props, railsContext) => { // This is where we'll put in the middleware for the async function. Placeholder. // store will have helloWorldData as a top level property - const store = applyMiddleware(middleware)(createStore)(combinedReducer, combinedProps); + const store = applyMiddleware(thunk)(createStore)(combinedReducer, combinedProps); // Provider uses the this.props.children, so we're not typical React syntax. // This allows redux to add additional props to the HelloWorldContainer. diff --git a/react_on_rails/spec/dummy/client/app/stores/SharedReduxStore.jsx b/react_on_rails/spec/dummy/client/app/stores/SharedReduxStore.jsx index 33dcc680ed..3d463ff891 100644 --- a/react_on_rails/spec/dummy/client/app/stores/SharedReduxStore.jsx +++ b/react_on_rails/spec/dummy/client/app/stores/SharedReduxStore.jsx @@ -1,5 +1,5 @@ import { combineReducers, applyMiddleware, createStore } from 'redux'; -import middleware from 'redux-thunk'; +import { thunk } from 'redux-thunk'; import reducers from '../reducers/reducersIndex'; @@ -12,5 +12,5 @@ export default (props, railsContext) => { delete props.prerender; const combinedReducer = combineReducers(reducers); const newProps = { ...props, railsContext }; - return applyMiddleware(middleware)(createStore)(combinedReducer, newProps); + return applyMiddleware(thunk)(createStore)(combinedReducer, newProps); }; diff --git a/react_on_rails/spec/dummy/package.json b/react_on_rails/spec/dummy/package.json index 752ccef475..b3602e226f 100644 --- a/react_on_rails/spec/dummy/package.json +++ b/react_on_rails/spec/dummy/package.json @@ -18,14 +18,14 @@ "node-libs-browser": "^2.2.1", "null-loader": "^4.0.0", "prop-types": "^15.7.2", - "react": "18.0.0", - "react-dom": "18.0.0", - "react-helmet": "^6.1.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "@dr.pogodin/react-helmet": "^3.0.4", "react-on-rails": "link:.yalc/react-on-rails", - "react-redux": "^8.0.2", + "react-redux": "^9.2.0", "react-router-dom": "^6.0.0", - "redux": "^4.0.1", - "redux-thunk": "^2.2.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", "regenerator-runtime": "^0.13.4" }, "devDependencies": { @@ -38,7 +38,6 @@ "@rescript/react": "^0.13.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", - "@types/react-helmet": "^6.1.5", "babel-loader": "8.2.4", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "compression-webpack-plugin": "9", diff --git a/react_on_rails/spec/dummy/yarn.lock b/react_on_rails/spec/dummy/yarn.lock index b7fc70ea04..281c1ff57c 100644 --- a/react_on_rails/spec/dummy/yarn.lock +++ b/react_on_rails/spec/dummy/yarn.lock @@ -1008,13 +1008,18 @@ "@babel/plugin-transform-react-jsx-source" "^7.10.4" "@babel/plugin-transform-react-pure-annotations" "^7.10.4" -"@babel/runtime@7.17.9", "@babel/runtime@^7.12.1", "@babel/runtime@^7.8.4": +"@babel/runtime@7.17.9", "@babel/runtime@^7.8.4": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.28.4": + version "7.28.4" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" + integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== + "@babel/template@^7.16.7", "@babel/template@^7.18.10", "@babel/template@^7.26.9", "@babel/template@^7.27.0", "@babel/template@^7.3.3": version "7.27.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.0.tgz#b253e5406cc1df1c57dcd18f11760c2dbf40c0b4" @@ -1055,6 +1060,13 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f" integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA== +"@dr.pogodin/react-helmet@^3.0.4": + version "3.0.4" + resolved "https://registry.npmjs.org/@dr.pogodin/react-helmet/-/react-helmet-3.0.4.tgz#d11d763fe07a2a11329438c2f892cd15c30239a9" + integrity sha512-TesfNpzO12qcbyqKyWGDIYTdwVxD3pJv75rE/zhKUq/k9yxeP0BpOdHQ5cc1zA3j/GyY7CuIZjAUXmsxqI1/yw== + dependencies: + "@babel/runtime" "^7.28.4" + "@hotwired/turbo-rails@^8.0.4": version "8.0.5" resolved "https://registry.yarnpkg.com/@hotwired/turbo-rails/-/turbo-rails-8.0.5.tgz#18c2f0e4f7f952307650308590edf5eb9544b0d3" @@ -1479,14 +1491,6 @@ dependencies: "@types/node" "*" -"@types/hoist-non-react-statics@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" - integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== - dependencies: - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - "@types/http-proxy@^1.17.8": version "1.17.9" resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" @@ -1543,14 +1547,7 @@ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.3.tgz#0804dfd279a165d5a0ad8b53a5b9e65f338050a4" integrity sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA== -"@types/react-helmet@^6.1.5": - version "6.1.5" - resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.5.tgz#35f89a6b1646ee2bc342a33a9a6c8777933f9083" - integrity sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@^19.0.0": +"@types/react@^19.0.0": version "19.0.7" resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.7.tgz#c451968b999d1cb2d9207dc5ff56496164cf511d" integrity sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA== @@ -1589,10 +1586,10 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== -"@types/use-sync-external-store@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" - integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== +"@types/use-sync-external-store@^0.0.6": + version "0.0.6" + resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" + integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== "@types/ws@^8.5.1": version "8.5.4" @@ -3474,13 +3471,6 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.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" - hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" @@ -4425,7 +4415,7 @@ lodash@^4.17.19, lodash@^4.17.4: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.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== @@ -5198,30 +5188,14 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -react-dom@18.0.0: - version "18.0.0" - resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.0.0.tgz#26b88534f8f1dbb80853e1eabe752f24100d8023" - integrity sha512-XqX7uzmFo0pUceWFCt7Gff6IyIMzFUn7QMZrbrQfGxtaxXZIcGQzoNpRLE3fQLnS4XzLLPMZX2T9TRcSrasicw== +react-dom@^19.0.0: + version "19.2.0" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz#00ed1e959c365e9a9d48f8918377465466ec3af8" + integrity sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ== dependencies: - loose-envify "^1.1.0" - scheduler "^0.21.0" - -react-fast-compare@^3.1.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== + scheduler "^0.27.0" -react-helmet@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" - integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== - dependencies: - object-assign "^4.1.1" - prop-types "^15.7.2" - react-fast-compare "^3.1.1" - react-side-effect "^2.1.0" - -react-is@^16.7.0, react-is@^16.8.1: +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== @@ -5235,17 +5209,13 @@ react-is@^18.0.0: version "0.0.0" uid "" -react-redux@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.2.tgz#bc2a304bb21e79c6808e3e47c50fe1caf62f7aad" - integrity sha512-nBwiscMw3NoP59NFCXFf02f8xdo+vSHT/uZ1ldDwF7XaTpzm+Phk97VT4urYBl5TYAPNVaFm12UHAEyzkpNzRA== +react-redux@^9.2.0: + version "9.2.0" + resolved "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz#96c3ab23fb9a3af2cb4654be4b51c989e32366f5" + integrity sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g== dependencies: - "@babel/runtime" "^7.12.1" - "@types/hoist-non-react-statics" "^3.3.1" - "@types/use-sync-external-store" "^0.0.3" - hoist-non-react-statics "^3.3.2" - react-is "^18.0.0" - use-sync-external-store "^1.0.0" + "@types/use-sync-external-store" "^0.0.6" + use-sync-external-store "^1.4.0" react-refresh@^0.11.0: version "0.11.0" @@ -5267,17 +5237,10 @@ react-router@6.30.1: dependencies: "@remix-run/router" "1.23.0" -react-side-effect@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.0.tgz#1ce4a8b4445168c487ed24dab886421f74d380d3" - integrity sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg== - -react@18.0.0: - version "18.0.0" - resolved "https://registry.npmjs.org/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" - integrity sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A== - dependencies: - loose-envify "^1.1.0" +react@^19.0.0: + version "19.2.0" + resolved "https://registry.npmjs.org/react/-/react-19.2.0.tgz#d33dd1721698f4376ae57a54098cb47fc75d93a5" + integrity sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ== readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.7" @@ -5324,18 +5287,15 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" -redux-thunk@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" - integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== +redux-thunk@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3" + integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== -redux@^4.0.1: - version "4.0.5" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" - integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== - dependencies: - loose-envify "^1.4.0" - symbol-observable "^1.2.0" +redux@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" + integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== regenerate-unicode-properties@^9.0.0: version "9.0.0" @@ -5535,12 +5495,10 @@ sass@^1.43.4: dependencies: chokidar ">=3.0.0 <4.0.0" -scheduler@^0.21.0: - version "0.21.0" - resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz#6fd2532ff5a6d877b6edb12f00d8ab7e8f308820" - integrity sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ== - dependencies: - loose-envify "^1.1.0" +scheduler@^0.27.0: + version "0.27.0" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" + integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== schema-utils@^2.6.5: version "2.7.0" @@ -5999,11 +5957,6 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -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@^2.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -6213,10 +6166,10 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" -use-sync-external-store@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" - integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== +use-sync-external-store@^1.4.0: + version "1.6.0" + resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== use@^3.1.0: version "3.1.1" diff --git a/script/convert b/script/convert index b99413323f..45e1e8d35f 100755 --- a/script/convert +++ b/script/convert @@ -1,6 +1,14 @@ #!/usr/bin/env ruby # frozen_string_literal: true +# This script converts the codebase to use minimum supported dependency versions. +# It's run during CI when testing the "minimum" dependency matrix to ensure +# backward compatibility. +# +# React/Shakapacker version compatibility is now tested through generator smoke tests +# (see examples_config.yml for minimum version examples like basic-minimum). +# This script only handles Node.js/tooling compatibility and Pro package test adjustments. + def gsub_file_content(path, old_content, new_content) path = File.expand_path(path, __dir__) @@ -17,19 +25,6 @@ def gsub_file_content(path, old_content, new_content) File.binwrite(path, content) end -def move(old_path, new_path) - old_path = File.expand_path(old_path, __dir__) - new_path = File.expand_path(new_path, __dir__) - File.rename(old_path, new_path) -end - -# Keep shakapacker.yml since we're using Shakapacker 8+ -# move("../react_on_rails/spec/dummy/config/shakapacker.yml", "../react_on_rails/spec/dummy/config/webpacker.yml") - -# Shakapacker - use version with async script loading support (8.2.0+) -gsub_file_content("../react_on_rails/Gemfile.development_dependencies", /gem "shakapacker", "[^"]*"/, 'gem "shakapacker", "8.2.0"') -gsub_file_content("../react_on_rails/spec/dummy/package.json", /"shakapacker": "[^"]*",/, '"shakapacker": "8.2.0",') - # The below packages don't work on the oldest supported Node version and aren't needed there anyway # Note: All dev dependencies remain in root package.json even after workspace migration gsub_file_content("../package.json", /"[^"]*eslint[^"]*": "[^"]*",?/, "") @@ -42,35 +37,16 @@ gsub_file_content("../package.json", %r{"@testing-library/[^"]*": "[^"]*",}, "") # Clean up any trailing commas before closing braces gsub_file_content("../package.json", /,(\s*})/, "\\1") -# Switch to minimum supported React version (React 18 since we removed PropTypes) -gsub_file_content("../package.json", /"react": "[^"]*",/, '"react": "18.0.0",') -gsub_file_content("../package.json", /"react-dom": "[^"]*",/, '"react-dom": "18.0.0",') -gsub_file_content("../react_on_rails/spec/dummy/package.json", /"react": "[^"]*",/, '"react": "18.0.0",') -gsub_file_content("../react_on_rails/spec/dummy/package.json", /"react-dom": "[^"]*",/, '"react-dom": "18.0.0",') +# Pro package: Skip RSC tests on React 18 since RSC requires React 19 gsub_file_content( "../packages/react-on-rails-pro/package.json", /"test:non-rsc": "(?:\\"|[^"])*",/, '"test:non-rsc": "jest tests --testPathIgnorePatterns=\".*(RSC|stream|' \ 'registerServerComponent|serverRenderReactComponent|SuspenseHydration).*\"",' ) -# Make test:rsc script do nothing +# Make test:rsc script do nothing on React 18 gsub_file_content( "../packages/react-on-rails-pro/package.json", /"test:rsc": "(?:\\"|[^"])*",/, '"test:rsc": "exit 0",' ) -# Keep modern JSX transform for React 18+ -# gsub_file_content("../tsconfig.json", "react-jsx", "react") -# gsub_file_content("../spec/dummy/babel.config.js", "runtime: 'automatic'", "runtime: 'classic'") -# Keep modern ReScript configuration for React 18+ -# gsub_file_content("../spec/dummy/rescript.json", '"version": 4', '"version": 4, "mode": "classic"') -# Skip React 16 file replacements since we're using React 18+ -# Dir.glob(File.expand_path("../spec/dummy/**/app-react16/**/*.*", __dir__)).each do |file| -# move(file, file.gsub("-react16", "")) -# end - -# These replacements were incorrect - generateWebpackConfig() is the correct function from shakapacker -# gsub_file_content("../spec/dummy/config/webpack/commonWebpackConfig.js", /generateWebpackConfig(\(\))?/, -# "webpackConfig") -# -# gsub_file_content("../spec/dummy/config/webpack/webpack.config.js", /generateWebpackConfig(\(\))?/, "webpackConfig") diff --git a/yarn.lock b/yarn.lock index f2d38fd05c..8c5de04e67 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6272,7 +6272,7 @@ lodash@^4.17.4, lodash@^4.7.0: resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7218,13 +7218,12 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" -react-dom@18.0.0: - version "18.0.0" - resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.0.0.tgz#26b88534f8f1dbb80853e1eabe752f24100d8023" - integrity sha512-XqX7uzmFo0pUceWFCt7Gff6IyIMzFUn7QMZrbrQfGxtaxXZIcGQzoNpRLE3fQLnS4XzLLPMZX2T9TRcSrasicw== +react-dom@^19.0.0: + version "19.2.0" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz#00ed1e959c365e9a9d48f8918377465466ec3af8" + integrity sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ== dependencies: - loose-envify "^1.1.0" - scheduler "^0.21.0" + scheduler "^0.27.0" react-is@^16.13.1: version "16.13.1" @@ -7255,12 +7254,10 @@ react-on-rails@*: resolved "https://registry.npmjs.org/react-on-rails/-/react-on-rails-16.1.2.tgz#27308d7d5806e73354eea2355c5c4036ade443d4" integrity sha512-SiE168FgUtp9Sld2MAZUs2+R/D7B+SlNGX4zl9T2rB1QluzgTY042a4/i6v+ivQpBIhNjJujlVFIHYp+uVqjoQ== -react@18.0.0: - version "18.0.0" - resolved "https://registry.npmjs.org/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" - integrity sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A== - dependencies: - loose-envify "^1.1.0" +react@^19.0.0: + version "19.2.0" + resolved "https://registry.npmjs.org/react/-/react-19.2.0.tgz#d33dd1721698f4376ae57a54098cb47fc75d93a5" + integrity sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ== readline-sync@^1.4.7: version "1.4.10" @@ -7538,12 +7535,10 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" -scheduler@^0.21.0: - version "0.21.0" - resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz#6fd2532ff5a6d877b6edb12f00d8ab7e8f308820" - integrity sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ== - dependencies: - loose-envify "^1.1.0" +scheduler@^0.27.0: + version "0.27.0" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" + integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== secure-json-parse@^4.0.0: version "4.1.0"