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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1b48cd9be4ed4e37e08b11eb39f35e71d449320f0efb7ee5d888e6f2be7d8ed3
4
+ data.tar.gz: 1f34fc7c632a67d01f7325c5d60b7a2d8e3e6c40178961713b42b817b7e3b5b4
5
+ SHA512:
6
+ metadata.gz: b5c0b679487adf8dce2910599a8018f1858b68a7e987d64662ac92edeb32a4bf00925f4a43a35b507bb816c359d17614c320a9f0999ba82fff109d168712673e
7
+ data.tar.gz: 21ff26c3c0989184d86d36d8fa4104478b7d3e9797f694a74cb573b252562ebead236e09347fd9211217ab77cea903817183ee88ac0f1846f1bbcb1340fa2f4e
@@ -0,0 +1,21 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/dependency"
5
+ require "dependabot/julia/version"
6
+ require "sorbet-runtime"
7
+
8
+ module Dependabot
9
+ module Julia
10
+ class Dependency < Dependabot::Dependency
11
+ extend T::Sig
12
+
13
+ # This class intentionally delegates most functionality to the base
14
+ # Dependabot::Dependency class.
15
+ #
16
+ # Note: Dependabot::Julia::Version is properly registered for the "julia" package manager
17
+ # and implements ::new(version_string) and ::correct?(version_string) methods that handle
18
+ # Julia version strings according to Julia's semantic versioning specification.
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/file_fetchers"
5
+ require "dependabot/file_fetchers/base"
6
+
7
+ module Dependabot
8
+ module Julia
9
+ class FileFetcher < Dependabot::FileFetchers::Base
10
+ sig { override.params(filenames: T::Array[String]).returns(T::Boolean) }
11
+ def self.required_files_in?(filenames)
12
+ filenames.any? { |name| name.match?(/^(Julia)?Project\.toml$/i) }
13
+ end
14
+
15
+ sig { override.returns(String) }
16
+ def self.required_files_message
17
+ "Repo must contain a Project.toml or JuliaProject.toml file."
18
+ end
19
+
20
+ sig { override.returns(T::Array[Dependabot::DependencyFile]) }
21
+ def fetch_files
22
+ # Julia is currently in beta - only fetch files if beta ecosystems are enabled
23
+ return [] unless allow_beta_ecosystems?
24
+
25
+ fetched_files = []
26
+
27
+ # Fetch the main project file (Project.toml or JuliaProject.toml)
28
+ fetched_files << project_file
29
+
30
+ # Fetch the Manifest file if it exists
31
+ fetched_files << manifest_file if manifest_file
32
+
33
+ fetched_files
34
+ end
35
+
36
+ private
37
+
38
+ sig { returns(Dependabot::DependencyFile) }
39
+ def project_file
40
+ @project_file ||= T.let(
41
+ fetch_file_if_present("Project.toml") ||
42
+ fetch_file_if_present("JuliaProject.toml") ||
43
+ raise(
44
+ Dependabot::DependencyFileNotFound,
45
+ "No Project.toml or JuliaProject.toml found."
46
+ ),
47
+ T.nilable(Dependabot::DependencyFile)
48
+ )
49
+ end
50
+
51
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
52
+ def manifest_file
53
+ @manifest_file ||= T.let(
54
+ fetch_file_if_present("Manifest.toml") ||
55
+ fetch_file_if_present("JuliaManifest.toml"),
56
+ T.nilable(Dependabot::DependencyFile)
57
+ )
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ Dependabot::FileFetchers.register("julia", Dependabot::Julia::FileFetcher)
@@ -0,0 +1,278 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "toml-rb"
5
+ require "tempfile"
6
+ require "fileutils"
7
+ require "dependabot/dependency"
8
+ require "dependabot/file_parsers"
9
+ require "dependabot/file_parsers/base"
10
+ require "dependabot/julia/version"
11
+ require "dependabot/julia/requirement"
12
+ require "dependabot/julia/registry_client"
13
+
14
+ module Dependabot
15
+ module Julia
16
+ class FileParser < Dependabot::FileParsers::Base
17
+ extend T::Sig
18
+
19
+ sig do
20
+ params(
21
+ dependency_files: T::Array[Dependabot::DependencyFile],
22
+ source: Dependabot::Source,
23
+ repo_contents_path: T.nilable(String),
24
+ credentials: T::Array[Dependabot::Credential],
25
+ reject_external_code: T::Boolean,
26
+ options: T::Hash[Symbol, T.untyped]
27
+ ).void
28
+ end
29
+ def initialize(
30
+ dependency_files:,
31
+ source:,
32
+ repo_contents_path: nil,
33
+ credentials: [],
34
+ reject_external_code: false,
35
+ options: {}
36
+ )
37
+ super
38
+ @parsed_project_file = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
39
+ @parsed_manifest_file = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
40
+ @registry_client = T.let(nil, T.nilable(Dependabot::Julia::RegistryClient))
41
+ @custom_registries = T.let(nil, T.nilable(T::Array[T::Hash[Symbol, T.untyped]]))
42
+ @temp_dir = T.let(nil, T.nilable(String))
43
+ end
44
+
45
+ sig { override.returns(T::Array[Dependabot::Dependency]) }
46
+ def parse
47
+ dependency_set = T.let([], T::Array[Dependabot::Dependency])
48
+
49
+ dependency_set += project_file_dependencies
50
+ dependency_set.uniq
51
+ end
52
+
53
+ private
54
+
55
+ # Helper methods for DependabotHelper.jl integration
56
+
57
+ sig { returns(Dependabot::Julia::RegistryClient) }
58
+ def registry_client
59
+ @registry_client ||= Dependabot::Julia::RegistryClient.new(
60
+ credentials: credentials,
61
+ custom_registries: custom_registries
62
+ )
63
+ end
64
+
65
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
66
+ def custom_registries
67
+ @custom_registries ||= begin
68
+ registries = options.dig(:registries, :julia) || []
69
+ # Convert string keys to symbols if needed
70
+ registries.map do |registry|
71
+ registry.is_a?(Hash) ? registry.transform_keys(&:to_sym) : registry
72
+ end
73
+ end
74
+ end
75
+
76
+ sig { returns(String) }
77
+ def write_temp_project_file
78
+ @temp_dir ||= Dir.mktmpdir("julia_project")
79
+ project_path = File.join(@temp_dir, "Project.toml")
80
+ File.write(project_path, T.must(project_file).content)
81
+ project_path
82
+ end
83
+
84
+ sig { returns(T.nilable(String)) }
85
+ def write_temp_manifest_file
86
+ return nil unless manifest_file
87
+
88
+ @temp_dir ||= Dir.mktmpdir("julia_project")
89
+ manifest_path = File.join(@temp_dir, "Manifest.toml")
90
+ File.write(manifest_path, T.must(manifest_file).content)
91
+ manifest_path
92
+ end
93
+
94
+ sig { returns(T::Array[Dependabot::Dependency]) }
95
+ def project_file_dependencies
96
+ dependencies = T.let([], T::Array[Dependabot::Dependency])
97
+ return dependencies unless project_file
98
+
99
+ # Use DependabotHelper.jl for project parsing
100
+ project_path = write_temp_project_file
101
+ manifest_path = write_temp_manifest_file if manifest_file
102
+
103
+ begin
104
+ result = registry_client.parse_project(
105
+ project_path: project_path,
106
+ manifest_path: manifest_path
107
+ )
108
+
109
+ if result["error"]
110
+ # Fallback to Ruby TOML parsing if Julia helper fails
111
+ Dependabot.logger.warn(
112
+ "DependabotHelper.jl parsing failed: #{result['error']}, " \
113
+ "falling back to Ruby parsing"
114
+ )
115
+ return fallback_project_file_dependencies
116
+ end
117
+
118
+ # Convert DependabotHelper.jl result to Dependabot::Dependency objects
119
+ dependencies = build_dependencies_from_julia_result(result)
120
+ ensure
121
+ # Cleanup temporary directory
122
+ FileUtils.rm_rf(@temp_dir) if @temp_dir && File.exist?(@temp_dir)
123
+ end
124
+
125
+ dependencies
126
+ end
127
+
128
+ sig { params(result: T::Hash[String, T.untyped]).returns(T::Array[Dependabot::Dependency]) }
129
+ def build_dependencies_from_julia_result(result)
130
+ dependencies = T.let([], T::Array[Dependabot::Dependency])
131
+ parsed_deps = T.cast(result["dependencies"] || [], T::Array[T.untyped])
132
+
133
+ parsed_deps.each do |dep_info|
134
+ dep_hash = T.cast(dep_info, T::Hash[String, T.untyped])
135
+ name = T.cast(dep_hash["name"], String)
136
+ uuid = T.cast(dep_hash["uuid"], T.nilable(String))
137
+ requirement_string = T.cast(dep_hash["requirement"] || "*", String)
138
+ resolved_version = T.cast(dep_hash["resolved_version"], T.nilable(String))
139
+
140
+ # Skip Julia version requirement
141
+ next if name == "julia"
142
+
143
+ dependencies << Dependabot::Dependency.new(
144
+ name: name,
145
+ version: resolved_version,
146
+ requirements: [{
147
+ requirement: requirement_string,
148
+ file: T.must(project_file).name,
149
+ groups: ["runtime"],
150
+ source: nil
151
+ }],
152
+ package_manager: "julia",
153
+ metadata: uuid ? { julia_uuid: uuid } : {}
154
+ )
155
+ end
156
+
157
+ dependencies
158
+ end
159
+
160
+ # Fallback method using Ruby TOML parsing
161
+ sig { returns(T::Array[Dependabot::Dependency]) }
162
+ def fallback_project_file_dependencies
163
+ dependencies = T.let([], T::Array[Dependabot::Dependency])
164
+
165
+ parsed_project = parsed_project_file
166
+ deps_section = T.cast(parsed_project["deps"] || {}, T::Hash[String, T.untyped])
167
+ compat_section = T.cast(parsed_project["compat"] || {}, T::Hash[String, T.untyped])
168
+
169
+ deps_section.each do |name, _uuid|
170
+ next if name == "julia" # Skip Julia version requirement
171
+
172
+ # Get the version requirement from compat section, default to "*" if not specified
173
+ requirement_string = T.cast(compat_section[name] || "*", String)
174
+
175
+ # Get the exact version from Manifest.toml if available
176
+ exact_version = version_from_manifest(name)
177
+
178
+ dependencies << Dependabot::Dependency.new(
179
+ name: name,
180
+ version: exact_version,
181
+ requirements: [{
182
+ requirement: requirement_string,
183
+ file: T.must(project_file).name,
184
+ groups: ["runtime"],
185
+ source: nil
186
+ }],
187
+ package_manager: "julia"
188
+ )
189
+ end
190
+
191
+ dependencies
192
+ end
193
+
194
+ sig { params(dependency_name: String).returns(T.nilable(String)) }
195
+ def version_from_manifest(dependency_name)
196
+ return nil unless manifest_file
197
+
198
+ # Try using DependabotHelper.jl first
199
+ temp_dir = Dir.mktmpdir("julia_manifest_only")
200
+ manifest_path = File.join(temp_dir, "Manifest.toml")
201
+ File.write(manifest_path, T.must(manifest_file).content)
202
+
203
+ begin
204
+ # We need the UUID for the DependabotHelper.jl call, so fallback to Ruby parsing
205
+ # Note: Future enhancement could add name-only lookup to DependabotHelper.jl
206
+ fallback_version_from_manifest(dependency_name)
207
+ ensure
208
+ FileUtils.rm_rf(temp_dir)
209
+ end
210
+ end
211
+
212
+ sig { params(dependency_name: String).returns(T.nilable(String)) }
213
+ def fallback_version_from_manifest(dependency_name)
214
+ return nil unless manifest_file
215
+
216
+ parsed_manifest = parsed_manifest_file
217
+
218
+ # Look for the dependency in the manifest
219
+ deps_section = T.cast(parsed_manifest["deps"], T.nilable(T::Hash[String, T.untyped]))
220
+ if deps_section && deps_section[dependency_name]
221
+ # Manifest v2 format
222
+ dep_entries = deps_section[dependency_name]
223
+ if dep_entries.is_a?(Array) && dep_entries.first.is_a?(Hash)
224
+ return T.cast(dep_entries.first["version"], T.nilable(String))
225
+ end
226
+ end
227
+
228
+ nil
229
+ end
230
+
231
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
232
+ def project_file
233
+ @project_file ||= T.let(
234
+ get_original_file("Project.toml") || get_original_file("JuliaProject.toml"),
235
+ T.nilable(Dependabot::DependencyFile)
236
+ )
237
+ end
238
+
239
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
240
+ def manifest_file
241
+ @manifest_file ||= T.let(
242
+ get_original_file("Manifest.toml") || get_original_file("JuliaManifest.toml"),
243
+ T.nilable(Dependabot::DependencyFile)
244
+ )
245
+ end
246
+
247
+ sig { returns(T::Hash[String, T.untyped]) }
248
+ def parsed_project_file
249
+ return @parsed_project_file if @parsed_project_file
250
+
251
+ parsed_content = T.cast(TomlRB.parse(T.must(project_file).content), T::Hash[String, T.untyped])
252
+ @parsed_project_file = parsed_content
253
+ parsed_content
254
+ rescue TomlRB::ParseError, TomlRB::ValueOverwriteError => e
255
+ raise Dependabot::DependencyFileNotParseable, "Error parsing #{T.must(project_file).name}: #{e.message}"
256
+ end
257
+
258
+ sig { returns(T::Hash[String, T.untyped]) }
259
+ def parsed_manifest_file
260
+ return {} unless manifest_file
261
+ return @parsed_manifest_file if @parsed_manifest_file
262
+
263
+ parsed_content = T.cast(TomlRB.parse(T.must(manifest_file).content), T::Hash[String, T.untyped])
264
+ @parsed_manifest_file = parsed_content
265
+ parsed_content
266
+ rescue TomlRB::ParseError, TomlRB::ValueOverwriteError => e
267
+ raise Dependabot::DependencyFileNotParseable, "Error parsing #{T.must(manifest_file).name}: #{e.message}"
268
+ end
269
+
270
+ sig { override.void }
271
+ def check_required_files
272
+ raise "No Project.toml or JuliaProject.toml!" unless project_file
273
+ end
274
+ end
275
+ end
276
+ end
277
+
278
+ Dependabot::FileParsers.register("julia", Dependabot::Julia::FileParser)
@@ -0,0 +1,295 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "toml-rb"
5
+ require "tempfile"
6
+ require "dependabot/file_updaters"
7
+ require "dependabot/file_updaters/base"
8
+ require "dependabot/julia/registry_client"
9
+
10
+ module Dependabot
11
+ module Julia
12
+ class FileUpdater < Dependabot::FileUpdaters::Base
13
+ extend T::Sig
14
+
15
+ sig { returns(T::Array[Regexp]) }
16
+ def self.updated_files_regex
17
+ [/(?:Julia)?Project\.toml$/i, /(?:Julia)?Manifest(?:-v[\d.]+)?\.toml$/i]
18
+ end
19
+
20
+ sig { override.returns(T::Array[Dependabot::DependencyFile]) }
21
+ def updated_dependency_files
22
+ updated_files = []
23
+
24
+ # Use DependabotHelper.jl for manifest updating
25
+ if project_file
26
+ project_path = T.let(nil, T.nilable(String))
27
+
28
+ begin
29
+ project_path = write_temp_project_file
30
+
31
+ result = registry_client.update_manifest(
32
+ project_path: project_path,
33
+ updates: build_updates_hash
34
+ )
35
+
36
+ if result["error"]
37
+ # Fallback to Ruby TOML manipulation
38
+ Dependabot.logger.warn(
39
+ "DependabotHelper.jl update failed: #{result['error']}, " \
40
+ "falling back to Ruby updating"
41
+ )
42
+ return fallback_updated_dependency_files
43
+ end
44
+
45
+ # Create updated files from DependabotHelper.jl results
46
+ updated_files = build_updated_files_from_result(result)
47
+ rescue StandardError => e
48
+ # Fallback to Ruby TOML manipulation if Julia helper fails
49
+ Dependabot.logger.warn(
50
+ "DependabotHelper.jl update failed with exception: #{e.message}, " \
51
+ "falling back to Ruby updating"
52
+ )
53
+ return fallback_updated_dependency_files
54
+ ensure
55
+ File.delete(project_path) if project_path && File.exist?(project_path)
56
+ end
57
+ end
58
+
59
+ raise "No files changed!" if updated_files.empty?
60
+
61
+ updated_files
62
+ end
63
+
64
+ # Fallback method using Ruby TOML manipulation
65
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
66
+ def fallback_updated_dependency_files
67
+ updated_files = []
68
+
69
+ # Update Project.toml file
70
+ if project_file && file_changed?(T.must(project_file))
71
+ updated_files << updated_file(
72
+ file: T.must(project_file),
73
+ content: updated_project_content
74
+ )
75
+ end
76
+
77
+ # Update Manifest.toml file if it exists and dependencies have changed
78
+ if manifest_file
79
+ updated_manifest_content = build_updated_manifest_content
80
+ if updated_manifest_content != T.must(manifest_file).content
81
+ updated_files << updated_file(
82
+ file: T.must(manifest_file),
83
+ content: updated_manifest_content
84
+ )
85
+ end
86
+ end
87
+
88
+ raise "No files changed!" if updated_files.empty?
89
+
90
+ updated_files
91
+ end
92
+
93
+ private
94
+
95
+ sig { returns(T::Hash[String, String]) }
96
+ def build_updates_hash
97
+ updates = {}
98
+ dependencies.each do |dependency|
99
+ next unless dependency.version
100
+
101
+ updates[dependency.name] = dependency.version
102
+ end
103
+ updates
104
+ end
105
+
106
+ sig { params(result: T::Hash[String, T.untyped]).returns(T::Array[Dependabot::DependencyFile]) }
107
+ def build_updated_files_from_result(result)
108
+ updated_files = T.let([], T::Array[Dependabot::DependencyFile])
109
+
110
+ if result["project_content"] && result["project_content"] != T.must(project_file).content
111
+ updated_files << updated_file(
112
+ file: T.must(project_file),
113
+ content: result["project_content"]
114
+ )
115
+ end
116
+
117
+ if manifest_file && result["manifest_content"] &&
118
+ result["manifest_content"] != T.must(manifest_file).content
119
+ updated_files << updated_file(
120
+ file: T.must(manifest_file),
121
+ content: result["manifest_content"]
122
+ )
123
+ end
124
+
125
+ updated_files
126
+ end
127
+
128
+ # Helper methods for DependabotHelper.jl integration
129
+
130
+ sig { returns(Dependabot::Julia::RegistryClient) }
131
+ def registry_client
132
+ @registry_client ||= T.let(
133
+ Dependabot::Julia::RegistryClient.new(
134
+ credentials: credentials
135
+ ),
136
+ T.nilable(Dependabot::Julia::RegistryClient)
137
+ )
138
+ end
139
+
140
+ sig { returns(String) }
141
+ def write_temp_project_file
142
+ temp_file = Tempfile.new(["Project", ".toml"])
143
+ temp_file.write(T.must(project_file).content)
144
+ temp_file.close
145
+ T.must(temp_file.path)
146
+ end
147
+
148
+ sig { override.void }
149
+ def check_required_files
150
+ return if dependency_files.empty?
151
+
152
+ return if dependency_files.any? { |f| f.name.match?(/^(Julia)?Project\.toml$/i) }
153
+
154
+ raise Dependabot::DependencyFileNotFound, "No Project.toml or JuliaProject.toml found."
155
+ end
156
+
157
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
158
+ def project_file
159
+ @project_file ||= T.let(
160
+ dependency_files.find do |f|
161
+ f.name.match?(/^(Julia)?Project\.toml$/i)
162
+ end,
163
+ T.nilable(Dependabot::DependencyFile)
164
+ )
165
+ end
166
+
167
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
168
+ def manifest_file
169
+ @manifest_file ||= T.let(
170
+ dependency_files.find do |f|
171
+ f.name.match?(/^(Julia)?Manifest(?:-v[\d.]+)?\.toml$/i)
172
+ end,
173
+ T.nilable(Dependabot::DependencyFile)
174
+ )
175
+ end
176
+
177
+ sig { returns(String) }
178
+ def updated_project_content
179
+ return T.must(T.must(project_file).content) unless project_file
180
+
181
+ content = T.must(T.must(project_file).content)
182
+
183
+ dependencies.each do |dependency|
184
+ # Find the new requirement for this dependency
185
+ new_requirement = dependency.requirements
186
+ .find { |req| T.cast(req[:file], String) == T.must(project_file).name }
187
+ &.fetch(:requirement)
188
+
189
+ next unless new_requirement
190
+
191
+ content = update_dependency_requirement_in_content(content, dependency.name, new_requirement)
192
+ end
193
+
194
+ content
195
+ end
196
+
197
+ sig { params(content: String, dependency_name: String, new_requirement: String).returns(String) }
198
+ def update_dependency_requirement_in_content(content, dependency_name, new_requirement)
199
+ # Pattern to match the dependency in [compat] section
200
+ # Handles various quote styles and spacing
201
+ pattern = /(^\s*#{Regexp.escape(dependency_name)}\s*=\s*)(?:"[^"]*"|'[^']*'|[^\s#\n]+)(\s*(?:\#.*)?$)/mx
202
+
203
+ if content.match?(pattern)
204
+ # Replace existing entry
205
+ content.gsub(pattern, "\\1\"#{new_requirement}\"\\2")
206
+ else
207
+ # Add new entry to [compat] section
208
+ add_compat_entry_to_content(content, dependency_name, new_requirement)
209
+ end
210
+ end
211
+
212
+ sig { params(content: String, dependency_name: String, requirement: String).returns(String) }
213
+ def add_compat_entry_to_content(content, dependency_name, requirement)
214
+ # Find [compat] section or create it
215
+ if content.match?(/^\s*\[compat\]\s*$/m)
216
+ # Add to existing [compat] section
217
+ content.gsub(/(\[compat\]\s*\n)/, "\\1#{dependency_name} = \"#{requirement}\"\n")
218
+ else
219
+ # Add new [compat] section at the end
220
+ content + "\n[compat]\n#{dependency_name} = \"#{requirement}\"\n"
221
+ end
222
+ end
223
+
224
+ sig { returns(String) }
225
+ def build_updated_manifest_content
226
+ return T.must(T.must(manifest_file).content) unless manifest_file
227
+
228
+ content = T.must(T.must(manifest_file).content)
229
+
230
+ dependencies.each do |dependency|
231
+ next unless dependency.version
232
+
233
+ content = update_dependency_version_in_manifest(content, dependency.name, T.must(dependency.version))
234
+ end
235
+
236
+ content
237
+ end
238
+
239
+ sig { params(content: String, dependency_name: String, new_version: String).returns(String) }
240
+ def update_dependency_version_in_manifest(content, dependency_name, new_version)
241
+ # Pattern to find the dependency entry and update its version
242
+ # Matches the [[deps.DependencyName]] section and updates the version line within it
243
+ dep_start = /^\[\[deps\.#{Regexp.escape(dependency_name)}\]\]\s*\n(?:.*\n)*?/
244
+ version_key = /^\s*version\s*=\s*/
245
+ old_version = /(?:"[^"]*"|'[^']*'|[^\s#\n]+)/
246
+ trailing = /\s*(?:\#.*)?$/
247
+ pattern = /(#{dep_start})(#{version_key})#{old_version}(#{trailing})/mx
248
+
249
+ if content.match?(pattern)
250
+ content.gsub(pattern, "\\1\\2\"#{new_version}\"\\3")
251
+ else
252
+ # If pattern doesn't match, fall back to original approach
253
+ Dependabot.logger.warn("Could not find manifest entry for #{dependency_name}, using fallback")
254
+ fallback_update_manifest_content(content, dependency_name, new_version)
255
+ end
256
+ end
257
+
258
+ sig { params(content: String, dependency_name: String, new_version: String).returns(String) }
259
+ def fallback_update_manifest_content(content, dependency_name, new_version)
260
+ # Fallback to parse-and-dump for complex cases
261
+ parsed_manifest = T.cast(TomlRB.parse(content), T::Hash[String, T.untyped])
262
+
263
+ deps_section = T.cast(parsed_manifest["deps"] || {}, T::Hash[String, T.untyped])
264
+ if deps_section[dependency_name]
265
+ dep_entries = deps_section[dependency_name]
266
+ update_dependency_entries(dep_entries, new_version)
267
+ end
268
+
269
+ T.cast(TomlRB.dump(parsed_manifest), String)
270
+ end
271
+
272
+ sig { params(dependency: Dependabot::Dependency, manifest: T::Hash[String, T.untyped]).void }
273
+ def update_dependency_in_manifest(dependency, manifest)
274
+ deps_section = T.cast(manifest["deps"] || {}, T::Hash[String, T.untyped])
275
+ return unless deps_section[dependency.name]
276
+
277
+ dep_entries = deps_section[dependency.name]
278
+ update_dependency_entries(dep_entries, dependency.version)
279
+ end
280
+
281
+ sig { params(dep_entries: T.untyped, version: T.nilable(String)).void }
282
+ def update_dependency_entries(dep_entries, version)
283
+ if dep_entries.is_a?(Array)
284
+ dep_entries.each do |dep_entry|
285
+ dep_entry["version"] = version if dep_entry.is_a?(Hash) && dep_entry["uuid"]
286
+ end
287
+ elsif dep_entries.is_a?(Hash) && dep_entries["uuid"]
288
+ dep_entries["version"] = version
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end
294
+
295
+ Dependabot::FileUpdaters.register("julia", Dependabot::Julia::FileUpdater)