dependabot-julia 0.342.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/dependabot/julia/dependency.rb +21 -0
- data/lib/dependabot/julia/file_fetcher.rb +63 -0
- data/lib/dependabot/julia/file_parser.rb +278 -0
- data/lib/dependabot/julia/file_updater.rb +295 -0
- data/lib/dependabot/julia/metadata_finder.rb +99 -0
- data/lib/dependabot/julia/package/package_details_fetcher.rb +129 -0
- data/lib/dependabot/julia/package_manager.rb +71 -0
- data/lib/dependabot/julia/registry_client.rb +340 -0
- data/lib/dependabot/julia/requirement.rb +94 -0
- data/lib/dependabot/julia/update_checker/latest_version_finder.rb +222 -0
- data/lib/dependabot/julia/update_checker/requirements_updater.rb +90 -0
- data/lib/dependabot/julia/update_checker.rb +146 -0
- data/lib/dependabot/julia/version.rb +44 -0
- data/lib/dependabot/julia.rb +65 -0
- metadata +281 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "excon"
|
|
5
|
+
require "dependabot/metadata_finders"
|
|
6
|
+
require "dependabot/metadata_finders/base"
|
|
7
|
+
require "dependabot/registry_client"
|
|
8
|
+
require "dependabot/julia/registry_client"
|
|
9
|
+
require "uri" # Required for URI.parse
|
|
10
|
+
require "toml-rb" # Required for TOML parsing
|
|
11
|
+
|
|
12
|
+
module Dependabot
|
|
13
|
+
module Julia
|
|
14
|
+
class MetadataFinder < Dependabot::MetadataFinders::Base
|
|
15
|
+
extend T::Sig
|
|
16
|
+
|
|
17
|
+
# The public source_url method is inherited from Base.
|
|
18
|
+
# We need to implement look_up_source as a private method.
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
sig { override.returns(T.nilable(Dependabot::Source)) }
|
|
23
|
+
def look_up_source
|
|
24
|
+
# Only use authoritative sources from Julia helper or dependency files
|
|
25
|
+
url_string = source_url_from_julia_helper ||
|
|
26
|
+
source_url_from_dependency_files
|
|
27
|
+
|
|
28
|
+
return nil unless url_string
|
|
29
|
+
|
|
30
|
+
parse_source_url(url_string)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
sig { returns(T.nilable(String)) }
|
|
34
|
+
def source_url_from_julia_helper
|
|
35
|
+
uuid = T.cast(dependency.metadata[:julia_uuid], T.nilable(String))
|
|
36
|
+
result = registry_client.find_package_source_url(dependency.name, uuid)
|
|
37
|
+
error = T.cast(result["error"], T.nilable(T.any(String, T::Boolean)))
|
|
38
|
+
return nil if error
|
|
39
|
+
|
|
40
|
+
T.cast(result["source_url"], T.nilable(String))
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
Dependabot.logger.warn("Failed to get source URL from Julia helper: #{e.message}")
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
sig { returns(Dependabot::Julia::RegistryClient) }
|
|
47
|
+
def registry_client
|
|
48
|
+
@registry_client ||= T.let(
|
|
49
|
+
Dependabot::Julia::RegistryClient.new(
|
|
50
|
+
credentials: credentials
|
|
51
|
+
),
|
|
52
|
+
T.nilable(Dependabot::Julia::RegistryClient)
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
sig { params(url_string: String).returns(T.nilable(Dependabot::Source)) }
|
|
57
|
+
def parse_source_url(url_string)
|
|
58
|
+
uri = URI.parse(url_string)
|
|
59
|
+
hostname = uri.host
|
|
60
|
+
return nil unless hostname
|
|
61
|
+
|
|
62
|
+
# Extract repository path and clean it
|
|
63
|
+
path = T.must(uri.path).delete_prefix("/").delete_suffix(".git")
|
|
64
|
+
path_parts = path.split("/")
|
|
65
|
+
return nil if path_parts.length < 2
|
|
66
|
+
|
|
67
|
+
repo = "#{path_parts[0]}/#{path_parts[1]}"
|
|
68
|
+
|
|
69
|
+
# Determine the provider based on hostname
|
|
70
|
+
provider = case hostname
|
|
71
|
+
when "github.com" then "github"
|
|
72
|
+
when "gitlab.com" then "gitlab"
|
|
73
|
+
when /\A.*\.gitlab\.io\z/ then "gitlab"
|
|
74
|
+
else
|
|
75
|
+
Dependabot.logger.info("Unknown SCM provider for #{hostname}, using generic")
|
|
76
|
+
return nil # Return nil for unknown providers
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
Dependabot::Source.new(
|
|
80
|
+
provider: provider,
|
|
81
|
+
repo: repo
|
|
82
|
+
)
|
|
83
|
+
rescue URI::InvalidURIError => e
|
|
84
|
+
Dependabot.logger.error("Invalid URI for dependency #{dependency.name}: #{url_string} - #{e.message}")
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
sig { returns(T.nilable(String)) }
|
|
89
|
+
def source_url_from_dependency_files
|
|
90
|
+
# MetadataFinder doesn't have access to dependency_files
|
|
91
|
+
# This would typically be handled by FileParser or other components
|
|
92
|
+
# For now, we'll skip this strategy and rely on the Julia helper
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
Dependabot::MetadataFinders.register("julia", Dependabot::Julia::MetadataFinder)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "time"
|
|
5
|
+
require "dependabot/julia/registry_client"
|
|
6
|
+
require "dependabot/julia/version"
|
|
7
|
+
require "dependabot/package/package_release"
|
|
8
|
+
require "dependabot/package/package_language"
|
|
9
|
+
|
|
10
|
+
module Dependabot
|
|
11
|
+
module Julia
|
|
12
|
+
module Package
|
|
13
|
+
class PackageDetailsFetcher
|
|
14
|
+
extend T::Sig
|
|
15
|
+
|
|
16
|
+
PACKAGE_LANGUAGE = "julia"
|
|
17
|
+
|
|
18
|
+
sig do
|
|
19
|
+
params(
|
|
20
|
+
dependency: Dependabot::Dependency,
|
|
21
|
+
credentials: T::Array[Dependabot::Credential],
|
|
22
|
+
custom_registries: T::Array[T::Hash[Symbol, String]]
|
|
23
|
+
).void
|
|
24
|
+
end
|
|
25
|
+
def initialize(dependency:, credentials:, custom_registries: [])
|
|
26
|
+
@dependency = dependency
|
|
27
|
+
@credentials = credentials
|
|
28
|
+
@custom_registries = custom_registries
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sig { returns(Dependabot::Dependency) }
|
|
32
|
+
attr_reader :dependency
|
|
33
|
+
|
|
34
|
+
sig { returns(T::Array[Dependabot::Credential]) }
|
|
35
|
+
attr_reader :credentials
|
|
36
|
+
|
|
37
|
+
sig { returns(T::Array[T::Hash[Symbol, String]]) }
|
|
38
|
+
attr_reader :custom_registries
|
|
39
|
+
|
|
40
|
+
sig { returns(T::Array[Dependabot::Package::PackageRelease]) }
|
|
41
|
+
def fetch_package_releases
|
|
42
|
+
releases = T.let([], T::Array[Dependabot::Package::PackageRelease])
|
|
43
|
+
|
|
44
|
+
begin
|
|
45
|
+
registry_client = RegistryClient.new(
|
|
46
|
+
credentials: credentials,
|
|
47
|
+
custom_registries: custom_registries
|
|
48
|
+
)
|
|
49
|
+
uuid = T.cast(dependency.metadata[:julia_uuid], T.nilable(String))
|
|
50
|
+
|
|
51
|
+
# Fetch all available versions
|
|
52
|
+
available_versions = registry_client.fetch_available_versions(dependency.name, uuid)
|
|
53
|
+
return releases if available_versions.empty?
|
|
54
|
+
|
|
55
|
+
releases = build_releases_for_versions(registry_client, available_versions, uuid)
|
|
56
|
+
mark_latest_release(releases)
|
|
57
|
+
|
|
58
|
+
releases
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
Dependabot.logger.error("Error while fetching package releases for #{dependency.name}: #{e.message}")
|
|
61
|
+
releases
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
sig do
|
|
68
|
+
params(
|
|
69
|
+
registry_client: RegistryClient,
|
|
70
|
+
available_versions: T::Array[String],
|
|
71
|
+
uuid: T.nilable(String)
|
|
72
|
+
).returns(T::Array[Dependabot::Package::PackageRelease])
|
|
73
|
+
end
|
|
74
|
+
def build_releases_for_versions(registry_client, available_versions, uuid)
|
|
75
|
+
releases = T.let([], T::Array[Dependabot::Package::PackageRelease])
|
|
76
|
+
|
|
77
|
+
available_versions.each do |version_string|
|
|
78
|
+
version = Julia::Version.new(version_string)
|
|
79
|
+
release_date = fetch_release_date_safely(registry_client, version_string, uuid)
|
|
80
|
+
|
|
81
|
+
releases << create_package_release(version, release_date)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
releases
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
sig do
|
|
88
|
+
params(
|
|
89
|
+
registry_client: RegistryClient,
|
|
90
|
+
version_string: String,
|
|
91
|
+
uuid: T.nilable(String)
|
|
92
|
+
).returns(T.nilable(Time))
|
|
93
|
+
end
|
|
94
|
+
def fetch_release_date_safely(registry_client, version_string, uuid)
|
|
95
|
+
registry_client.fetch_version_release_date(dependency.name, version_string, uuid)
|
|
96
|
+
rescue StandardError => e
|
|
97
|
+
Dependabot.logger.warn(
|
|
98
|
+
"Failed to fetch release info for #{dependency.name} version #{version_string}: #{e.message}"
|
|
99
|
+
)
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
sig do
|
|
104
|
+
params(
|
|
105
|
+
version: Julia::Version,
|
|
106
|
+
release_date: T.nilable(Time)
|
|
107
|
+
).returns(Dependabot::Package::PackageRelease)
|
|
108
|
+
end
|
|
109
|
+
def create_package_release(version, release_date)
|
|
110
|
+
Dependabot::Package::PackageRelease.new(
|
|
111
|
+
version: version,
|
|
112
|
+
released_at: release_date,
|
|
113
|
+
latest: false, # Will be determined later
|
|
114
|
+
yanked: false, # Julia registries don't support yanked packages
|
|
115
|
+
language: Dependabot::Package::PackageLanguage.new(name: PACKAGE_LANGUAGE)
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
sig { params(releases: T::Array[Dependabot::Package::PackageRelease]).void }
|
|
120
|
+
def mark_latest_release(releases)
|
|
121
|
+
return if releases.empty?
|
|
122
|
+
|
|
123
|
+
latest_release = releases.max_by(&:version)
|
|
124
|
+
latest_release&.instance_variable_set(:@latest, true)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/ecosystem"
|
|
6
|
+
require "dependabot/shared_helpers"
|
|
7
|
+
require "dependabot/version"
|
|
8
|
+
|
|
9
|
+
module Dependabot
|
|
10
|
+
module Julia
|
|
11
|
+
class PackageManager < Ecosystem::VersionManager
|
|
12
|
+
extend T::Sig
|
|
13
|
+
|
|
14
|
+
PACKAGE_MANAGER_COMMAND = T.let("julia", String)
|
|
15
|
+
# Julia versions as of June 2025:
|
|
16
|
+
# - 1.10 is the LTS (Long Term Support) version
|
|
17
|
+
# - 1.11 is the current stable version
|
|
18
|
+
# Update these constants when new LTS or major versions are released
|
|
19
|
+
MINIMUM_VERSION = T.let("1.10", String) # LTS version
|
|
20
|
+
CURRENT_VERSION = T.let("1.11", String) # Current stable version
|
|
21
|
+
|
|
22
|
+
sig { returns(T.nilable(String)) }
|
|
23
|
+
def self.detected_version
|
|
24
|
+
# Try to detect Julia version by executing `julia --version`
|
|
25
|
+
output = T.let(
|
|
26
|
+
Dependabot::SharedHelpers.run_shell_command("julia --version"),
|
|
27
|
+
String
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Parse output like "julia version 1.10.0" or "julia version 1.6.7"
|
|
31
|
+
version_match = output.match(/julia version (\d+\.\d+(?:\.\d+)?)/)
|
|
32
|
+
return version_match[1] if version_match
|
|
33
|
+
|
|
34
|
+
# If we can't parse the version, log and fallback
|
|
35
|
+
Dependabot.logger.warn("Could not parse Julia version from output: #{output}")
|
|
36
|
+
MINIMUM_VERSION
|
|
37
|
+
rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e
|
|
38
|
+
Dependabot.logger.info("Julia not found or failed to execute: #{e.message}")
|
|
39
|
+
MINIMUM_VERSION
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
Dependabot.logger.error("Error detecting Julia version: #{e.message}")
|
|
42
|
+
MINIMUM_VERSION
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
sig { void }
|
|
46
|
+
def initialize
|
|
47
|
+
detected_ver_str = self.class.detected_version
|
|
48
|
+
# detected_version always returns a string (either detected or MINIMUM_VERSION fallback),
|
|
49
|
+
# so we can safely use T.must here
|
|
50
|
+
super(
|
|
51
|
+
name: "julia",
|
|
52
|
+
detected_version: Dependabot::Version.new(T.must(detected_ver_str)),
|
|
53
|
+
version: Dependabot::Version.new(T.must(detected_ver_str)),
|
|
54
|
+
supported_versions: [
|
|
55
|
+
Dependabot::Version.new(MINIMUM_VERSION),
|
|
56
|
+
Dependabot::Version.new(CURRENT_VERSION)
|
|
57
|
+
],
|
|
58
|
+
deprecated_versions: []
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
|
63
|
+
def ecosystem
|
|
64
|
+
{
|
|
65
|
+
package_manager: "julia",
|
|
66
|
+
version: version # This will use the potentially configured version
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# Julia Registry Client with DependabotHelper.jl integration
|
|
2
|
+
# typed: strict
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require "time"
|
|
6
|
+
require "dependabot/credential"
|
|
7
|
+
require "dependabot/julia/version"
|
|
8
|
+
require "dependabot/shared_helpers"
|
|
9
|
+
require "dependabot/errors"
|
|
10
|
+
|
|
11
|
+
module Dependabot
|
|
12
|
+
module Julia
|
|
13
|
+
class RegistryClient
|
|
14
|
+
extend T::Sig
|
|
15
|
+
|
|
16
|
+
sig do
|
|
17
|
+
params(credentials: T::Array[Dependabot::Credential], custom_registries: T::Array[T::Hash[Symbol, String]]).void
|
|
18
|
+
end
|
|
19
|
+
def initialize(credentials:, custom_registries: [])
|
|
20
|
+
@credentials = credentials
|
|
21
|
+
@custom_registries = custom_registries
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
sig { params(package_name: String, package_uuid: T.nilable(String)).returns(T.nilable(Gem::Version)) }
|
|
25
|
+
def fetch_latest_version(package_name, package_uuid = nil)
|
|
26
|
+
# Use custom registries if available
|
|
27
|
+
return fetch_latest_version_with_custom_registries(package_name, package_uuid) if custom_registries.any?
|
|
28
|
+
|
|
29
|
+
args = { package_name: package_name }
|
|
30
|
+
args[:package_uuid] = package_uuid if package_uuid
|
|
31
|
+
|
|
32
|
+
result = call_julia_helper(
|
|
33
|
+
function: "get_latest_version",
|
|
34
|
+
args: args
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Check if the result itself contains an error (package not found)
|
|
38
|
+
return nil if result["error"]
|
|
39
|
+
|
|
40
|
+
# Extract version from the result structure
|
|
41
|
+
# The Julia helper returns version directly in the result
|
|
42
|
+
return nil unless result["version"]
|
|
43
|
+
|
|
44
|
+
Gem::Version.new(result["version"])
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
Dependabot.logger.warn(
|
|
47
|
+
"Failed to fetch latest version for #{package_name}: #{e.message}"
|
|
48
|
+
)
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
sig { params(package_name: String, package_uuid: T.nilable(String)).returns(T.nilable(Gem::Version)) }
|
|
53
|
+
def fetch_latest_version_with_custom_registries(package_name, package_uuid = nil)
|
|
54
|
+
args = {
|
|
55
|
+
package_name: package_name,
|
|
56
|
+
package_uuid: package_uuid || "",
|
|
57
|
+
registry_urls: custom_registry_urls
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
result = call_julia_helper(
|
|
61
|
+
function: "get_latest_version_with_custom_registries",
|
|
62
|
+
args: args
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Check if the result itself contains an error (package not found)
|
|
66
|
+
return nil if result["error"]
|
|
67
|
+
|
|
68
|
+
# Extract version from the result structure
|
|
69
|
+
return nil unless result["version"]
|
|
70
|
+
|
|
71
|
+
Gem::Version.new(result["version"])
|
|
72
|
+
rescue StandardError => e
|
|
73
|
+
Dependabot.logger.warn(
|
|
74
|
+
"Failed to fetch latest version with custom registries for #{package_name}: #{e.message}"
|
|
75
|
+
)
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
sig { params(package_name: String, package_uuid: String).returns(T.nilable(T::Hash[String, T.untyped])) }
|
|
80
|
+
def fetch_package_metadata(package_name, package_uuid)
|
|
81
|
+
call_julia_helper(
|
|
82
|
+
function: "get_package_metadata",
|
|
83
|
+
args: { package_name: package_name, package_uuid: package_uuid }
|
|
84
|
+
)
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
Dependabot.logger.warn("Failed to fetch metadata for #{package_name}: #{e.message}")
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
sig do
|
|
91
|
+
params(
|
|
92
|
+
project_path: String,
|
|
93
|
+
package_name: String,
|
|
94
|
+
target_version: String
|
|
95
|
+
).returns(T::Hash[String, T.untyped])
|
|
96
|
+
end
|
|
97
|
+
def check_update_compatibility(project_path, package_name, target_version)
|
|
98
|
+
call_julia_helper(
|
|
99
|
+
function: "check_update_compatibility",
|
|
100
|
+
args: {
|
|
101
|
+
project_path: project_path,
|
|
102
|
+
package_name: package_name,
|
|
103
|
+
target_version: target_version
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
sig do
|
|
109
|
+
params(
|
|
110
|
+
project_path: String,
|
|
111
|
+
manifest_path: T.nilable(String)
|
|
112
|
+
).returns(T::Hash[String, T.untyped])
|
|
113
|
+
end
|
|
114
|
+
def parse_project(project_path:, manifest_path: nil)
|
|
115
|
+
args = { project_path: project_path }
|
|
116
|
+
args[:manifest_path] = manifest_path if manifest_path
|
|
117
|
+
|
|
118
|
+
call_julia_helper(
|
|
119
|
+
function: "parse_project",
|
|
120
|
+
args: args
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
sig { params(manifest_path: String).returns(T::Hash[String, T.untyped]) }
|
|
125
|
+
def parse_manifest(manifest_path)
|
|
126
|
+
call_julia_helper(
|
|
127
|
+
function: "parse_manifest",
|
|
128
|
+
args: { manifest_path: manifest_path }
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
sig do
|
|
133
|
+
params(
|
|
134
|
+
manifest_path: String,
|
|
135
|
+
name: String,
|
|
136
|
+
uuid: String
|
|
137
|
+
).returns(T.nilable(String))
|
|
138
|
+
end
|
|
139
|
+
def get_version_from_manifest(manifest_path, name, uuid)
|
|
140
|
+
result = call_julia_helper(
|
|
141
|
+
function: "get_version_from_manifest",
|
|
142
|
+
args: {
|
|
143
|
+
manifest_path: manifest_path,
|
|
144
|
+
name: name,
|
|
145
|
+
uuid: uuid
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
result["version"] unless result["error"]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
sig { params(package_name: String, package_uuid: T.nilable(String)).returns(T::Hash[String, T.untyped]) }
|
|
153
|
+
def find_package_source_url(package_name, package_uuid = nil)
|
|
154
|
+
args = { package_name: package_name }
|
|
155
|
+
args[:package_uuid] = package_uuid if package_uuid
|
|
156
|
+
|
|
157
|
+
call_julia_helper(
|
|
158
|
+
function: "find_package_source_url",
|
|
159
|
+
args: args
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
sig do
|
|
164
|
+
params(
|
|
165
|
+
package_name: String,
|
|
166
|
+
source_url: String
|
|
167
|
+
).returns(T::Hash[String, T.untyped])
|
|
168
|
+
end
|
|
169
|
+
def extract_package_metadata_from_url(package_name, source_url)
|
|
170
|
+
call_julia_helper(
|
|
171
|
+
function: "extract_package_metadata_from_url",
|
|
172
|
+
args: {
|
|
173
|
+
package_name: package_name,
|
|
174
|
+
source_url: source_url
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
sig do
|
|
180
|
+
params(
|
|
181
|
+
project_path: String,
|
|
182
|
+
updates: T::Hash[String, String]
|
|
183
|
+
).returns(T::Hash[String, T.untyped])
|
|
184
|
+
end
|
|
185
|
+
def update_manifest(project_path:, updates:)
|
|
186
|
+
call_julia_helper(
|
|
187
|
+
function: "update_manifest",
|
|
188
|
+
args: {
|
|
189
|
+
project_path: project_path,
|
|
190
|
+
updates: updates
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
sig { params(package_name: String, version: String, package_uuid: T.nilable(String)).returns(T.nilable(Time)) }
|
|
196
|
+
def fetch_version_release_date(package_name, version, package_uuid = nil)
|
|
197
|
+
result = call_julia_helper(
|
|
198
|
+
function: "get_version_release_date",
|
|
199
|
+
args: {
|
|
200
|
+
package_name: package_name,
|
|
201
|
+
version: version,
|
|
202
|
+
package_uuid: package_uuid
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Check if the result contains an error
|
|
207
|
+
return nil if result["error"]
|
|
208
|
+
|
|
209
|
+
# Parse the release date if available
|
|
210
|
+
return nil unless result["release_date"]
|
|
211
|
+
|
|
212
|
+
Time.parse(result["release_date"])
|
|
213
|
+
rescue StandardError => e
|
|
214
|
+
Dependabot.logger.warn("Failed to fetch release date for #{package_name} v#{version}: #{e.message}")
|
|
215
|
+
nil
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
sig { params(package_name: String, package_uuid: T.nilable(String)).returns(T::Array[String]) }
|
|
219
|
+
def fetch_available_versions(package_name, package_uuid = nil)
|
|
220
|
+
# Use custom registries if available
|
|
221
|
+
return fetch_available_versions_with_custom_registries(package_name, package_uuid) if custom_registries.any?
|
|
222
|
+
|
|
223
|
+
args = { package_name: package_name }
|
|
224
|
+
args[:package_uuid] = package_uuid if package_uuid
|
|
225
|
+
|
|
226
|
+
result = call_julia_helper(
|
|
227
|
+
function: "get_available_versions",
|
|
228
|
+
args: args
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Check if the result contains an error
|
|
232
|
+
return [] if result["error"]
|
|
233
|
+
|
|
234
|
+
# Extract versions array from the result
|
|
235
|
+
versions = result["versions"]
|
|
236
|
+
return [] unless versions.is_a?(Array)
|
|
237
|
+
|
|
238
|
+
versions.map(&:to_s)
|
|
239
|
+
rescue StandardError => e
|
|
240
|
+
Dependabot.logger.warn("Failed to fetch available versions for #{package_name}: #{e.message}")
|
|
241
|
+
[]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
sig { params(package_name: String, package_uuid: T.nilable(String)).returns(T::Array[String]) }
|
|
245
|
+
def fetch_available_versions_with_custom_registries(package_name, package_uuid = nil)
|
|
246
|
+
args = {
|
|
247
|
+
package_name: package_name,
|
|
248
|
+
package_uuid: package_uuid || "",
|
|
249
|
+
registry_urls: custom_registry_urls
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
result = call_julia_helper(
|
|
253
|
+
function: "get_available_versions_with_custom_registries",
|
|
254
|
+
args: args
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Check if the result contains an error
|
|
258
|
+
return [] if result["error"]
|
|
259
|
+
|
|
260
|
+
# Extract versions array from the result
|
|
261
|
+
versions = result["versions"]
|
|
262
|
+
return [] unless versions.is_a?(Array)
|
|
263
|
+
|
|
264
|
+
versions.map(&:to_s)
|
|
265
|
+
rescue StandardError => e
|
|
266
|
+
Dependabot.logger.warn(
|
|
267
|
+
"Failed to fetch available versions with custom registries for #{package_name}: #{e.message}"
|
|
268
|
+
)
|
|
269
|
+
[]
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
private
|
|
273
|
+
|
|
274
|
+
sig { returns(T::Array[T::Hash[Symbol, String]]) }
|
|
275
|
+
attr_reader :custom_registries
|
|
276
|
+
|
|
277
|
+
sig { returns(T::Array[String]) }
|
|
278
|
+
def custom_registry_urls
|
|
279
|
+
custom_registries.filter_map { |reg| reg[:url] }
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
sig { returns(T::Array[Dependabot::Credential]) }
|
|
283
|
+
attr_reader :credentials
|
|
284
|
+
|
|
285
|
+
sig do
|
|
286
|
+
params(
|
|
287
|
+
function: String,
|
|
288
|
+
args: T::Hash[Symbol, T.untyped]
|
|
289
|
+
).returns(T::Hash[String, T.untyped])
|
|
290
|
+
end
|
|
291
|
+
def call_julia_helper(function:, args:)
|
|
292
|
+
# Use the main julia helpers directory as project (contains Project.toml with DependabotHelper in [sources])
|
|
293
|
+
julia_project_dir = File.dirname(julia_helper_script)
|
|
294
|
+
julia_command = "julia --project=#{julia_project_dir} #{julia_helper_script}"
|
|
295
|
+
|
|
296
|
+
SharedHelpers.run_helper_subprocess(
|
|
297
|
+
command: julia_command,
|
|
298
|
+
function: function,
|
|
299
|
+
args: args,
|
|
300
|
+
env: julia_env,
|
|
301
|
+
allow_unsafe_shell_command: true
|
|
302
|
+
)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
sig { returns(String) }
|
|
306
|
+
def julia_helper_script
|
|
307
|
+
# Use environment variable if available, otherwise fall back to relative path
|
|
308
|
+
helpers_path = ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", nil)
|
|
309
|
+
if helpers_path
|
|
310
|
+
File.join(helpers_path, "julia", "run_dependabot_helper.jl")
|
|
311
|
+
else
|
|
312
|
+
# Fallback for development/test environments
|
|
313
|
+
File.join(__dir__, "..", "..", "..", "helpers", "run_dependabot_helper.jl")
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
sig { returns(T::Hash[String, String]) }
|
|
318
|
+
def julia_env
|
|
319
|
+
env = {}
|
|
320
|
+
|
|
321
|
+
if ENV["DEPENDABOT_NATIVE_HELPERS_PATH"]
|
|
322
|
+
# In production/CI, use the shared depot where packages were precompiled
|
|
323
|
+
user_depot = File.join(ENV.fetch("HOME", "/home/dependabot"), ".julia")
|
|
324
|
+
# Trailing : is intentional. It automatically includes the bundled stdlibs
|
|
325
|
+
env["JULIA_DEPOT_PATH"] = "#{user_depot}:"
|
|
326
|
+
end
|
|
327
|
+
# In development use the default Julia depot
|
|
328
|
+
|
|
329
|
+
# Add Julia-specific environment variables for registry authentication
|
|
330
|
+
julia_credentials = credentials.select { |c| c["type"] == "julia_registry" }
|
|
331
|
+
julia_credentials.each_with_index do |cred, index|
|
|
332
|
+
env["JULIA_PKG_SERVER_REGISTRY_PREFERENCE_#{index}"] = cred.fetch("url")
|
|
333
|
+
env["JULIA_PKG_SERVER_#{index}_TOKEN"] = cred.fetch("token") if cred["token"]
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
env
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|