dependabot-julia 0.356.0 → 0.357.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '084f4d34864ffbbedb588562ce527fce084f36a5e5ad4f59eba5ac8af0b43aa6'
4
- data.tar.gz: e0c2782c41d890ad0c25890646cb8090b8d7b2b519dd1a2077baaaef772e4544
3
+ metadata.gz: 76fe4785dc95ee847322f2673a67df374eb0d2eebf5a740b966f1c487e2e6375
4
+ data.tar.gz: 174617969c5b36d6aa341f23c310c125794a095a61e3f7de457a874542a8dfb5
5
5
  SHA512:
6
- metadata.gz: 6a3eed4abae611010acce9afa1442dcf53d524817b9aa2fa4702be1287faee6bd673d874d92641a5a5cd95ff9280d3909ee9d57ffc6d15c3f5016aa04eaf8dfe
7
- data.tar.gz: 3d0e3301b92a9d106c7e7c64437e872fc2adcf27307a35ea09a4739686865d74efa3772fee516228fc2ca52b30de926f73de9577a5f580f80af0f0ee70636601
6
+ metadata.gz: 6240ebc3636af1e5b52bf4c18090800555593aa8be82aa4a567f6b4802eec283d103618d135a08ee8b62b695e182f7d245dd8d367c93c7120a5060fb7ec03fa9
7
+ data.tar.gz: df44056fe3096eabd8ba8ff3ce42445cf7c1a68d8f2c3b795c58c12fa0dc7386cc460613033f2598c63463ffa1668ce75c4ade3b7d5357bf3e39212c1d120bc6
@@ -32,33 +32,55 @@ module Dependabot
32
32
 
33
33
  sig { params(temp_dir: T.any(Pathname, String)).returns(T::Array[Dependabot::DependencyFile]) }
34
34
  def fetch_files_using_julia_helper(temp_dir)
35
- # Use Julia helper to identify the correct environment files
36
- env_files = registry_client.find_environment_files(temp_dir.to_s)
35
+ workspace_info = registry_client.find_workspace_project_files(temp_dir.to_s)
36
+ validate_workspace_info!(workspace_info)
37
37
 
38
- if env_files.empty? || !env_files["project_file"]
39
- raise Dependabot::DependencyFileNotFound, "No Project.toml or JuliaProject.toml found."
40
- end
38
+ project_files = T.cast(workspace_info["project_files"], T::Array[String])
39
+ manifest_path = T.cast(workspace_info["manifest_file"], String)
41
40
 
42
- fetched_files = []
41
+ fetched_files = fetch_all_project_files(project_files, temp_dir.to_s)
42
+ raise Dependabot::DependencyFileNotFound, "No Project.toml or JuliaProject.toml found." if fetched_files.empty?
43
43
 
44
- # Fetch the project file identified by Julia helper
45
- project_path = T.must(env_files["project_file"])
46
- project_filename = File.basename(project_path)
47
- fetched_files << fetch_file_from_host(project_filename)
44
+ fetch_manifest_file(fetched_files, manifest_path, project_files, temp_dir.to_s)
45
+ fetched_files
46
+ end
48
47
 
49
- # Fetch the manifest file if Julia helper found one
50
- manifest_path = env_files["manifest_file"]
51
- if manifest_path && !manifest_path.empty?
52
- # Calculate relative path from project to manifest
53
- project_dir = File.dirname(project_path)
54
- manifest_relative = Pathname.new(manifest_path).relative_path_from(Pathname.new(project_dir)).to_s
48
+ sig { params(workspace_info: T::Hash[String, T.untyped]).void }
49
+ def validate_workspace_info!(workspace_info)
50
+ error_value = T.cast(workspace_info["error"], T.nilable(String))
51
+ has_error = !error_value.nil?
52
+ project_files = T.cast(workspace_info["project_files"], T.nilable(T::Array[String]))
53
+ no_projects = project_files.nil? || project_files.empty?
54
+ return unless has_error || no_projects
55
+
56
+ raise Dependabot::DependencyFileNotFound, "No Project.toml or JuliaProject.toml found."
57
+ end
55
58
 
56
- # Fetch manifest (handles workspace cases where manifest is in parent directory)
57
- manifest_file = fetch_file_if_present(manifest_relative)
58
- fetched_files << manifest_file if manifest_file
59
+ sig { params(project_files: T::Array[String], base_dir: String).returns(T::Array[Dependabot::DependencyFile]) }
60
+ def fetch_all_project_files(project_files, base_dir)
61
+ project_files.filter_map do |project_path|
62
+ project_relative = Pathname.new(project_path).relative_path_from(Pathname.new(base_dir)).to_s
63
+ fetch_file_if_present(project_relative)
59
64
  end
