@@ -713,6 +713,7 @@ def swap_package_json(package_json_path)
713713 end
714714 # rubocop:enable Metrics/AbcSize
715715
716+ # rubocop:disable Metrics/MethodLength
716717 def restore_demo ( demo_path )
717718 restored = 0
718719 gemfile_path = File . join ( demo_path , 'Gemfile' )
@@ -733,12 +734,17 @@ def restore_demo(demo_path)
733734 end
734735
735736 if restored . positive?
736- run_bundle_install ( demo_path ) if File . exist? ( gemfile_path )
737- run_npm_install ( demo_path ) if File . exist? ( package_json_path )
737+ bundle_success = File . exist? ( gemfile_path ) ? run_bundle_install ( demo_path , for_restore : true ) : true
738+ npm_success = File . exist? ( package_json_path ) ? run_npm_install ( demo_path , for_restore : true ) : true
739+
740+ unless bundle_success && npm_success
741+ warn ' ⚠️ Warning: Some dependency installations failed. Check the errors above.'
742+ end
738743 end
739744
740745 restored
741746 end
747+ # rubocop:enable Metrics/MethodLength
742748
743749 # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
744750 def backup_file ( file_path )
@@ -808,27 +814,100 @@ def write_file(file_path, content)
808814 end
809815 end
810816
811- def run_bundle_install ( demo_path )
817+ def find_supported_gems_in_gemfile ( demo_path )
818+ gemfile_content = File . read ( File . join ( demo_path , 'Gemfile' ) )
819+ SUPPORTED_GEMS . select do |gem_name |
820+ # Match: gem 'name' or gem "name" at line start (avoiding false matches in comments)
821+ # Note: Regex compilation per gem is negligible for our 3 supported gems
822+ gemfile_content . match? ( /^\s *gem\s +["']#{ Regexp . escape ( gem_name ) } ["']/ )
823+ end
824+ end
825+
826+ # rubocop:disable Metrics/MethodLength
827+ def run_bundle_install ( demo_path , for_restore : false )
812828 return if dry_run
813829
814- puts ' Running bundle install...'
815- success = Dir . chdir ( demo_path ) do
816- system ( 'bundle' , 'install' , '--quiet' )
830+ if for_restore
831+ # For restore, we need to update the gems to fetch from rubygems
832+ # This ensures Gemfile.lock is properly updated
833+ puts ' Running bundle update (to restore gem sources)...'
834+
835+ gems_to_update = find_supported_gems_in_gemfile ( demo_path )
836+
837+ if gems_to_update . empty?
838+ # No supported gems found in Gemfile - this might indicate they were never swapped
839+ # or the Gemfile structure is unexpected
840+ puts ' ⚠️ No swapped gems detected in Gemfile. Running standard bundle install...'
841+ success = Dir . chdir ( demo_path ) do
842+ system ( 'bundle' , 'install' , '--quiet' )
843+ end
844+ else
845+ puts " Updating gems: #{ gems_to_update . join ( ', ' ) } " if verbose
846+ success = Dir . chdir ( demo_path ) do
847+ # Update specific gems to pull from rubygems
848+ result = system ( 'bundle' , 'update' , *gems_to_update , '--quiet' )
849+ warn ' ⚠️ ERROR: Failed to update gems. Lock file may be inconsistent.' unless result
850+ result
851+ end
852+ end
853+ else
854+ puts ' Running bundle install...'
855+ success = Dir . chdir ( demo_path ) do
856+ system ( 'bundle' , 'install' , '--quiet' )
857+ end
817858 end
818859
819- warn ' ⚠️ Warning: bundle install failed' unless success
860+ warn ' ⚠️ ERROR: bundle command failed' unless success
861+ success
820862 end
863+ # rubocop:enable Metrics/MethodLength
821864
822- def run_npm_install ( demo_path )
865+ # rubocop:disable Metrics/MethodLength
866+ def run_npm_install ( demo_path , for_restore : false )
823867 return if dry_run
824868
825- puts ' Running npm install...'
826- success = Dir . chdir ( demo_path ) do
827- system ( 'npm' , 'install' , '--silent' , out : '/dev/null' , err : '/dev/null' )
869+ if for_restore
870+ # For restore, we need to regenerate package-lock.json from package.json
871+ # to fetch from npm registry instead of local file: paths
872+ puts ' Running npm install (regenerating lock file)...'
873+
874+ package_lock_path = File . join ( demo_path , 'package-lock.json' )
875+ package_lock_backup = "#{ package_lock_path } .backup"
876+
877+ # Atomically move package-lock.json to backup to avoid race conditions
878+ begin
879+ File . rename ( package_lock_path , package_lock_backup )
880+ puts ' Moved package-lock.json to backup for regeneration' if verbose
881+ rescue Errno ::ENOENT
882+ # File doesn't exist, which is fine - nothing to backup
883+ puts ' No package-lock.json found to backup' if verbose
884+ end
885+
886+ success = Dir . chdir ( demo_path ) do
887+ # Use npm install to regenerate package-lock.json from package.json
888+ # Don't use npm ci since we just deleted package-lock.json
889+ system ( 'npm' , 'install' , '--silent' , out : '/dev/null' , err : '/dev/null' )
890+ end
891+
892+ if success
893+ # Remove backup on success
894+ FileUtils . rm_f ( package_lock_backup )
895+ elsif File . exist? ( package_lock_backup )
896+ # Restore backup on failure
897+ FileUtils . mv ( package_lock_backup , package_lock_path )
898+ warn ' ⚠️ ERROR: npm install failed. Restored original package-lock.json'
899+ end
900+ else
901+ puts ' Running npm install...'
902+ success = Dir . chdir ( demo_path ) do
903+ system ( 'npm' , 'install' , '--silent' , out : '/dev/null' , err : '/dev/null' )
904+ end
828905 end
829906
830- warn ' ⚠️ Warning: npm install failed' unless success
907+ warn ' ⚠️ ERROR: npm install failed' unless success
908+ success
831909 end
910+ # rubocop:enable Metrics/MethodLength
832911
833912 def build_local_packages!
834913 return if dry_run
0 commit comments