bibliothecary 12.0.0 → 12.1.1

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +2 -1
  3. data/.rubocop.yml +10 -2
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +13 -0
  6. data/Gemfile +16 -1
  7. data/Rakefile +2 -0
  8. data/bibliothecary.gemspec +11 -13
  9. data/bin/bibliothecary +2 -1
  10. data/bin/console +1 -0
  11. data/lib/bibliothecary/analyser/analysis.rb +13 -8
  12. data/lib/bibliothecary/analyser/determinations.rb +2 -0
  13. data/lib/bibliothecary/analyser/matchers.rb +17 -17
  14. data/lib/bibliothecary/analyser.rb +11 -8
  15. data/lib/bibliothecary/cli.rb +3 -1
  16. data/lib/bibliothecary/configuration.rb +3 -8
  17. data/lib/bibliothecary/dependency.rb +17 -15
  18. data/lib/bibliothecary/exceptions.rb +6 -2
  19. data/lib/bibliothecary/file_info.rb +9 -11
  20. data/lib/bibliothecary/multi_parsers/bundler_like_manifest.rb +13 -10
  21. data/lib/bibliothecary/multi_parsers/cyclonedx.rb +10 -8
  22. data/lib/bibliothecary/multi_parsers/dependencies_csv.rb +11 -4
  23. data/lib/bibliothecary/multi_parsers/json_runtime.rb +5 -2
  24. data/lib/bibliothecary/multi_parsers/spdx.rb +24 -19
  25. data/lib/bibliothecary/parsers/bower.rb +5 -3
  26. data/lib/bibliothecary/parsers/cargo.rb +10 -4
  27. data/lib/bibliothecary/parsers/cocoapods.rb +15 -11
  28. data/lib/bibliothecary/parsers/conda.rb +20 -18
  29. data/lib/bibliothecary/parsers/cpan.rb +6 -4
  30. data/lib/bibliothecary/parsers/cran.rb +10 -6
  31. data/lib/bibliothecary/parsers/dub.rb +4 -2
  32. data/lib/bibliothecary/parsers/elm.rb +4 -1
  33. data/lib/bibliothecary/parsers/go.rb +51 -43
  34. data/lib/bibliothecary/parsers/haxelib.rb +2 -1
  35. data/lib/bibliothecary/parsers/julia.rb +5 -1
  36. data/lib/bibliothecary/parsers/maven.rb +93 -77
  37. data/lib/bibliothecary/parsers/meteor.rb +2 -0
  38. data/lib/bibliothecary/parsers/npm.rb +89 -75
  39. data/lib/bibliothecary/parsers/nuget.rb +37 -28
  40. data/lib/bibliothecary/parsers/packagist.rb +21 -11
  41. data/lib/bibliothecary/parsers/pub.rb +4 -2
  42. data/lib/bibliothecary/parsers/pypi.rb +48 -29
  43. data/lib/bibliothecary/parsers/rubygems.rb +16 -12
  44. data/lib/bibliothecary/parsers/shard.rb +10 -7
  45. data/lib/bibliothecary/purl_util.rb +2 -1
  46. data/lib/bibliothecary/related_files_info.rb +7 -8
  47. data/lib/bibliothecary/runner/multi_manifest_filter.rb +5 -4
  48. data/lib/bibliothecary/runner.rb +13 -10
  49. data/lib/bibliothecary/version.rb +3 -1
  50. data/lib/bibliothecary.rb +7 -4
  51. data/lib/sdl_parser.rb +11 -6
  52. metadata +19 -106
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
 
3
5
  module Bibliothecary
@@ -37,29 +39,29 @@ module Bibliothecary
37
39
  add_multi_parser(Bibliothecary::MultiParsers::Spdx)
38
40
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
39
41
 