65
+ end
60
66
 
61
- fetched_files
67
+ sig do
68
+ params(
69
+ fetched_files: T::Array[Dependabot::DependencyFile],
70
+ manifest_path: String,
71
+ project_files: T::Array[String],
72
+ base_dir: String
73
+ ).void
74
+ end
75
+ def fetch_manifest_file(fetched_files, manifest_path, project_files, base_dir)
76
+ return if manifest_path.empty? || !File.exist?(manifest_path)
77
+
78
+ primary_project_path = project_files.find { |p| File.dirname(p) == base_dir } || project_files.first
79
+ primary_project_dir = File.dirname(T.must(primary_project_path))
80
+ manifest_relative = Pathname.new(manifest_path).relative_path_from(Pathname.new(primary_project_dir)).to_s
81
+
82
+ manifest_file = fetch_file_if_present(manifest_relative)
83
+ fetched_files << manifest_file if manifest_file
62
84
  end
63
85
 
64
86
  sig { returns(Dependabot::Julia::RegistryClient) }
@@ -39,7 +39,6 @@ module Dependabot
39
39
  super
40
40
  @registry_client = T.let(nil, T.nilable(Dependabot::Julia::RegistryClient))
41
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
42
  end
44
43
 
45
44
  sig { override.returns(T::Array[Dependabot::Dependency]) }
@@ -101,94 +100,107 @@ module Dependabot
101
100
  end
102
101
  end
103
102
 
104
- sig { returns(String) }
105
- def write_temp_project_file
106
- @temp_dir ||= Dir.mktmpdir("julia_project")
107
- project_path = File.join(@temp_dir, T.must(project_file).name)
108
- File.write(project_path, T.must(project_file).content)
109
- project_path
110
- end
111
-
112
103
  sig { returns(T::Array[Dependabot::Dependency]) }
113
104
  def project_file_dependencies
114
- dependencies = T.let([], T::Array[Dependabot::Dependency])
115
- return dependencies unless project_file
116
-
117
- # Use DependabotHelper.jl for project parsing
118
- project_path = write_temp_project_file
105
+ dependencies_map = T.let({}, T::Hash[String, Dependabot::Dependency])
119
106
 
120
- begin
121
- result = registry_client.parse_project(project_path: project_path)
122
-
123
- raise Dependabot::DependencyFileNotParseable, result["error"] if result["error"]
124
-
125
- # Convert DependabotHelper.jl result to Dependabot::Dependency objects
126
- dependencies = build_dependencies_from_julia_result(result)
127
- ensure
128
- # Cleanup temporary directory
129
- FileUtils.rm_rf(@temp_dir) if @temp_dir && File.exist?(@temp_dir)
107
+ # Parse all project files in the workspace
108
+ all_project_files.each do |proj_file|
109
+ parse_single_project_file(proj_file, dependencies_map)
130
110
  end
131
111
 
132
- dependencies
112
+ dependencies_map.values
133
113
  end
134
114
 
135
- sig { params(result: T::Hash[String, T.untyped]).returns(T::Array[Dependabot::Dependency]) }
136
- def build_dependencies_from_julia_result(result)
137
- dependencies = T.let([], T::Array[Dependabot::Dependency])
115
+ sig { params(proj_file: Dependabot::DependencyFile, dependencies_map: T::Hash[String, Dependabot::Dependency]).void }
116
+ def parse_single_project_file(proj_file, dependencies_map)
117
+ temp_dir = Dir.mktmpdir("julia_project")
118
+ project_path = File.join(temp_dir, proj_file.name)
119
+ FileUtils.mkdir_p(File.dirname(project_path))
120
+ File.write(project_path, proj_file.content)
138
121
 
139
- # Process dependencies and weak dependencies (matching CompatHelper.jl behavior)
140
- # Note: We don't process dev_dependencies/extras to match CompatHelper.jl
141
- parsed_deps = T.cast(result["dependencies"] || [], T::Array[T.untyped])
142
- dependencies.concat(build_dependencies_from_dep_list(parsed_deps, ["deps"]))
122
+ begin
123
+ result = registry_client.parse_project(project_path: project_path)
124
+
125
+ return if result["error"]
143
126
 
