| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  | # typed: strict | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-09 03:12:21 +01:00
										 |  |  | require "timeout" | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | require "cask/download" | 
					
						
							|  |  |  | require "cask/installer" | 
					
						
							|  |  |  | require "cask/cask_loader" | 
					
						
							| 
									
										
										
										
											2024-01-31 20:00:54 -08:00
										 |  |  | require "system_command" | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | require "tap" | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  | require "unversioned_cask_checker" | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | module Homebrew | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |   module DevCmd | 
					
						
							| 
									
										
										
										
											2024-03-21 14:02:42 -07:00
										 |  |  |     class BumpUnversionedCasks < AbstractCommand | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |       include SystemCommand::Mixin | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       cmd_args do | 
					
						
							|  |  |  |         description <<~EOS | 
					
						
							|  |  |  |           Check all casks with unversioned URLs in a given <tap> for updates. | 
					
						
							|  |  |  |         EOS | 
					
						
							|  |  |  |         switch "-n", "--dry-run", | 
					
						
							|  |  |  |                description: "Do everything except caching state and opening pull requests." | 
					
						
							|  |  |  |         flag   "--limit=", | 
					
						
							|  |  |  |                description: "Maximum runtime in minutes." | 
					
						
							|  |  |  |         flag   "--state-file=", | 
					
						
							|  |  |  |                description: "File for caching state." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         named_args [:cask, :tap], min: 1, without_api: true | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |       sig { override.void } | 
					
						
							|  |  |  |       def run | 
					
						
							|  |  |  |         Homebrew.install_bundler_gems!(groups: ["bump_unversioned_casks"]) | 
					
						
							| 
									
										
										
										
											2023-10-27 19:44:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         state_file = if args.state_file.present? | 
					
						
							|  |  |  |           Pathname(T.must(args.state_file)).expand_path | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |           HOMEBREW_CACHE/"bump_unversioned_casks.json" | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |         state_file.dirname.mkpath | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         state = state_file.exist? ? JSON.parse(state_file.read) : {} | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         casks = args.named.to_paths(only: :cask, recurse_tap: true).map { |path| Cask::CaskLoader.load(path) } | 
					
						
							| 
									
										
										
										
											2020-12-08 01:03:39 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         unversioned_casks = casks.select do |cask| | 
					
						
							| 
									
										
										
										
											2024-05-07 12:01:26 +01:00
										 |  |  |           cask.url&.unversioned? && !cask.livecheckable? | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         ohai "Unversioned Casks: #{unversioned_casks.count} (#{state.size} cached)" | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         checked, unchecked = unversioned_casks.partition { |c| state.key?(c.full_name) } | 
					
						
							| 
									
										
										
										
											2020-12-05 21:30:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         queue = Queue.new | 
					
						
							| 
									
										
										
										
											2020-12-05 21:30:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         # Start with random casks which have not been checked. | 
					
						
							|  |  |  |         unchecked.shuffle.each do |c| | 
					
						
							|  |  |  |           queue.enq c | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         # Continue with previously checked casks, ordered by when they were last checked. | 
					
						
							|  |  |  |         checked.sort_by { |c| state.dig(c.full_name, "check_time") }.each do |c| | 
					
						
							|  |  |  |           queue.enq c | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2020-12-05 21:30:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         limit = args.limit.presence&.to_i | 
					
						
							|  |  |  |         end_time = Time.now + (limit * 60) if limit | 
					
						
							| 
									
										
										
										
											2020-12-05 21:30:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         until queue.empty? || (end_time && end_time < Time.now) | 
					
						
							|  |  |  |           cask = queue.deq | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |           key = cask.full_name | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |           new_state = bump_unversioned_cask(cask, state: state.fetch(key, {})) | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |           next unless new_state | 
					
						
							| 
									
										
										
										
											2020-12-06 19:14:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |           state[key] = new_state | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |           state_file.atomic_write JSON.pretty_generate(state) unless args.dry_run? | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |       private | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |       sig { | 
					
						
							|  |  |  |         params(cask: Cask::Cask, state: T::Hash[String, T.untyped]) | 
					
						
							|  |  |  |           .returns(T.nilable(T::Hash[String, T.untyped])) | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       def bump_unversioned_cask(cask, state:) | 
					
						
							|  |  |  |         ohai "Checking #{cask.full_name}" | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         unversioned_cask_checker = UnversionedCaskChecker.new(cask) | 
					
						
							| 
									
										
										
										
											2020-12-07 17:23:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         if !unversioned_cask_checker.single_app_cask? && | 
					
						
							|  |  |  |            !unversioned_cask_checker.single_pkg_cask? && | 
					
						
							|  |  |  |            !unversioned_cask_checker.single_qlplugin_cask? | 
					
						
							|  |  |  |           opoo "Skipping, not a single-app or PKG cask." | 
					
						
							|  |  |  |           return | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2020-12-07 17:23:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         last_check_time = state["check_time"]&.then { |t| Time.parse(t) } | 
					
						
							| 
									
										
										
										
											2020-12-07 17:23:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         check_time = Time.now | 
					
						
							|  |  |  |         if last_check_time && (check_time - last_check_time) / 3600 < 24
 | 
					
						
							|  |  |  |           opoo "Skipping, already checked within the last 24 hours." | 
					
						
							|  |  |  |           return | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2020-12-07 17:23:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         last_sha256 = state["sha256"] | 
					
						
							|  |  |  |         last_time = state["time"]&.then { |t| Time.parse(t) } | 
					
						
							|  |  |  |         last_file_size = state["file_size"] | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         download = Cask::Download.new(cask) | 
					
						
							|  |  |  |         time, file_size = begin | 
					
						
							|  |  |  |           download.time_file_size | 
					
						
							|  |  |  |         rescue | 
					
						
							|  |  |  |           [nil, nil] | 
					
						
							| 
									
										
										
										
											2020-12-09 03:12:21 +01:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |         if last_time != time || last_file_size != file_size | 
					
						
							|  |  |  |           sha256 = begin | 
					
						
							|  |  |  |             Timeout.timeout(5 * 60) do | 
					
						
							|  |  |  |               unversioned_cask_checker.installer.download.sha256 | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |           rescue => e | 
					
						
							|  |  |  |             onoe e | 
					
						
							| 
									
										
										
										
											2024-05-10 14:12:19 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             nil | 
					
						
							| 
									
										
										
										
											2020-12-07 17:23:56 +01:00
										 |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |           if sha256.present? && last_sha256 != sha256 | 
					
						
							|  |  |  |             version = begin | 
					
						
							|  |  |  |               Timeout.timeout(60) do | 
					
						
							|  |  |  |                 unversioned_cask_checker.guess_cask_version | 
					
						
							|  |  |  |               end | 
					
						
							|  |  |  |             rescue Timeout::Error | 
					
						
							|  |  |  |               onoe "Timed out guessing version for cask '#{cask}'." | 
					
						
							| 
									
										
										
										
											2024-05-10 14:12:19 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |               nil | 
					
						
							| 
									
										
										
										
											2020-12-11 02:21:20 +01:00
										 |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  |             if version | 
					
						
							|  |  |  |               if cask.version == version | 
					
						
							|  |  |  |                 oh1 "Cask #{cask} is up-to-date at #{version}" | 
					
						
							|  |  |  |               else | 
					
						
							|  |  |  |                 bump_cask_pr_args = [ | 
					
						
							|  |  |  |                   "bump-cask-pr", | 
					
						
							|  |  |  |                   "--version", version.to_s, | 
					
						
							|  |  |  |                   "--sha256", ":no_check", | 
					
						
							|  |  |  |                   "--message", "Automatic update via `brew bump-unversioned-casks`.", | 
					
						
							|  |  |  |                   cask.sourcefile_path | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if args.dry_run? | 
					
						
							|  |  |  |                   bump_cask_pr_args << "--dry-run" | 
					
						
							|  |  |  |                   oh1 "Would bump #{cask} from #{cask.version} to #{version}" | 
					
						
							|  |  |  |                 else | 
					
						
							|  |  |  |                   oh1 "Bumping #{cask} from #{cask.version} to #{version}" | 
					
						
							|  |  |  |                 end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 begin | 
					
						
							|  |  |  |                   system_command! HOMEBREW_BREW_FILE, args: bump_cask_pr_args | 
					
						
							|  |  |  |                 rescue ErrorDuringExecution => e | 
					
						
							|  |  |  |                   onoe e | 
					
						
							|  |  |  |                 end | 
					
						
							|  |  |  |               end | 
					
						
							| 
									
										
										
										
											2020-12-11 02:21:20 +01:00
										 |  |  |             end | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2024-03-18 16:49:26 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           "sha256"     => sha256, | 
					
						
							|  |  |  |           "check_time" => check_time.iso8601, | 
					
						
							|  |  |  |           "time"       => time&.iso8601, | 
					
						
							|  |  |  |           "file_size"  => file_size, | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2020-12-06 20:04:53 +01:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-12-04 00:07:02 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | end |