40
- def self.parse_package_lock(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
42
+ def self.parse_package_lock(file_contents, options: {})
41
43
  manifest = JSON.parse(file_contents)
42
44
  # https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#lockfileversion
43
45
  if manifest["lockfileVersion"].to_i <= 1
44
46
  # lockfileVersion 1 uses the "dependencies" object
45
- parse_package_lock_v1(manifest)
47
+ parse_package_lock_v1(manifest, options.fetch(:filename, nil))
46
48
  else
47
49
  # lockfileVersion 2 has backwards-compatability by including both "packages" and the legacy "dependencies" object
48
50
  # lockfileVersion 3 has no backwards-compatibility and only includes the "packages" object
49
- parse_package_lock_v2(manifest)
51
+ parse_package_lock_v2(manifest, options.fetch(:filename, nil))
50
52
  end
51
53
  end
52
54
 
53
55
  class << self
54
56
  # "package-lock.json" and "npm-shrinkwrap.json" have same format, so use same parsing logic
55
- alias_method :parse_shrinkwrap, :parse_package_lock
57
+ alias parse_shrinkwrap parse_package_lock
56
58
  end
57
59
 
58
- def self.parse_package_lock_v1(manifest)
59
- parse_package_lock_deps_recursively(manifest.fetch("dependencies", []))
60
+ def self.parse_package_lock_v1(manifest, source = nil)
61
+ parse_package_lock_deps_recursively(manifest.fetch("dependencies", []), source)
60
62
  end
61
63
 
62
- def self.parse_package_lock_v2(manifest)
64
+ def self.parse_package_lock_v2(manifest, source = nil)
63
65
  # "packages" is a flat object where each key is the installed location of the dep, e.g. node_modules/foo/node_modules/bar.
64
66
  manifest
65
67
  .fetch("packages")
@@ -73,33 +75,39 @@ module Bibliothecary
73
75
  Dependency.new(
74
76
  name: name.split("node_modules/").last,
75
77
  requirement: dep["version"],
76
- type: dep.fetch("dev", false) || dep.fetch("devOptional", false) ? "development" : "runtime",
78
+ type: dep.fetch("dev", false) || dep.fetch("devOptional", false) ? "development" : "runtime",
77
79
  local: dep.fetch("link", false),
80
+ source: source
78
81
  )
79
82
  end
80
83
  end
81
84
 
82
- def self.parse_package_lock_deps_recursively(dependencies, depth=1)
85
+ def self.parse_package_lock_deps_recursively(dependencies, source = nil, depth = 1)
83
86
  dependencies.flat_map do |name, requirement|
84
87
  type = requirement.fetch("dev", false) ? "development" : "runtime"
85
88
  version = requirement.key?("from") ? requirement["from"][/#(?:semver:)?v?(.*)/, 1] : nil
86
89
  version ||= requirement["version"].split("#").last
87
90
  child_dependencies = if depth >= PACKAGE_LOCK_JSON_MAX_DEPTH
88
- []
89
- else
90
- parse_package_lock_deps_recursively(requirement.fetch("dependencies", []), depth + 1)
91
+ []
92
+ else
93
+ parse_package_lock_deps_recursively(requirement.fetch("dependencies", []), source, depth + 1)
91
94
  end
92
95
 
93
96
  [Dependency.new(
94
97
  name: name,
95
98
  requirement: version,
96
99
  type: type,
100
+ source: source
97
101
  )] + child_dependencies
98
102
  end
99
103
  end
100
104
 
101
- def self.parse_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
105
+ def self.parse_manifest(file_contents, options: {})
106
+ # on ruby 3.2 we suddenly get this JSON error, so detect and return early: "package.json: unexpected token at ''"
107
+ return [] if file_contents.empty?
108
+
102
109
  manifest = JSON.parse(file_contents)
110
+
103
111
  raise "appears to be a lockfile rather than manifest format" if manifest.key?("lockfileVersion")
104
112
 
105
113
  dependencies = manifest.fetch("dependencies", [])
@@ -109,7 +117,8 @@ module Bibliothecary
109
117
  name: name,
110
118
  requirement: requirement,
111
119
  type: "runtime",
112
- local: requirement.start_with?("file:")
120
+ local: requirement.start_with?("file:"),
121
+ source: options.fetch(:filename, nil)
113
122
  )
114
123
  end
115
124
 
@@ -120,19 +129,20 @@ module Bibliothecary
120
129
  name: name,
121
130
  requirement: requirement,
122
131
  type: "development",
123
- local: requirement.start_with?("file:")
132
+ local: requirement.start_with?("file:"),
133
+ source: options.fetch(:filename, nil)
124
134
  )