144
- parsed_weak_deps = T.cast(result["weak_dependencies"] || [], T::Array[T.untyped])
145
- dependencies.concat(build_dependencies_from_dep_list(parsed_weak_deps, ["weakdeps"]))
127
+ # Process dependencies
128
+ parsed_deps = T.cast(result["dependencies"] || [], T::Array[T.untyped])
129
+ merge_dependencies_from_list(parsed_deps, ["deps"], proj_file.name, dependencies_map)
146
130
 
147
- dependencies
131
+ # Process weak dependencies
132
+ parsed_weak_deps = T.cast(result["weak_dependencies"] || [], T::Array[T.untyped])
133
+ merge_dependencies_from_list(parsed_weak_deps, ["weakdeps"], proj_file.name, dependencies_map)
134
+ ensure
135
+ FileUtils.rm_rf(temp_dir)
136
+ end
148
137
  end
149
138
 
150
139
  sig do
151
140
  params(
152
141
  dep_list: T::Array[T.untyped],
153
- groups: T::Array[String]
154
- ).returns(T::Array[Dependabot::Dependency])
142
+ groups: T::Array[String],
143
+ file_name: String,
144
+ dependencies_map: T::Hash[String, Dependabot::Dependency]
145
+ ).void
155
146
  end
156
- def build_dependencies_from_dep_list(dep_list, groups)
157
- dep_list.filter_map do |dep_info|
147
+ def merge_dependencies_from_list(dep_list, groups, file_name, dependencies_map)
148
+ dep_list.each do |dep_info|
158
149
  dep_hash = T.cast(dep_info, T::Hash[String, T.untyped])
159
150
  name = T.cast(dep_hash["name"], String)
160
151
  next if name == "julia" # Skip Julia version requirement
161
152
 
162
153
  uuid = T.cast(dep_hash["uuid"], T.nilable(String))
163
- # NOTE: Missing "requirement" means no compat entry (any version acceptable)
164
154
  requirement_string = T.cast(dep_hash["requirement"], T.nilable(String))
165
155
 
166
- Dependabot::Dependency.new(
167
- name: name,
168
- version: nil, # Julia dependencies don't use locked versions
169
- requirements: [{
170
- requirement: requirement_string,
171
- file: T.must(project_file).name,
172
- groups: groups,
173
- source: nil
174
- }],
175
- package_manager: "julia",
176
- metadata: uuid ? { julia_uuid: uuid } : {}
177
- )
156
+ new_requirement = {
157
+ requirement: requirement_string,
158
+ file: file_name,
159
+ groups: groups,
160
+ source: nil
161
+ }
162
+
163
+ if dependencies_map.key?(name)
164
+ # Merge requirements from additional project files
165
+ existing_dep = T.must(dependencies_map[name])
166
+ existing_requirements = existing_dep.requirements.dup
167
+ existing_requirements << new_requirement
168
+ dependencies_map[name] = Dependabot::Dependency.new(
169
+ name: name,
170
+ version: nil,
171
+ requirements: existing_requirements,
172
+ package_manager: "julia",
173
+ metadata: existing_dep.metadata
174
+ )
175
+ else
176
+ # Create new dependency
177
+ dependencies_map[name] = Dependabot::Dependency.new(
178
+ name: name,
179
+ version: nil,
180
+ requirements: [new_requirement],
181
+ package_manager: "julia",
182
+ metadata: uuid ? { julia_uuid: uuid } : {}
183
+ )
184
+ end
178
185
  end
179
186
  end
180
187
 
188
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
189
+ def all_project_files
190
+ dependency_files.select { |f| f.name.match?(/Project\.toml$/i) }
191
+ end
192
+
181
193
  sig { returns(T.nilable(Dependabot::DependencyFile)) }
182
194
  def project_file
183
195
  @project_file ||= T.let(
184
- get_original_file("Project.toml") || get_original_file("JuliaProject.toml"),
196
+ all_project_files.first || get_original_file("Project.toml") || get_original_file("JuliaProject.toml"),
185
197
  T.nilable(Dependabot::DependencyFile)
186
198
  )
187
199
  end
188
200
 
189
201
  sig { override.void }
190
202
  def check_required_files
191
- raise "No Project.toml or JuliaProject.toml!" unless project_file
203
+ raise "No Project.toml or JuliaProject.toml!" if all_project_files.empty?
192
204
  end
193
205
  end
194
206
  end
@@ -52,19 +52,19 @@ module Dependabot
52
52
  updated_files = []
53
53
 
54
54
  SharedHelpers.in_a_temporary_repo_directory(T.must(dependency_files.first).directory, repo_contents_path) do
55
- updated_project = updated_project_content
55
+ # Update all project files (main + workspace members)
56
+ updated_project_files = update_all_project_files
56
57
  actual_manifest = find_manifest_file
