diff --git a/Gemfile b/Gemfile index 15bb1b3e..5a22c009 100644 --- a/Gemfile +++ b/Gemfile @@ -21,6 +21,8 @@ gem 'vanagon', *location_for(ENV['VANAGON_LOCATION'] || 'https://github.com/open # https://www.rubyonmac.dev/certificate-verify-failed-unable-to-get-certificate-crl-openssl-ssl-sslerror gem 'openssl' unless `uname -o`.chomp == 'Cygwin' +gem 'octokit', '< 11' + group(:development, optional: true) do gem 'hashdiff', require: false gem 'highline', require: false diff --git a/README.md b/README.md index b707fec0..94ab766b 100644 --- a/README.md +++ b/README.md @@ -107,3 +107,98 @@ end ### End automated maintenance section ### ``` The rake task will leave any lines it doesn't know about alone (in this case, the if/else/end logic) and update both checksums, with the default without the `# GEM TYPE` decorator being the `ruby` uncompiled gem. Try not to get too fancy with logic in here. + +## Updating (GitHub) releases + +We provide two rake tasks, `vox:print_outdated_components` and `vox:update_outdated_components`. +The first one inspects all non-rubygem components: + +``` +$ bundle exec rake vox:print_outdated_components +Checking 14 component(s) for updates... + + augeas... up to date (1.14.1) + curl... error (Could not parse upstream version 'curl-8_20_0') + dmidecode... skipped (No GitHub URL detected) + libedit... skipped (No GitHub URL detected) + libffi... up to date (3.5.2) + libxml2... skipped (No GitHub URL detected) + libyaml... up to date (0.2.5) + openssl-3.0... OUTDATED: 3.0.20 -> 4.0.0 + puppet-ca-bundle... up to date (1.1.0) + readline... skipped (No GitHub URL detected) + ruby-3.2... skipped (No GitHub URL detected) + ruby-augeas... up to date (0.6.0) + ruby-shadow... up to date (2.5.1) + virt-what... skipped (No GitHub URL detected) + +=== Components with available updates === + openssl-3.0: 3.0.20 -> 4.0.0 (upstream tag: openssl-4.0.0) + +=== Errors encountered === + curl: Could not parse upstream version 'curl-8_20_0' + +=== Skipped (no checkable upstream) === + dmidecode: No GitHub URL detected + libedit: No GitHub URL detected + libxml2: No GitHub URL detected + readline: No GitHub URL detected + ruby-3.2: No GitHub URL detected + virt-what: No GitHub URL detected +``` + +It will search for new releases upstream. +Right now only github.com is supported, but most of our components come from rubygems.org or github.com anyways, so this catches 85% of our components. +The second rake task checks the GitHub API for new releases and updates the json file with the version & URL. + +``` +$ bundle exec rake vox:update_outdated_components +Checking 14 component(s) for updates... + + augeas... up to date (1.14.1) + curl... error (Could not parse upstream version 'curl-8_20_0') + dmidecode... skipped (No GitHub URL detected) + libedit... skipped (No GitHub URL detected) + libffi... up to date (3.5.2) + libxml2... skipped (No GitHub URL detected) + libyaml... up to date (0.2.5) + openssl-3.0... OUTDATED: 3.0.20 -> 4.0.0 + puppet-ca-bundle... up to date (1.1.0) + readline... skipped (No GitHub URL detected) + ruby-3.2... skipped (No GitHub URL detected) + ruby-augeas... up to date (0.6.0) + ruby-shadow... up to date (2.5.1) + virt-what... skipped (No GitHub URL detected) + +=== Components with available updates === + openssl-3.0: 3.0.20 -> 4.0.0 (upstream tag: openssl-4.0.0) + +=== Errors encountered === + curl: Could not parse upstream version 'curl-8_20_0' + +=== Skipped (no checkable upstream) === + dmidecode: No GitHub URL detected + libedit: No GitHub URL detected + libxml2: No GitHub URL detected + readline: No GitHub URL detected + ruby-3.2: No GitHub URL detected + virt-what: No GitHub URL detected + +Updated openssl-3.0 to 4.0.0 +One or more components could not be checked. +$ git diff configs/components/openssl-3.0.json +diff --git a/configs/components/openssl-3.0.json b/configs/components/openssl-3.0.json +index 0f11c8a..10f1a32 100644 +--- a/configs/components/openssl-3.0.json ++++ b/configs/components/openssl-3.0.json +@@ -1,5 +1,5 @@ + { +- "version": "3.0.20", +- "url": "https://github.com/openssl/openssl/releases/download/openssl-3.0.20/openssl-3.0.20.tar.gz", +- "sha256sum": "c80a01dfc70ece4dc21168932c37739042d404d46ccc81a5986dd75314ecda6f" ++ "version": "4.0.0", ++ "url": "https://github.com/openssl/openssl/releases/download/openssl-4.0.0/openssl-4.0.0.tar.gz", ++ "sha256sum": "c32cf49a959c4f345f9606982dd36e7d28f7c58b19c2e25d75624d2b3d2f79ac" + } +$ +``` diff --git a/configs/components/augeas.json b/configs/components/augeas.json new file mode 100644 index 00000000..34452458 --- /dev/null +++ b/configs/components/augeas.json @@ -0,0 +1,5 @@ +{ + "version": "1.14.1", + "url": "https://github.com/hercules-team/augeas/releases/download/release-1.14.1/augeas-1.14.1.tar.gz", + "sha256sum": "368bfdd782e4b9c7163baadd621359c82b162734864b667051ff6bcb57b9edff" +} diff --git a/configs/components/augeas.rb b/configs/components/augeas.rb index a5a59645..f6641080 100644 --- a/configs/components/augeas.rb +++ b/configs/components/augeas.rb @@ -4,9 +4,7 @@ component 'augeas' do |pkg, settings, platform| # Solaris and AIX depend on libedit which breaks augeas compliation starting with 1.13.0. # Figure out a solution if we ever need to update augeas on those platforms. - pkg.version '1.14.1' - pkg.md5sum 'ac31216268b4b64809afd3a25f2515e5' - pkg.url "https://github.com/hercules-team/augeas/releases/download/release-#{pkg.get_version}/augeas-#{pkg.get_version}.tar.gz" + pkg.load_from_json('configs/components/augeas.json') pkg.apply_patch 'resources/patches/augeas/augeas-1.14.1-return_reg_enosys.patch' diff --git a/configs/components/curl.json b/configs/components/curl.json new file mode 100644 index 00000000..a863620e --- /dev/null +++ b/configs/components/curl.json @@ -0,0 +1,5 @@ +{ + "version": "8.20.0", + "url": "https://github.com/curl/curl/releases/download/curl-8_20_0/curl-8.20.0.tar.gz", + "sha256sum": "fc5819cad3f9f5482669adcdc49a782c15f36d2a0715b395b06d9173593d2dc0" +} diff --git a/configs/components/curl.rb b/configs/components/curl.rb index 57de85d7..bd1542d0 100644 --- a/configs/components/curl.rb +++ b/configs/components/curl.rb @@ -2,10 +2,7 @@ # Component release information: https://github.com/curl/curl/releases ##### component 'curl' do |pkg, settings, platform| - pkg.version '8.20.0' - pkg.sha256sum 'fc5819cad3f9f5482669adcdc49a782c15f36d2a0715b395b06d9173593d2dc0' - - pkg.url "https://curl.se/download/curl-#{pkg.get_version}.tar.gz" + pkg.load_from_json('configs/components/curl.json') pkg.mirror "#{settings[:buildsources_url]}/curl-#{pkg.get_version}.tar.gz" pkg.build_requires "openssl-#{settings[:openssl_version]}" diff --git a/configs/components/dmidecode.json b/configs/components/dmidecode.json new file mode 100644 index 00000000..86e5a5dd --- /dev/null +++ b/configs/components/dmidecode.json @@ -0,0 +1,5 @@ +{ + "version": "3.7", + "url": "https://download-mirror.savannah.gnu.org/releases/dmidecode/dmidecode-3.7.tar.xz", + "sha256sum": "2c3aed12c85a1e6a9410d406d5e417c455466dc1bc7c89278bb32cf7cad91e8a" +} diff --git a/configs/components/dmidecode.rb b/configs/components/dmidecode.rb index 0556828d..593bb6e4 100644 --- a/configs/components/dmidecode.rb +++ b/configs/components/dmidecode.rb @@ -2,11 +2,9 @@ # Component release information: https://github.com/mirror/dmidecode/tags ##### component 'dmidecode' do |pkg, settings, platform| - pkg.version '3.7' - pkg.sha256sum '2c3aed12c85a1e6a9410d406d5e417c455466dc1bc7c89278bb32cf7cad91e8a' + pkg.load_from_json('configs/components/dmidecode.json') pkg.apply_patch 'resources/patches/dmidecode/dmidecode-install-to-bin.patch' - pkg.url "https://download-mirror.savannah.gnu.org/releases/dmidecode/dmidecode-#{pkg.get_version}.tar.xz" pkg.mirror "#{settings[:buildsources_url]}/dmidecode-#{pkg.get_version}.tar.xz" pkg.environment 'LDFLAGS', settings[:ldflags] diff --git a/configs/components/libedit.json b/configs/components/libedit.json new file mode 100644 index 00000000..00cf848e --- /dev/null +++ b/configs/components/libedit.json @@ -0,0 +1,5 @@ +{ + "version": "20150325-3.1", + "url": "http://thrysoee.dk/editline/libedit-20150325-3.1.tar.gz", + "sha256sum": "c88a5e4af83c5f40dda8455886ac98923a9c33125699742603a88a0253fcc8c5" +} diff --git a/configs/components/libedit.rb b/configs/components/libedit.rb index 1a0d8bcc..904ae1e8 100644 --- a/configs/components/libedit.rb +++ b/configs/components/libedit.rb @@ -6,9 +6,7 @@ # in changes from the BSD version of libedit. Needs investigation. ##### component 'libedit' do |pkg, settings, platform| - pkg.version '20150325-3.1' - pkg.md5sum '43cdb5df3061d78b5e9d59109871b4f6' - pkg.url "http://thrysoee.dk/editline/libedit-#{pkg.get_version}.tar.gz" + pkg.load_from_json('configs/components/libedit.json') pkg.mirror "#{settings[:buildsources_url]}/libedit-#{pkg.get_version}.tar.gz" pkg.environment 'PATH', '/opt/pl-build-tools/bin:$(PATH)' diff --git a/configs/components/libffi.json b/configs/components/libffi.json new file mode 100644 index 00000000..a95775ae --- /dev/null +++ b/configs/components/libffi.json @@ -0,0 +1,5 @@ +{ + "version": "3.5.2", + "url": "https://github.com/libffi/libffi/releases/download/v3.5.2/libffi-3.5.2.tar.gz", + "sha256sum": "f3a3082a23b37c293a4fcd1053147b371f2ff91fa7ea1b2a52e335676bac82dc" +} diff --git a/configs/components/libffi.rb b/configs/components/libffi.rb index c7f8323f..f0470f45 100644 --- a/configs/components/libffi.rb +++ b/configs/components/libffi.rb @@ -2,9 +2,7 @@ # Component release information: https://github.com/libffi/libffi/releases ##### component 'libffi' do |pkg, settings, platform| - pkg.version '3.5.2' - pkg.sha256sum 'f3a3082a23b37c293a4fcd1053147b371f2ff91fa7ea1b2a52e335676bac82dc' - pkg.url "https://github.com/libffi/libffi/releases/download/v#{pkg.get_version}/#{pkg.get_name}-#{pkg.get_version}.tar.gz" + pkg.load_from_json('configs/components/libffi.json') pkg.mirror "#{settings[:buildsources_url]}/#{pkg.get_name}-#{pkg.get_version}.tar.gz" if platform.is_aix? diff --git a/configs/components/libxml2.json b/configs/components/libxml2.json new file mode 100644 index 00000000..6b17b902 --- /dev/null +++ b/configs/components/libxml2.json @@ -0,0 +1,5 @@ +{ + "version": "2.15.3", + "url": "https://download.gnome.org/sources/libxml2/2.15/libxml2-2.15.3.tar.xz", + "sha256sum": "78262a6e7ac170d6528ebfe2efccdf220191a5af6a6cd61ea4a9a9a5042c7a07" +} diff --git a/configs/components/libxml2.rb b/configs/components/libxml2.rb index 911bf623..23ff0dbd 100644 --- a/configs/components/libxml2.rb +++ b/configs/components/libxml2.rb @@ -4,11 +4,7 @@ # https://github.com/GNOME/libxml2/tags ##### component 'libxml2' do |pkg, settings, platform| - pkg.version '2.15.3' - pkg.sha256sum '78262a6e7ac170d6528ebfe2efccdf220191a5af6a6cd61ea4a9a9a5042c7a07' - - libxml2_version_y = pkg.get_version.gsub(/(\d+)\.(\d+)(\.\d+)?/, '\1.\2') - pkg.url "https://download.gnome.org/sources/libxml2/#{libxml2_version_y}/libxml2-#{pkg.get_version}.tar.xz" + pkg.load_from_json('configs/components/libxml2.json') pkg.mirror "#{settings[:buildsources_url]}/libxml2-#{pkg.get_version}.tar.xz" if platform.is_aix? diff --git a/configs/components/libyaml.json b/configs/components/libyaml.json new file mode 100644 index 00000000..1b54685b --- /dev/null +++ b/configs/components/libyaml.json @@ -0,0 +1,5 @@ +{ + "version": "0.2.5", + "url": "https://github.com/yaml/libyaml/releases/download/0.2.5/yaml-0.2.5.tar.gz", + "sha256sum": "c642ae9b75fee120b2d96c712538bd2cf283228d2337df2cf2988e3c02678ef4" +} diff --git a/configs/components/libyaml.rb b/configs/components/libyaml.rb index 3012c5a4..7423d4bf 100644 --- a/configs/components/libyaml.rb +++ b/configs/components/libyaml.rb @@ -2,9 +2,7 @@ # Component release information: https://github.com/yaml/libyaml/releases ##### component 'libyaml' do |pkg, settings, platform| - pkg.version '0.2.5' - pkg.md5sum 'bb15429d8fb787e7d3f1c83ae129a999' - pkg.url "https://github.com/yaml/libyaml/releases/download/#{pkg.get_version}/yaml-#{pkg.get_version}.tar.gz" + pkg.load_from_json('configs/components/libyaml.json') pkg.mirror "#{settings[:buildsources_url]}/yaml-#{pkg.get_version}.tar.gz" if platform.is_aix? diff --git a/configs/components/openssl-3.0.json b/configs/components/openssl-3.0.json new file mode 100644 index 00000000..0f11c8a2 --- /dev/null +++ b/configs/components/openssl-3.0.json @@ -0,0 +1,5 @@ +{ + "version": "3.0.20", + "url": "https://github.com/openssl/openssl/releases/download/openssl-3.0.20/openssl-3.0.20.tar.gz", + "sha256sum": "c80a01dfc70ece4dc21168932c37739042d404d46ccc81a5986dd75314ecda6f" +} diff --git a/configs/components/openssl-3.0.rb b/configs/components/openssl-3.0.rb index b13cc7ba..6d050523 100644 --- a/configs/components/openssl-3.0.rb +++ b/configs/components/openssl-3.0.rb @@ -6,9 +6,7 @@ # need to move to the 3.5.x LTS stream in the next year. ##### component 'openssl' do |pkg, settings, platform| - pkg.version '3.0.20' - pkg.sha256sum 'c80a01dfc70ece4dc21168932c37739042d404d46ccc81a5986dd75314ecda6f' - pkg.url "https://github.com/openssl/openssl/releases/download/openssl-#{pkg.get_version}/openssl-#{pkg.get_version}.tar.gz" + pkg.load_from_json('configs/components/openssl-3.0.json') pkg.mirror "#{settings[:buildsources_url]}/openssl-#{pkg.get_version}.tar.gz" ############################# diff --git a/configs/components/readline.json b/configs/components/readline.json new file mode 100644 index 00000000..96c7bfca --- /dev/null +++ b/configs/components/readline.json @@ -0,0 +1,5 @@ +{ + "version": "8.1.2", + "url": "http://ftp.gnu.org/gnu/readline/readline-8.1.2.tar.gz", + "sha256sum": "7589a2381a8419e68654a47623ce7dfcb756815c8fee726b98f90bf668af7bc6" +} diff --git a/configs/components/readline.rb b/configs/components/readline.rb index eec1c8c4..776decae 100644 --- a/configs/components/readline.rb +++ b/configs/components/readline.rb @@ -7,9 +7,7 @@ # new version breaks anything. ##### component 'readline' do |pkg, settings, platform| - pkg.version '8.1.2' - pkg.md5sum '12819fa739a78a6172400f399ab34f81' - pkg.url "http://ftp.gnu.org/gnu/#{pkg.get_name}/#{pkg.get_name}-#{pkg.get_version}.tar.gz" + pkg.load_from_json('configs/components/readline.json') pkg.mirror "#{settings[:buildsources_url]}/#{pkg.get_name}-#{pkg.get_version}.tar.gz" if platform.is_aix? diff --git a/configs/components/ruby-3.2.json b/configs/components/ruby-3.2.json new file mode 100644 index 00000000..1c871938 --- /dev/null +++ b/configs/components/ruby-3.2.json @@ -0,0 +1,4 @@ +{ + "version": "3.2.11", + "sha256sum": "b3eeabd6636f334531db3ffdc3229eb05e524740e6c84fdc043720573cf2f8b2" +} diff --git a/configs/components/ruby-3.2.rb b/configs/components/ruby-3.2.rb index d57ef302..7e34164e 100644 --- a/configs/components/ruby-3.2.rb +++ b/configs/components/ruby-3.2.rb @@ -6,8 +6,7 @@ # The file name of the ruby component must match the ruby_version ##### component 'ruby-3.2' do |pkg, settings, platform| - pkg.version '3.2.11' - pkg.sha256sum 'b3eeabd6636f334531db3ffdc3229eb05e524740e6c84fdc043720573cf2f8b2' + pkg.load_from_json('configs/components/ruby-3.2.json') ruby_dir = settings[:ruby_dir] ruby_bindir = settings[:ruby_bindir] diff --git a/configs/components/ruby-augeas.json b/configs/components/ruby-augeas.json new file mode 100644 index 00000000..bf3fb974 --- /dev/null +++ b/configs/components/ruby-augeas.json @@ -0,0 +1,5 @@ +{ + "version": "0.6.0", + "url": "https://github.com/hercules-team/ruby-augeas/releases/download/release-0.6.0/ruby-augeas-0.6.0.tgz", + "sha256sum": "98158a54c655b4823439b4bd38609f01e0b912a3d1453144082b8a5f43b0c4dc" +} diff --git a/configs/components/ruby-augeas.rb b/configs/components/ruby-augeas.rb index 8e5b82b1..697852bf 100644 --- a/configs/components/ruby-augeas.rb +++ b/configs/components/ruby-augeas.rb @@ -3,9 +3,7 @@ # https://github.com/hercules-team/ruby-augeas/releases ##### component 'ruby-augeas' do |pkg, settings, platform| - pkg.version '0.6.0' - pkg.sha256sum '98158a54c655b4823439b4bd38609f01e0b912a3d1453144082b8a5f43b0c4dc' - pkg.url "https://github.com/hercules-team/ruby-augeas/releases/download/release-#{pkg.get_version}/ruby-augeas-#{pkg.get_version}.tgz" + pkg.load_from_json('configs/components/ruby-augeas.json') pkg.build_requires "ruby-#{settings[:ruby_version]}" pkg.build_requires 'augeas' diff --git a/configs/components/ruby-shadow.json b/configs/components/ruby-shadow.json new file mode 100644 index 00000000..97e608d1 --- /dev/null +++ b/configs/components/ruby-shadow.json @@ -0,0 +1,4 @@ +{ + "url": "https://github.com/apalmblad/ruby-shadow", + "ref": "refs/tags/2.5.1" +} diff --git a/configs/components/ruby-shadow.rb b/configs/components/ruby-shadow.rb index ed344681..eaa5c0ad 100644 --- a/configs/components/ruby-shadow.rb +++ b/configs/components/ruby-shadow.rb @@ -4,8 +4,7 @@ # https://rubygems.org/gems/ruby-shadow ##### component 'ruby-shadow' do |pkg, settings, platform| - pkg.url 'https://github.com/apalmblad/ruby-shadow' - pkg.ref 'refs/tags/2.5.1' + pkg.load_from_json('configs/components/ruby-shadow.json') pkg.build_requires "ruby-#{settings[:ruby_version]}" if !platform.is_cross_compiled? && platform.architecture == 'sparc' diff --git a/configs/components/virt-what.json b/configs/components/virt-what.json new file mode 100644 index 00000000..a4ac1a5d --- /dev/null +++ b/configs/components/virt-what.json @@ -0,0 +1,5 @@ +{ + "version": "1.27", + "url": "https://artifacts.voxpupuli.org/components/virt-what-1.27.tar.gz", + "sha256sum": "d4d9bd9d4ae59095597443fac663495315c7eb4330b872aa5f062df38ac69bf1" +} diff --git a/configs/components/virt-what.rb b/configs/components/virt-what.rb index 1b54853c..3d8d0fa0 100644 --- a/configs/components/virt-what.rb +++ b/configs/components/virt-what.rb @@ -8,13 +8,7 @@ # SHOULD NOT USE as this is a fork. ##### component 'virt-what' do |pkg, settings, platform| - pkg.version '1.27' - pkg.sha256sum 'd4d9bd9d4ae59095597443fac663495315c7eb4330b872aa5f062df38ac69bf1' - - # 2025-08-05: The upstream site was down, so using a mirror here. Revert this back to the - # original URL next time we bump this. - # pkg.url "https://people.redhat.com/~rjones/virt-what/files/virt-what-#{pkg.get_version}.tar.gz" - pkg.url "https://artifacts.voxpupuli.org/components/virt-what-#{pkg.get_version}.tar.gz" + pkg.load_from_json('configs/components/virt-what.json') pkg.mirror "#{settings[:buildsources_url]}/virt-what-#{pkg.get_version}.tar.gz" pkg.replaces 'pe-virt-what' diff --git a/tasks/update_components.rake b/tasks/update_components.rake new file mode 100644 index 00000000..a396aca0 --- /dev/null +++ b/tasks/update_components.rake @@ -0,0 +1,242 @@ +# frozen_string_literal: true + +require 'digest' +require 'json' +require 'octokit' +require 'open-uri' +require 'rake' +require 'rubygems/version' +require 'uri' + +COMPONENTS_JSON_GLOB = File.join(File.expand_path('..', __dir__), 'configs', 'components', '*.json') + +def github_client + @github_client ||= Octokit::Client.new(access_token: ENV['GITHUB_TOKEN']).tap do |client| + client.auto_paginate = true + end +end + +# Extract GitHub owner and repo from a URL string. +# Returns [owner, repo] or nil if not a GitHub URL. +def github_owner_repo(url) + return nil unless url.to_s =~ %r{github\.com/([^/]+)/([^/\s?#]+)} + + [Regexp.last_match(1), Regexp.last_match(2).sub(/\.git$/, '')] +end + +# Normalize a version string by stripping common tag prefixes. +def normalize_version(tag) + tag.sub(/\Av(?=\d)/, '') + .sub(/\Arefs\/tags\/v?/, '') + .sub(/\Arelease-/, '') + .sub(/\Aopenssl-/, '') +end + +# Try to parse a version from a normalized string, returning nil if unparseable. +def try_version(str) + Gem::Version.new(str) +rescue ArgumentError + nil +end + +def latest_github_release(owner, repo) + github_client.latest_release("#{owner}/#{repo}") +rescue StandardError => e + warn " Warning: could not fetch latest release for #{owner}/#{repo}: #{e}" + nil +end + +# *sighs* this looks a bit complicated. some repos have weird tags like release-$ver or $major_$minor_$patch +# and somme people, like openssl, maintain multiple streams. So the latest tag might not be the highest version +# We do some version comparison to find the actual highest version +def latest_github_tag(owner, repo) + github_client.tags("#{owner}/#{repo}", per_page: 100) + .map { |tag| [tag.name, try_version(normalize_version(tag.name))] } + .reject { |_, version| version.nil? || version.prerelease? } + .max_by { |_, version| version } + .first +end + +def current_version(data) + if data['ref'].to_s =~ %r{refs/tags/(.*)} + normalize_version(Regexp.last_match(1)) + else + normalize_version(data['version']) + end +end + +def latest_upstream_tag(owner, repo, ref) + if ref.nil? + latest_github_release(owner, repo)&.tag_name || latest_github_tag(owner, repo) + else + latest_github_tag(owner, repo) + end +end + +def download_digest(url) + digest = Digest::SHA256.new + + OpenURI.open_uri(url, 'rb') do |io| + digest << io.read(1024 * 16) until io.eof? + end + + digest.hexdigest +end + +def updated_release_url(current_url:, current_version:, latest_version:, latest_tag:, release:) + uri = URI(current_url) + updated_path = uri.path.sub(%r{(/releases/download/)[^/]+(/)}, "\\1#{latest_tag}\\2") + updated_path = updated_path.gsub(current_version, latest_version) unless current_version.empty? + asset_name = File.basename(updated_path) + release_asset = release&.assets&.find { |asset| asset.name == asset_name } + + return release_asset.browser_download_url if release_asset + + uri.path = updated_path + uri.to_s +end + +def update_component_file(path, latest_tag:, latest_version:) + data = JSON.parse(File.read(path)) + owner, repo = github_owner_repo(data['url']) + release = latest_github_release(owner, repo) + current_ver = current_version(data) + updated = false + + if data.key?('version') && data['version'] != latest_version + data['version'] = latest_version + updated = true + end + + desired_ref = "refs/tags/#{latest_tag}" + if data.key?('ref') && data['ref'] != desired_ref + data['ref'] = desired_ref + updated = true + end + + if data['url'].include?('github.com') && data['url'].include?('/releases/download/') + new_url = updated_release_url( + current_url: data['url'], + current_version: current_ver, + latest_version: latest_version, + latest_tag: latest_tag, + release: release + ) + + if new_url != data['url'] + data['url'] = new_url + updated = true + end + end + + digest = download_digest(data['url']) + data['sha256sum'] = digest + + File.write(path, "#{JSON.pretty_generate(data)}\n") if updated + + { updated: updated, url: data['url'] } +end + +def check_component(path) + name = File.basename(path, '.json') + data = JSON.parse(File.read(path)) + + owner, repo = github_owner_repo(data['url']) + + return { name: name, status: :skip, reason: 'No GitHub URL detected' } unless owner + + current_ver_str = current_version(data) + current_ver = try_version(current_ver_str) + latest_tag = latest_upstream_tag(owner, repo, data['ref']) + + return { name: name, status: :error, reason: 'Could not determine latest upstream version' } if latest_tag.nil? + + latest_ver_str = normalize_version(latest_tag) + latest_ver = try_version(latest_ver_str) + + return { name: name, status: :error, reason: "Could not parse upstream version '#{latest_ver_str}'" } if latest_ver.nil? + return { name: name, status: :error, reason: "Could not parse current version '#{current_ver_str}'" } if current_ver.nil? + + if latest_ver > current_ver + { name: name, path: path, status: :outdated, current: current_ver_str, latest: latest_ver_str, tag: latest_tag } + else + { name: name, path: path, status: :up_to_date, current: current_ver_str } + end +end + +def component_results + Dir[COMPONENTS_JSON_GLOB].map { |path| check_component(path) } +end + +def print_component_results(results) + puts "Checking #{results.length} component(s) for updates...\n\n" + + outdated = [] + errors = [] + skipped = [] + + results.each do |result| + print " #{result[:name]}... " + $stdout.flush + + case result[:status] + when :up_to_date + puts "up to date (#{result[:current]})" + when :outdated + puts "OUTDATED: #{result[:current]} -> #{result[:latest]}" + outdated << result + when :skip + puts "skipped (#{result[:reason]})" + skipped << result + when :error + puts "error (#{result[:reason]})" + errors << result + end + end + + puts "\n" + + unless outdated.empty? + puts '=== Components with available updates ===' + outdated.each do |result| + puts " #{result[:name]}: #{result[:current]} -> #{result[:latest]} (upstream tag: #{result[:tag]})" + end + puts '' + end + + unless errors.empty? + puts '=== Errors encountered ===' + errors.each { |result| puts " #{result[:name]}: #{result[:reason]}" } + puts '' + end + + unless skipped.empty? + puts '=== Skipped (no checkable upstream) ===' + skipped.each { |result| puts " #{result[:name]}: #{result[:reason]}" } + puts '' + end + + puts 'All components are up to date.' if outdated.empty? && errors.empty? + + { outdated: outdated, errors: errors, skipped: skipped } +end + +namespace :vox do + desc 'Print non-rubygem components with upstream GitHub updates' + task :print_outdated_components do + print_component_results(component_results) + end + + desc 'Update outdated non-rubygem components backed by GitHub releases or tags' + task :update_outdated_components do + summary = print_component_results(component_results) + + summary[:outdated].each do |result| + update_component_file(result[:path], latest_tag: result[:tag], latest_version: result[:latest]) + puts "Updated #{result[:name]} to #{result[:latest]}" + end + + abort 'One or more components could not be checked.' unless summary[:errors].empty? + puts 'No component files needed changes.' if summary[:outdated].empty? + end +end