125
135
  end
126
136
 
127
137
  dependencies
128
138
  end
129
139
 
130
- def self.parse_yarn_lock(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
140
+ def self.parse_yarn_lock(file_contents, options: {})
131
141
  dep_hash = if file_contents.match(/__metadata:/)
132
- parse_v2_yarn_lock(file_contents)
133
- else
134
- parse_v1_yarn_lock(file_contents)
135
- end
142
+ parse_v2_yarn_lock(file_contents, options.fetch(:filename, nil))
143
+ else
144
+ parse_v1_yarn_lock(file_contents, options.fetch(:filename, nil))
145
+ end
136
146
 
137
147
  dep_hash.map do |dep|
138
148
  Dependency.new(
@@ -140,67 +150,70 @@ module Bibliothecary
140
150
  requirement: dep[:version],
141
151
  type: "runtime", # lockfile doesn't tell us more about the type of dep
142
152
  local: dep[:requirements]&.first&.start_with?("file:"),
153
+ source: options.fetch(:filename, nil)
143
154
  )
144
155
  end
145
- end
156
+ end
146
157
 
147
- # Returns a hash representation of the deps in yarn.lock, eg:
148
- # [{
149
- # name: "foo",
150
- # requirements: [["foo", "^1.0.0"], ["foo", "^1.0.1"]],
151
- # version: "1.2.0",
152
- # }, ...]
153
- def self.parse_v1_yarn_lock(contents)
154
- contents
155
- .gsub(/^#.*/, "")
156
- .strip
157
- .split("\n\n")
158
- .map do |chunk|
159
- requirements = chunk
160
- .lines
161
- .find { |l| !l.start_with?(" ") && l.strip.end_with?(":") } # first line, eg: '"@bar/foo@1.0.0", "@bar/foo@^1.0.1":'
162
- .strip
163
- .gsub(/"|:$/, "") # don't need quotes or trailing colon
164
- .split(",") # split the list of requirements
165
- .map { |d| d.strip.split(/(?<!^)@/, 2) } # split each requirement on name/version "@"", not on leading namespace "@"
166
- version = chunk.match(/version "?([^"]*)"?/)[1]
167
-
168
- {
169
- name: requirements.first.first,
170
- requirements: requirements.map { |x| x[1] },
171
- version: version,
172
- }
173
- end
174
- end
158
+ # Returns a hash representation of the deps in yarn.lock, eg:
159
+ # [{
160
+ # name: "foo",
161
+ # requirements: [["foo", "^1.0.0"], ["foo", "^1.0.1"]],
162
+ # version: "1.2.0",
163
+ # }, ...]
164
+ def self.parse_v1_yarn_lock(contents, source = nil)
165
+ contents
166
+ .gsub(/^#.*/, "")
167
+ .strip
168
+ .split("\n\n")
169
+ .map do |chunk|
170
+ requirements = chunk
171
+ .lines
172
+ .find { |l| !l.start_with?(" ") && l.strip.end_with?(":") } # first line, eg: '"@bar/foo@1.0.0", "@bar/foo@^1.0.1":'
173
+ .strip
174
+ .gsub(/"|:$/, "") # don't need quotes or trailing colon
175
+ .split(",") # split the list of requirements
176
+ .map { |d| d.strip.split(/(?<!^)@/, 2) } # split each requirement on name/version "@"", not on leading namespace "@"
177
+ version = chunk.match(/version "?([^"]*)"?/)[1]
178
+
179
+ {
180
+ name: requirements.first.first,
181
+ requirements: requirements.map { |x| x[1] },
182
+ version: version,
183
+ source: source,
184
+ }
185
+ end
186
+ end
175
187
 
176
- def self.parse_v2_yarn_lock(contents)
177
- parsed = YAML.load(contents)
178
- parsed = parsed.except("__metadata")
179
- parsed
180
- .reject do |packages, info|
181
- # yarn v4+ creates a lockfile entry: "myproject@workspace" with a "use.local" version
182
- # this lockfile entry is a reference to the project to which the lockfile belongs
183
- # skip this self-referential package
184
- info["version"].to_s.include?("use.local") && packages.include?("workspace")
185
- end
186
- .map do |packages, info|
187
- packages = packages.split(", ")
188
- # use first requirement's name, assuming that deps will always resolve from deps of the same name
189
- name = packages.first.rpartition("@").first
190
- requirements = packages.map { |p| p.rpartition("@").last.gsub(/^.*:/, "") }
188
+ def self.parse_v2_yarn_lock(contents, source = nil)
189
+ parsed = YAML.load(contents)
190
+ parsed = parsed.except("__metadata")
191
+ parsed
192
+ .reject do |packages, info|
193
+ # yarn v4+ creates a lockfile entry: "myproject@workspace" with a "use.local" version
194
+ # this lockfile entry is a reference to the project to which the lockfile belongs
195
+ # skip this self-referential package
196
+ info["version"].to_s.include?("use.local") && packages.include?("workspace")
197
+ end
198
+ .map do |packages, info|
199
+ packages = packages.split(", ")
200
+ # use first requirement's name, assuming that deps will always resolve from deps of the same name
201
+ name = packages.first.rpartition("@").first
202
+ requirements = packages.map { |p| p.rpartition("@").last.gsub(/^.*:/, "") }
191
203
 
192
- {
193
- name: name,
194
- requirements: requirements,
195
- version: info["version"].to_s,
196
- }
197
- end
198
- end
204
+ {
205
+ name: name,
206
+ requirements: requirements,
207
+ version: info["version"].to_s,
208
+ source: source,
209
+ }
210
+ end
211
+ end
199
212
 
200
- def self.parse_ls(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
213
+ def self.parse_ls(file_contents, options: {})
201
214
  manifest = JSON.parse(file_contents)
202
215
 
203
- transform_tree_to_array(manifest.fetch("dependencies", {}))
216
+ transform_tree_to_array(manifest.fetch("dependencies", {}), options.fetch(:filename, nil))
204
217
  end
205
218
 
206
219
  def self.lockfile_preference_order(file_infos)
@@ -215,15 +228,16 @@ module Bibliothecary
215
228
  end
216
229
  end
217
230
 
218
- private_class_method def self.transform_tree_to_array(deps_by_name)
231
+ private_class_method def self.transform_tree_to_array(deps_by_name, source = nil)
219
232
  deps_by_name.map do |name, metadata|
220
233
  [
221
234
  Dependency.new(
222
235
  name: name,
223
236
  requirement: metadata["version"],
224
237
  type: "runtime",
238
+ source: source
225
239
  ),
226
- ] + transform_tree_to_array(metadata.fetch("dependencies", {}))
240
+ ] + transform_tree_to_array(metadata.fetch("dependencies", {}), source)
227
241
  end.flatten(1)
228
242
  end
229
243
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "ox"
2
4
  require "json"
3
5
 
@@ -48,23 +50,24 @@ module Bibliothecary
48
50
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
49
51
  add_multi_parser(Bibliothecary::MultiParsers::Spdx)
50
52
 
51
- def self.parse_project_lock_json(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
53
+ def self.parse_project_lock_json(file_contents, options: {})
52
54
  manifest = JSON.parse file_contents
53
- manifest.fetch("libraries",[]).map do |name, _requirement|
55
+ manifest.fetch("libraries", []).map do |name, _requirement|
54
56
  dep = name.split("/")
55
57
  Dependency.new(
56
58
  name: dep[0],
57
59
  requirement: dep[1],
58
60
  type: "runtime",
61
+ source: options.fetch(:filename, nil)
59
62
  )
60
63
  end
61
64
  end
62
65
 
63
- def self.parse_packages_lock_json(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
66
+ def self.parse_packages_lock_json(file_contents, options: {})
64
67
  manifest = JSON.parse file_contents
65
68
 
66
69
  frameworks = {}
67
- manifest.fetch("dependencies",[]).each do |framework, deps|
70
+ manifest.fetch("dependencies", []).each do |framework, deps|
68
71
  frameworks[framework] = deps
69
72
  .reject { |_name, details| details["type"] == "Project" } # Projects do not have versions
70
73
  .map do |name, details|
@@ -74,44 +77,46 @@ module Bibliothecary
74
77
  # so fallback to requested is pure paranoia
75
78
  requirement: details.fetch("resolved", details.fetch("requested", "*")),
76
79
  type: "runtime",
80
+ source: options.fetch(:filename, nil)
77
81
  )
78
82
  end
79
83
  end
80
84
 
81
- if frameworks.size > 0
85
+ unless frameworks.empty?
82
86
  # we should really return multiple manifests, but bibliothecary doesn't
83
87
  # do that yet so at least pick deterministically.
84
88
 
85
89
  # Note, frameworks can be empty, so remove empty ones and then return the last sorted item if any
86
90
  frameworks = frameworks.delete_if { |_k, v| v.empty? }
87
- return frameworks[frameworks.keys.sort.last] unless frameworks.empty?
91
+ return frameworks[frameworks.keys.max] unless frameworks.empty?
88
92
  end
89
93
  []
90
94
  end
91
95
 
92
- def self.parse_packages_config(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
96
+ def self.parse_packages_config(file_contents, options: {})
93
97
  manifest = Ox.parse file_contents
94
98
  manifest.packages.locate("package").map do |dependency|
95
99
  Dependency.new(
96
100
  name: dependency.id,
97
101
  requirement: (dependency.version if dependency.respond_to? "version"),
98
102
  type: dependency.respond_to?("developmentDependency") && dependency.developmentDependency == "true" ? "development" : "runtime",
103
+ source: options.fetch(:filename, nil)
99
104
  )
100
105
  end
101
- rescue
106
+ rescue StandardError
102
107
  []
103
108
  end
104
109
 
105
- def self.parse_csproj(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
110
+ def self.parse_csproj(file_contents, options: {})
106
111
  manifest = Ox.parse file_contents
107
112
 
108
- packages = manifest.locate("ItemGroup/PackageReference").select{ |dep| dep.respond_to? "Include" }.map do |dependency|
113
+ packages = manifest.locate("ItemGroup/PackageReference").select { |dep| dep.respond_to? "Include" }.map do |dependency|
109
114
  requirement = (dependency.Version if dependency.respond_to? "Version")
110
115
  if requirement.is_a?(Ox::Element)
111
- requirement = dependency.nodes.detect{ |n| n.value == "Version" }&.text
116
+ requirement = dependency.nodes.detect { |n| n.value == "Version" }&.text
112
117
  end
113
118
 
114
- type = if dependency.nodes.first && dependency.nodes.first.nodes.include?("all") && dependency.nodes.first.value.include?("PrivateAssets") || dependency.attributes[:PrivateAssets] == "All"
119
+ type = if (dependency.nodes.first&.nodes&.include?("all") && dependency.nodes.first.value.include?("PrivateAssets")) || dependency.attributes[:PrivateAssets] == "All"
115
120
  "development"
116
121
  else
117
122
  "runtime"
@@ -121,27 +126,29 @@ module Bibliothecary
121
126
  name: dependency.Include,
122
127
  requirement: requirement,
123
128
  type: type,
129
+ source: options.fetch(:filename, nil)
124
130
  )
125
131
  end
126
- packages.uniq {|package| package.name }
127
- rescue
132
+ packages.uniq(&:name)
133
+ rescue StandardError
128
134
  []
129
135
  end
130
136
 
131
- def self.parse_nuspec(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
137
+ def self.parse_nuspec(file_contents, options: {})
132
138
  manifest = Ox.parse file_contents
133
139
  manifest.package.metadata.dependencies.locate("dependency").map do |dependency|
134
140
  Dependency.new(
135
141
  name: dependency.id,
136
142
  requirement: dependency.attributes[:version],
137
143
  type: dependency.respond_to?("developmentDependency") && dependency.developmentDependency == "true" ? "development" : "runtime",
144
+ source: options.fetch(:filename, nil)
138
145
  )
139
146
  end
140
- rescue
147
+ rescue StandardError
141
148
  []
142
149
  end
143
150
 
144
- def self.parse_paket_lock(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
151
+ def self.parse_paket_lock(file_contents, options: {})
145
152
  lines = file_contents.split("\n")
146
153
  package_version_re = /\s+(?<name>\S+)\s\((?<version>\d+\.\d+[\.\d+[\.\d+]*]*)\)/
147
154
  packages = lines.select { |line| package_version_re.match(line) }.map { |line| package_version_re.match(line) }.map do |match|
@@ -149,36 +156,38 @@ module Bibliothecary
149
156
  name: match[:name].strip,
150
157
  requirement: match[:version],
151
158
  type: "runtime",
159
+ source: options.fetch(:filename, nil)
152
160
  )
153
161
  end
154
162
  # we only have to enforce uniqueness by name because paket ensures that there is only the single version globally in the project
155
- packages.uniq {|package| package.name }
163
+ packages.uniq(&:name)
156
164
  end
157
165
 
158
- def self.parse_project_assets_json(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
166
+ def self.parse_project_assets_json(file_contents, options: {})
159
167
  manifest = JSON.parse file_contents
160
168
 
161
169
  frameworks = {}
162
- manifest.fetch("targets",[]).each do |framework, deps|
170
+ manifest.fetch("targets", []).each do |framework, deps|
163
171
  frameworks[framework] = deps
164
172
  .select { |_name, details| details["type"] == "package" }
165
173
  .map do |name, _details|
166
- name_split = name.split("/")
167
- Dependency.new(
168
- name: name_split[0],
169
- requirement: name_split[1],
170
- type: "runtime",
171
- )
174
+ name_split = name.split("/")
175
+ Dependency.new(
176
+ name: name_split[0],
177
+ requirement: name_split[1],
178
+ type: "runtime",
179
+ source: options.fetch(:filename, nil)
180
+ )
172
181
  end
173
182
  end
174
183
 
175
- if frameworks.size > 0
184
+ unless frameworks.empty?
176
185
  # we should really return multiple manifests, but bibliothecary doesn't
177
186
  # do that yet so at least pick deterministically.
178
187
 
179
188
  # Note, frameworks can be empty, so remove empty ones and then return the last sorted item if any
180
189
  frameworks = frameworks.delete_if { |_k, v| v.empty? }
181
- return frameworks[frameworks.keys.sort.last] unless frameworks.empty?
190
+ return frameworks[frameworks.keys.max] unless frameworks.empty?
182
191
  end
183
192
  []
184
193
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
 
3
5
  module Bibliothecary
@@ -22,39 +24,47 @@ module Bibliothecary
22
24
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
23
25
  add_multi_parser(Bibliothecary::MultiParsers::Spdx)
24
26
 
25
- def self.parse_lockfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
27
+ def self.parse_lockfile(file_contents, options: {})
26
28
  manifest = JSON.parse file_contents
27
- manifest.fetch("packages",[]).map do |dependency|
29
+ manifest.fetch("packages", []).map do |dependency|
28
30
  requirement = dependency["version"]
29
31
 
30
32
  # Store Drupal version if Drupal, but include the original manifest version for reference
31
- original_requirement, requirement = requirement, dependency.dig("source", "reference") if is_drupal_module(dependency)
33
+ if drupal_module?(dependency)
34
+ original_requirement = requirement
35
+ requirement = dependency.dig("source", "reference")
36
+ end
32
37
 
33
38
  Dependency.new(
34
39
  name: dependency["name"],
35
40
  requirement: requirement,
36
41
  type: "runtime",
37
- original_requirement: original_requirement
42
+ original_requirement: original_requirement,
43
+ source: options.fetch(:filename, nil)
38
44
  )
39
- end + manifest.fetch("packages-dev",[]).map do |dependency|
45
+ end + manifest.fetch("packages-dev", []).map do |dependency|
40
46
  requirement = dependency["version"]
41
47
 
42
48
  # Store Drupal version if Drupal, but include the original manifest version for reference
43
- original_requirement, requirement = requirement, dependency.dig("source", "reference") if is_drupal_module(dependency)
49
+ if drupal_module?(dependency)
50
+ original_requirement = requirement
51
+ requirement = dependency.dig("source", "reference")
52
+ end
44
53
 
45
54
  Dependency.new(
46
55
  name: dependency["name"],
47
56
  requirement: requirement,
48
57
  type: "development",
49
- original_requirement: original_requirement
58
+ original_requirement: original_requirement,
59
+ source: options.fetch(:filename, nil)
50
60
  )
51
61
  end
52
62
  end
53
63
 
54
- def self.parse_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
64
+ def self.parse_manifest(file_contents, options: {})
55
65
  manifest = JSON.parse file_contents
56
- map_dependencies(manifest, "require", "runtime") +
57
- map_dependencies(manifest, "require-dev", "development")
66
+ map_dependencies(manifest, "require", "runtime", options.fetch(:filename, nil)) +
67
+ map_dependencies(manifest, "require-dev", "development", options.fetch(:filename, nil))
58
68
  end
59
69
 
60
70
  # Drupal hosts its own Composer repository, where its "modules" are indexed and searchable. The best way to
@@ -65,7 +75,7 @@ module Bibliothecary
65
75
  # (https://www.drupal.org/project/project_composer/issues/2622450),
66
76
  # so we return the Drupal requirement instead of semver requirement if it's here
67
77
  # (https://www.drupal.org/docs/develop/using-composer/using-composer-to-install-drupal-and-manage-dependencies#s-about-semantic-versioning)
68
- private_class_method def self.is_drupal_module(dependency)
78
+ private_class_method def self.drupal_module?(dependency)
69
79
  dependency["type"] =~ /drupal/ && dependency.dig("source", "reference")
70
80
  end
71
81
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "yaml"
2
4
 
3
5
  module Bibliothecary
@@ -23,7 +25,7 @@ module Bibliothecary
23
25
  def self.parse_yaml_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
24
26
  manifest = YAML.load file_contents
25
27
  map_dependencies(manifest, "dependencies", "runtime") +
26
- map_dependencies(manifest, "dev_dependencies", "development")
28
+ map_dependencies(manifest, "dev_dependencies", "development")
27
29
  end
28
30
 
29
31
  def self.parse_yaml_lockfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
@@ -32,7 +34,7 @@ module Bibliothecary
32
34
  Dependency.new(
33
35
  name: name,
34
36
  requirement: dep["version"],
35
- type: "runtime",
37
+ type: "runtime"
36
38
  )
37
39
  end
38
40
  end