57
58
 
58
- return project_only_update(updated_project) if actual_manifest.nil?
59
+ return all_projects_only_update(updated_project_files) if actual_manifest.nil?
59
60
 
60
- # Work directly in the repo directory - no need for another temp directory
61
- # This ensures all workspace packages are accessible to Julia's Pkg
62
- write_temporary_files(updated_project, actual_manifest)
61
+ # Write all updated project files to disk for Julia's Pkg
62
+ write_all_temporary_files(updated_project_files, actual_manifest)
63
63
  result = call_julia_helper
64
64
 
65
- return handle_julia_helper_error(result, actual_manifest, updated_project) if result["error"]
65
+ return handle_julia_helper_error_multi(result, actual_manifest, updated_project_files) if result["error"]
66
66
 
67
- build_updated_files(updated_files, updated_project, actual_manifest, result)
67
+ build_updated_files_multi(updated_files, updated_project_files, actual_manifest, result)
68
68
  end
69
69
 
70
70
  raise "No files changed!" if updated_files.empty?
@@ -72,22 +72,68 @@ module Dependabot
72
72
  updated_files
73
73
  end
74
74
 
75
- sig { params(updated_project: String).returns(T::Array[Dependabot::DependencyFile]) }
76
- def project_only_update(updated_project)
77
- [updated_file(file: T.must(project_file), content: updated_project)]
75
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
76
+ def update_all_project_files
77
+ all_project_files.map do |proj_file|
78
+ {
79
+ file: proj_file,
80
+ content: updated_project_content_for_file(proj_file)
81
+ }
82
+ end
83
+ end
84
+
85
+ sig { params(proj_file: Dependabot::DependencyFile).returns(String) }
86
+ def updated_project_content_for_file(proj_file)
87
+ content = T.must(proj_file.content)
88
+
89
+ dependencies.each do |dependency|
90
+ # Find the new requirement for this dependency in this file
91
+ new_requirement = dependency.requirements
92
+ .find { |req| T.cast(req[:file], String) == proj_file.name }
93
+ &.fetch(:requirement)
94
+
95
+ next unless new_requirement
96
+
97
+ content = update_dependency_requirement_in_content(content, dependency.name, new_requirement)
98
+ end
99
+
100
+ content
101
+ end
102
+
103
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
104
+ def all_project_files
105
+ dependency_files.select { |f| f.name.match?(/Project\.toml$/i) }
106
+ end
107
+
108
+ sig { params(updated_project_files: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Array[Dependabot::DependencyFile]) }
109
+ def all_projects_only_update(updated_project_files)
110
+ updated_project_files.filter_map do |update_info|
111
+ file = T.cast(update_info[:file], Dependabot::DependencyFile)
112
+ content = T.cast(update_info[:content], String)
113
+ next if content == file.content
114
+
115
+ updated_file(file: file, content: content)
116
+ end
78
117
  end
79
118
 
80
119
  sig do
81
120
  params(
82
- updated_project: String,
121
+ updated_project_files: T::Array[T::Hash[Symbol, T.untyped]],
83
122
  actual_manifest: Dependabot::DependencyFile
84
123
  ).void
85
124
  end
86
- def write_temporary_files(updated_project, actual_manifest)
87
- File.write(T.must(project_file).name, updated_project)
125
+ def write_all_temporary_files(updated_project_files, actual_manifest)
126
+ # Write all updated project files
127
+ updated_project_files.each do |update_info|
128
+ file = T.cast(update_info[:file], Dependabot::DependencyFile)
129
+ content = T.cast(update_info[:content], String)
130
+
131
+ file_path = file.name
132
+ FileUtils.mkdir_p(File.dirname(file_path)) if file_path.include?("/")
133
+ File.write(file_path, content)
134
+ end
88
135
 
89
- # Preserve relative paths (e.g., ../Manifest.toml for workspace packages)
90
- # so Julia's Pkg can find and update the correct shared manifest
136
+ # Write manifest file
91
137
  manifest_path = actual_manifest.name
92
138
  FileUtils.mkdir_p(File.dirname(manifest_path)) if manifest_path.include?("/")
93
139
  File.write(manifest_path, actual_manifest.content)
@@ -105,10 +151,10 @@ module Dependabot
105
151
  params(
106
152
  result: T::Hash[String, T.untyped],
107
153
  actual_manifest: Dependabot::DependencyFile,
108
- updated_project: String
154
+ updated_project_files: T::Array[T::Hash[Symbol, T.untyped]]
109
155
  ).returns(T::Array[Dependabot::DependencyFile])
