@@ -14,6 +14,7 @@ def setup_js_dependencies
1414        @added_dependencies_to_package_json  ||= false 
1515        @ran_direct_installs  ||= false 
1616        add_js_dependencies 
17+         # Only run final install if package_json gem was used and no direct installs ran 
1718        install_js_dependencies  if  @added_dependencies_to_package_json  && !@ran_direct_installs 
1819      end 
1920
@@ -27,25 +28,24 @@ def add_js_dependencies
2728      def  add_react_on_rails_package 
2829        major_minor_patch_only  =  /\A \d +\. \d +\. \d +\z / 
2930
30-         # Try to use package_json gem first, fall back to direct npm commands 
3131        react_on_rails_pkg  =  if  ReactOnRails ::VERSION . match? ( major_minor_patch_only ) 
32-                                [ "react-on-rails@#{ ReactOnRails ::VERSION }  ] 
32+                                "react-on-rails@#{ ReactOnRails ::VERSION }  
3333                             else 
3434                               puts  "Adding the latest react-on-rails NPM module. "  \
3535                                    "Double check this is correct in package.json" 
36-                                [ "react-on-rails" ] 
36+                                "react-on-rails" 
3737                             end 
3838
3939        puts  "Installing React on Rails package..." 
40-         if  add_npm_dependencies ( react_on_rails_pkg ) 
40+         if  add_js_dependency ( react_on_rails_pkg ) 
4141          @added_dependencies_to_package_json  =  true 
42-           return 
42+         else 
43+           # Fallback to direct npm install 
44+           puts  "Using direct npm commands as fallback" 
45+           success  =  system ( "npm" ,  "install" ,  react_on_rails_pkg ) 
46+           @ran_direct_installs  =  true  if  success 
47+           handle_npm_failure ( "react-on-rails package" ,  [ react_on_rails_pkg ] )  unless  success 
4348        end 
44- 
45-         puts  "Using direct npm commands as fallback" 
46-         success  =  system ( "npm" ,  "install" ,  *react_on_rails_pkg ) 
47-         @ran_direct_installs  =  true  if  success 
48-         handle_npm_failure ( "react-on-rails package" ,  react_on_rails_pkg )  unless  success 
4949      end 
5050
5151      def  add_react_dependencies 
@@ -58,14 +58,15 @@ def add_react_dependencies
5858          babel-plugin-transform-react-remove-prop-types 
5959          babel-plugin-macros 
6060        ] 
61-         if  add_npm_dependencies ( react_deps ) 
61+ 
62+         if  add_js_dependencies_batch ( react_deps ) 
6263          @added_dependencies_to_package_json  =  true 
63-           return 
64+         else 
65+           # Fallback to direct npm install 
66+           success  =  system ( "npm" ,  "install" ,  *react_deps ) 
67+           @ran_direct_installs  =  true  if  success 
68+           handle_npm_failure ( "React dependencies" ,  react_deps )  unless  success 
6469        end 
65- 
66-         success  =  system ( "npm" ,  "install" ,  *react_deps ) 
67-         @ran_direct_installs  =  true  if  success 
68-         handle_npm_failure ( "React dependencies" ,  react_deps )  unless  success 
6970      end 
7071
7172      def  add_css_dependencies 
@@ -76,14 +77,15 @@ def add_css_dependencies
7677          mini-css-extract-plugin 
7778          style-loader 
7879        ] 
79-         if  add_npm_dependencies ( css_deps ) 
80+ 
81+         if  add_js_dependencies_batch ( css_deps ) 
8082          @added_dependencies_to_package_json  =  true 
81-           return 
83+         else 
84+           # Fallback to direct npm install 
85+           success  =  system ( "npm" ,  "install" ,  *css_deps ) 
86+           @ran_direct_installs  =  true  if  success 
87+           handle_npm_failure ( "CSS dependencies" ,  css_deps )  unless  success 
8288        end 
83- 
84-         success  =  system ( "npm" ,  "install" ,  *css_deps ) 
85-         @ran_direct_installs  =  true  if  success 
86-         handle_npm_failure ( "CSS dependencies" ,  css_deps )  unless  success 
8789      end 
8890
8991      def  add_dev_dependencies 
@@ -92,29 +94,72 @@ def add_dev_dependencies
9294          @pmmmwh/react-refresh-webpack-plugin 
9395          react-refresh 
9496        ] 
95-         if  add_npm_dependencies ( dev_deps ,  dev : true ) 
97+ 
98+         if  add_js_dependencies_batch ( dev_deps ,  dev : true ) 
9699          @added_dependencies_to_package_json  =  true 
97-           return 
100+         else 
101+           # Fallback to direct npm install 
102+           success  =  system ( "npm" ,  "install" ,  "--save-dev" ,  *dev_deps ) 
103+           @ran_direct_installs  =  true  if  success 
104+           handle_npm_failure ( "development dependencies" ,  dev_deps ,  dev : true )  unless  success 
98105        end 
106+       end 
107+ 
108+       # Add a single dependency using package_json gem 
109+       def  add_js_dependency ( package ,  dev : false ) 
110+         return  false  unless  package_json_available? 
111+ 
112+         pj  =  package_json 
113+         return  false  unless  pj 
114+ 
115+         begin 
116+           if  dev 
117+             pj . manager . add ( package ,  type : :dev ) 
118+           else 
119+             pj . manager . add ( package ) 
120+           end 
121+           true 
122+         rescue  StandardError  =>  e 
123+           puts  "Warning: Could not add #{ package } #{ e . message }  
124+           false 
125+         end 
126+       end 
99127
100-         success  =  system ( "npm" ,  "install" ,  "--save-dev" ,  *dev_deps ) 
101-         @ran_direct_installs  =  true  if  success 
102-         handle_npm_failure ( "development dependencies" ,  dev_deps ,  dev : true )  unless  success 
128+       # Add multiple dependencies at once using package_json gem 
129+       def  add_js_dependencies_batch ( packages ,  dev : false ) 
130+         return  false  unless  package_json_available? 
131+ 
132+         # Use the add_npm_dependencies helper from GeneratorHelper 
133+         add_npm_dependencies ( packages ,  dev : dev ) 
134+       end 
135+ 
136+       # Check if package_json gem is available and loaded 
137+       def  package_json_available? 
138+         # Check if Shakapacker or package_json gem is available 
139+         return  true  if  defined? ( PackageJson ) 
140+ 
141+         begin 
142+           require  "package_json" 
143+           true 
144+         rescue  LoadError 
145+           false 
146+         end 
103147      end 
104148
105149      def  install_js_dependencies 
106-         # Detect which package manager to use 
107-         success  =  if  File . exist? ( File . join ( destination_root ,  "yarn.lock" ) ) 
108-                     system ( "yarn" ,  "install" ) 
109-                   elsif  File . exist? ( File . join ( destination_root ,  "pnpm-lock.yaml" ) ) 
110-                     system ( "pnpm" ,  "install" ) 
111-                   elsif  File . exist? ( File . join ( destination_root ,  "package-lock.json" ) )  ||
112-                         File . exist? ( File . join ( destination_root ,  "package.json" ) ) 
113-                     # Use npm for package-lock.json or as default fallback 
114-                     system ( "npm" ,  "install" ) 
115-                   else 
116-                     true  # No package manager detected, skip 
117-                   end 
150+         # First try to use package_json gem's install method if available 
151+         if  package_json_available?  && package_json 
152+           begin 
153+             package_json . manager . install 
154+             return  true 
155+           rescue  StandardError  =>  e 
156+             puts  "Warning: package_json gem install failed: #{ e . message }  
157+             # Fall through to manual detection 
158+           end 
159+         end 
160+ 
161+         # Fallback to detecting package manager and running install 
162+         success  =  detect_and_run_package_manager_install 
118163
119164        unless  success 
120165          GeneratorMessages . add_warning ( <<~MSG . strip ) 
@@ -131,6 +176,21 @@ def install_js_dependencies
131176        success 
132177      end 
133178
179+       def  detect_and_run_package_manager_install 
180+         # Detect which package manager to use based on lock files 
181+         if  File . exist? ( File . join ( destination_root ,  "yarn.lock" ) ) 
182+           system ( "yarn" ,  "install" ) 
183+         elsif  File . exist? ( File . join ( destination_root ,  "pnpm-lock.yaml" ) ) 
184+           system ( "pnpm" ,  "install" ) 
185+         elsif  File . exist? ( File . join ( destination_root ,  "package-lock.json" ) )  ||
186+               File . exist? ( File . join ( destination_root ,  "package.json" ) ) 
187+           # Use npm for package-lock.json or as default fallback 
188+           system ( "npm" ,  "install" ) 
189+         else 
190+           true  # No package manager detected, skip 
191+         end 
192+       end 
193+ 
134194      def  handle_npm_failure ( dependency_type ,  packages ,  dev : false ) 
135195        install_command  =  dev  ? "npm install --save-dev"  : "npm install" 
136196        GeneratorMessages . add_warning ( <<~MSG . strip ) 
0 commit comments