110
156
  end
111
- def handle_julia_helper_error(result, actual_manifest, updated_project)
157
+ def handle_julia_helper_error_multi(result, actual_manifest, updated_project_files)
112
158
  error_message = result["error"]
113
159
  manifest_path = actual_manifest.name
114
160
 
@@ -117,8 +163,8 @@ module Dependabot
117
163
 
118
164
  add_manifest_update_notice(manifest_path, error_message)
119
165
 
120
- # Return only the updated Project.toml
121
- [updated_file(file: T.must(project_file), content: updated_project)]
166
+ # Return all updated Project.toml files
167
+ all_projects_only_update(updated_project_files)
122
168
  end
123
169
 
124
170
  sig { params(error_message: String).returns(T::Boolean) }
@@ -156,13 +202,20 @@ module Dependabot
156
202
  sig do
157
203
  params(
158
204
  updated_files: T::Array[Dependabot::DependencyFile],
159
- updated_project: String,
205
+ updated_project_files: T::Array[T::Hash[Symbol, T.untyped]],
160
206
  actual_manifest: Dependabot::DependencyFile,
161
207
  result: T::Hash[String, T.untyped]
162
208
  ).void
163
209
  end
164
- def build_updated_files(updated_files, updated_project, actual_manifest, result)
165
- updated_files << updated_file(file: T.must(project_file), content: updated_project)
210
+ def build_updated_files_multi(updated_files, updated_project_files, actual_manifest, result)
211
+ # Add all updated project files
212
+ updated_project_files.each do |update_info|
213
+ file = T.cast(update_info[:file], Dependabot::DependencyFile)
214
+ content = T.cast(update_info[:content], String)
215
+ next if content == file.content
216
+
217
+ updated_files << updated_file(file: file, content: content)
218
+ end
166
219
 
167
220
  return unless result["manifest_content"]
168
221
 
@@ -275,26 +328,6 @@ module Dependabot
275
328
  )
276
329
  end
277
330
 
278
- sig { returns(String) }
279
- def updated_project_content
280
- return T.must(T.must(project_file).content) unless project_file
281
-
282
- content = T.must(T.must(project_file).content)
283
-
284
- dependencies.each do |dependency|
285
- # Find the new requirement for this dependency
286
- new_requirement = dependency.requirements
287
- .find { |req| T.cast(req[:file], String) == T.must(project_file).name }
288
- &.fetch(:requirement)
289
-
290
- next unless new_requirement
291
-
292
- content = update_dependency_requirement_in_content(content, dependency.name, new_requirement)
293
- end
294
-
295
- content
296
- end
297
-
298
331
  sig { params(content: String, dependency_name: String, new_requirement: String).returns(String) }
299
332
  def update_dependency_requirement_in_content(content, dependency_name, new_requirement)
300
333
  # Extract the [compat] section to update it specifically
@@ -129,6 +129,25 @@ module Dependabot
129
129
  {}
130
130
  end
131
131
 
132
+ sig { params(directory: String).returns(T::Hash[String, T.untyped]) }
133
+ def find_workspace_project_files(directory)
134
+ result = call_julia_helper(
135
+ function: "find_workspace_project_files",
136
+ args: { directory: directory }
137
+ )
138
+
139
+ return { "error" => result["error"] } if result["error"]
140
+
141
+ {
142
+ "project_files" => result["project_files"] || [],
143
+ "manifest_file" => result["manifest_file"] || "",
144
+ "workspace_root" => result["workspace_root"] || ""
145
+ }
146
+ rescue StandardError => e
147
+ Dependabot.logger.warn("Failed to find workspace project files in #{directory}: #{e.message}")
148
+ { "error" => e.message }
149
+ end
150
+
132
151
  sig do
133
152
  params(
134
153
  manifest_path: String,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-julia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.356.0
4
+ version: 0.357.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.356.0
18
+ version: 0.357.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.356.0
25
+ version: 0.357.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -261,7 +261,7 @@ licenses:
261
261
  - MIT
262
262
  metadata:
263
263
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
264
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.356.0
264
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.357.0
265
265
  rdoc_options: []
266
266
  require_paths:
267
267
  - lib
@@ -276,7 +276,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
276
276
  - !ruby/object:Gem::Version
277
277
  version: 3.3.7
278
278
  requirements: []
279
- rubygems_version: 3.6.9
279
+ rubygems_version: 3.7.2
280
280
  specification_version: 4
281
281
  summary: Provides Dependabot support for Julia
282
282
  test_files: []