bibliothecary 12.1.2 → 12.1.3

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: 1c70a38503e81b9c6d0abd51195077a225e09835ebedd66babbb55dd6742c0b4
4
- data.tar.gz: f6c02ba982f9661216fdc1b2e4ebe80d879d278d5583188db9e41664b7f35ae8
3
+ metadata.gz: b061a0f0ac234c87fb6821f0575ecb1f4fe7b9a217fdeec726cf5d9389a6264a
4
+ data.tar.gz: f4479d3b94254de19b34f0e0773ef6fdd4459b7fe388fe80a1567686b70c2a34
5
5
  SHA512:
6
- metadata.gz: 7b09e212ea2c5fe442ed23e1008d5e9c4abb50aab2cee4fd8314097566b6b5238af49bf71c83720b9b4ed4ceeb7e014a16b2ee891da5f22e1a8c2ff9f9c20f85
7
- data.tar.gz: b2ae2d6f40eb17538b7f0241db8008db6bfd9cb61f52651562458c036dfc004d6356f833015b12ca6471f18a3777f2838c37ea26c64c1ee7059b039d970f8b2a
6
+ metadata.gz: 8ba28c715feabd5561329e72361c7b01aae12987ddd1bd338c77d31e079b220c2ec0f08e8d03ac9386c08196b3d77a4bd8a5b01a18bff1c432efcae9034d00b8
7
+ data.tar.gz: 97d7c4fadc853771ca46a4a08da9d79c393d8c7048fc0dc867685115d632e0178fdc18fc6964e8aa9ad5c737d706ac4ba0ca0425ef7bdf483b9c8b6191d1ce98
data/Gemfile CHANGED
@@ -20,5 +20,6 @@ group :test do
20
20
  gem "codeclimate-test-reporter", "~> 1.0.0"
21
21
  gem "rspec", "~> 3.0"
22
22
  gem "simplecov"
23
+ gem "super_diff", "~> 0.15.0"
23
24
  gem "webmock"
24
25
  end
@@ -254,34 +254,89 @@ module Bibliothecary
254
254
  .uniq
255
255
  end
256
256
 
257
- def self.parse_maven_tree(file_contents, options: {})
258
- captures = file_contents
257
+ # Return each item in the ascii art tree with a depth of that item,
258
+ # like [[0, "groupId:artifactId:jar:version:scope"], [1, "..."], ...]
259
+ # The depth-0 items are the (sub)project names
260
+ # These are in the original order, with no de-duplication.
261
+ def self.parse_maven_tree_items_with_depths(file_contents)
262
+ file_contents
259
263
  .gsub(ANSI_MATCHER, "")
260
264
  .gsub(/\r\n?/, "\n")
261
- .scan(/^\[INFO\](?:(?:\+-)|\||(?:\\-)|\s)+((?:[\w\.-]+:)+[\w\.\-${}]+)/)
262
- .flatten
263
- .uniq
265
+ # capture two groups; one is the ASCII art telling us the tree depth,
266
+ # and two is the actual dependency
267
+ .scan(/^\[INFO\]\s((?:[-+|\\]|\s)*)((?:[\w\.-]+:)+[\w\.\-${}]+)/)
268
+ # lines that start with "-" aren't part of the tree, example: "[INFO] --- dependency:3.8.1:tree"
269
+ .reject { |(tree_ascii_art, _dep_info)| tree_ascii_art.start_with?("-") }
270
+ .map do |(tree_ascii_art, dep_info)|
271
+ child_marker_index = tree_ascii_art.index(/(\+-)|(\\-)/)
272
+ depth = if child_marker_index.nil?
273
+ 0
274
+ else
275
+ # There are three characters present in the line for each level of depth
276
+ (child_marker_index / 3) + 1
277
+ end
278
+ [depth, dep_info]
279
+ end
280
+ end
281
+
282
+ # split "org.yaml:snakeyaml:jar:2.2:compile" into
283
+ # ["org.yaml:snakeyaml", "2.2", "compile"]
284
+ def self.parse_maven_tree_dependency(item)
285
+ parts = item.split(":")
286
+ case parts.count
287
+ when 4
288
+ version = parts[-1]
289
+ type = parts[-2]
290
+ when 5..6
291
+ version, type = parts[-2..]
292
+ end
293
+
294
+ name = parts[0..1].join(":")
295
+
296
+ [name, version, type]
297
+ end
298
+
299
+ def self.parse_maven_tree(file_contents, options: {})
300
+ keep_subprojects = options.fetch(:keep_subprojects_in_maven_tree, false)
301
+
302
+ items = parse_maven_tree_items_with_depths(file_contents)
303
+
304
+ raise "found no lines with deps in maven-dependency-tree.txt" if items.empty?
264
305
 
265
- deps = captures.map do |item|
266
- parts = item.split(":")
267
- case parts.count
268
- when 4
269
- version = parts[-1]
270
- type = parts[-2]
271
- when 5..6
272
- version, type = parts[-2..]
306
+ projects = {}
307
+
308
+ if keep_subprojects
309
+ # traditional behavior: we only exclude the root project, and only if we parsed multiple lines
310
+ (root_name, root_version, _root_type) = parse_maven_tree_dependency(items.shift[1])
311
+ unless items.empty?
312
+ projects[root_name] = Set.new
313
+ projects[root_name].add(root_version)
273
314
  end
274
- Dependency.new(
275
- name: parts[0..1].join(":"),
276
- requirement: version,
277
- type: type,
278
- source: options.fetch(:filename, nil)
279
- )
280
315
  end
281
316
 
282
- # First dep line will be the package itself (unless we're only analyzing a single line)
283
- package = deps[0]
284
- deps.size < 2 ? deps : deps[1..].reject { |d| d.name == package.name && d.requirement == package.requirement }
317
+ unique_items = items.map do |(depth, item)|
318
+ (name, version, type) = parse_maven_tree_dependency(item)
319
+ if depth == 0 && !keep_subprojects
320
+ # record and then remove the depth 0
321
+ projects[name] ||= Set.new
322
+ projects[name].add(version)
323
+ nil
324
+ else
325
+ [name, version, type]
326
+ end
327
+ end.compact.uniq
328
+
329
+ unique_items
330
+ # drop the projects and subprojects
331
+ .reject { |(name, version, _type)| projects[name]&.include?(version) }
332
+ .map do |(name, version, type)|
333
+ Bibliothecary::Dependency.new(
334
+ name: name,
335
+ requirement: version,
336
+ type: type,
337
+ source: options.fetch(:filename, nil)
338
+ )
339
+ end
285
340
  end
286
341
 
287
342
  def self.parse_resolved_dep_line(line, options: {})
@@ -72,9 +72,22 @@ module Bibliothecary
72
72
  # * The other occurrence's name is the path to the local dependency (which has less information, and is duplicative, so we discard)
73
73
  .select { |name, _dep| name.start_with?("node_modules") }
74
74
  .map do |name, dep|
75
+ # check if the name property is available and differs from the node modules location
76
+ # this indicates that the package has been aliased
77
+ node_module_name = name.split("node_modules/").last
78
+ name_property = dep["name"]
79
+ if !name_property.nil? && node_module_name != name_property
80
+ name = name_property
81
+ original_name = node_module_name
82
+ else
83
+ name = node_module_name
84
+ end
85
+
75
86
  Dependency.new(
76
- name: name.split("node_modules/").last,
87
+ name: name,
88
+ original_name: original_name,
77
89
  requirement: dep["version"],
90
+ original_requirement: original_name.nil? ? nil : dep["version"],
78
91
  type: dep.fetch("dev", false) || dep.fetch("devOptional", false) ? "development" : "runtime",
79
92
  local: dep.fetch("link", false),
80
93
  source: source
@@ -113,9 +126,20 @@ module Bibliothecary
113
126
  dependencies = manifest.fetch("dependencies", [])
114
127
  .reject { |name, _requirement| name.start_with?("//") } # Omit comment keys. They are valid in package.json: https://groups.google.com/g/nodejs/c/NmL7jdeuw0M/m/yTqI05DRQrIJ
115
128
  .map do |name, requirement|
129
+ # check to see if this is an aliased package name
130
+ # example: "alias-package-name": "npm:actual-package@^1.1.3"
131
+ if requirement.include?("npm:")
132
+ # the name of the real dependency is contained in the requirement with the version
133
+ requirement.gsub!("npm:", "")
134
+ original_name = name
135
+ name, _, requirement = requirement.rpartition("@")
136
+ end
137
+
116
138
  Dependency.new(
117
139
  name: name,
140
+ original_name: original_name,
118
141
  requirement: requirement,
142
+ original_requirement: original_name.nil? ? nil : requirement,
119
143
  type: "runtime",
120
144
  local: requirement.start_with?("file:"),
121
145
  source: options.fetch(:filename, nil)
@@ -147,7 +171,9 @@ module Bibliothecary
147
171
  dep_hash.map do |dep|
148
172
  Dependency.new(
149
173
  name: dep[:name],
174
+ original_name: dep[:original_name],
150
175
  requirement: dep[:version],
176
+ original_requirement: dep[:original_requirement],
151
177
  type: "runtime", # lockfile doesn't tell us more about the type of dep
152
178
  local: dep[:requirements]&.first&.start_with?("file:"),
153
179
  source: options.fetch(:filename, nil)
@@ -173,12 +199,17 @@ module Bibliothecary
173
199
  .strip
174
200
  .gsub(/"|:$/, "") # don't need quotes or trailing colon
175
201
  .split(",") # split the list of requirements
176
- .map { |d| d.strip.split(/(?<!^)@/, 2) } # split each requirement on name/version "@"", not on leading namespace "@"
202
+
203
+ name, alias_name = yarn_strip_npm_protocol(requirements.first) # if a package is aliased, strip the alias and return the real package name
204
+ name = name.strip.split(/(?<!^)@/).first
205
+ requirements = requirements.map { |d| d.strip.split(/(?<!^)@/, 2) } # split each requirement on name/version "@"", not on leading namespace "@"
177
206
  version = chunk.match(/version "?([^"]*)"?/)[1]
178
207
 
179
208
  {
180
- name: requirements.first.first,
209
+ name: name,
210
+ original_name: alias_name,
181
211
  requirements: requirements.map { |x| x[1] },
212
+ original_requirement: alias_name.nil? ? nil : version,
182
213
  version: version,
183
214
  source: source,
184
215
  }
@@ -193,17 +224,24 @@ module Bibliothecary
193
224
  # yarn v4+ creates a lockfile entry: "myproject@workspace" with a "use.local" version
194
225
  # this lockfile entry is a reference to the project to which the lockfile belongs
195
226
  # skip this self-referential package
196
- info["version"].to_s.include?("use.local") && packages.include?("workspace")
227
+ (info["version"].to_s.include?("use.local") && packages.include?("workspace")) ||
228
+ # yarn allows users to insert patches to their dependencies from within their project
229
+ # these patches are marked as a separate entry in the lock file but do not represent a new dependency
230
+ # and should be skipped here
231
+ # https://yarnpkg.com/protocol/patch
232
+ packages.include?("@patch:")
197
233
  end
198
234
  .map do |packages, info|
199
235
  packages = packages.split(", ")
200
236
  # use first requirement's name, assuming that deps will always resolve from deps of the same name
201
- name = packages.first.rpartition("@").first
237
+ name, alias_name = yarn_strip_npm_protocol(packages.first.rpartition("@").first)
202
238
  requirements = packages.map { |p| p.rpartition("@").last.gsub(/^.*:/, "") }
203
239
 
204
240
  {
205
241
  name: name,
242
+ original_name: alias_name,
206
243
  requirements: requirements,
244
+ original_requirement: alias_name.nil? ? nil : info["version"].to_s,
207
245
  version: info["version"].to_s,
208
246
  source: source,
209
247
  }
@@ -240,6 +278,20 @@ module Bibliothecary
240
278
  ] + transform_tree_to_array(metadata.fetch("dependencies", {}), source)
241
279
  end.flatten(1)
242
280
  end
281
+
282
+ # Yarn package names can be aliased by using the NPM protocol. If a package name includes
283
+ # the NPM protocol then the name following the @npm: protocol identifier is the name of the
284
+ # actual package being imported into the project under a different alias.
285
+ # https://classic.yarnpkg.com/lang/en/docs/cli/add/#toc-yarn-add-alias
286
+ private_class_method def self.yarn_strip_npm_protocol(dep_name)
287
+ if dep_name.include?("@npm:")
288
+ partitions = dep_name.rpartition("@npm:")
289
+ alias_name = partitions.first
290
+ dep_name = partitions.last
291
+ end
292
+
293
+ [dep_name, alias_name]
294
+ end
243
295
  end
244
296
  end
245
297
  end
@@ -87,7 +87,7 @@ module Bibliothecary
87
87
  # do that yet so at least pick deterministically.
88
88
 
89
89
  # Note, frameworks can be empty, so remove empty ones and then return the last sorted item if any
90
- frameworks = frameworks.delete_if { |_k, v| v.empty? }
90
+ frameworks.delete_if { |_k, v| v.empty? }
91
91
  return frameworks[frameworks.keys.max] unless frameworks.empty?
92
92
  end
93
93
  []
@@ -186,7 +186,7 @@ module Bibliothecary
186
186
  # do that yet so at least pick deterministically.
187
187
 
188
188
  # Note, frameworks can be empty, so remove empty ones and then return the last sorted item if any
189
- frameworks = frameworks.delete_if { |_k, v| v.empty? }
189
+ frameworks.delete_if { |_k, v| v.empty? }
190
190
  return frameworks[frameworks.keys.max] unless frameworks.empty?
191
191
  end
192
192
  []
@@ -13,7 +13,7 @@ module Bibliothecary
13
13
  REQUIRE_REGEXP = /([a-zA-Z0-9]+[a-zA-Z0-9\-_\.]+)(?:\[.*?\])*([><=\w\.,]+)?/
14
14
  REQUIREMENTS_REGEXP = /^#{REQUIRE_REGEXP}/
15
15
 
16
- MANIFEST_REGEXP = /.*require[^\/]*(\/)?[^\/]*\.(txt|pip|in)$/
16
+ MANIFEST_REGEXP = /.*require[^\/]*\.(txt|pip|in)$/
17
17
  # TODO: can this be a more specific regexp so it doesn't match something like ".yarn/cache/create-require-npm-1.0.0.zip"?
18
18
  PIP_COMPILE_REGEXP = /.*require.*$/
19
19
 
@@ -5,11 +5,11 @@ module Bibliothecary
5
5
  # A runner is created every time a file is targeted to be parsed. Don't call
6
6
  # parse methods directory! Use a Runner.
7
7
  class Runner
8
- def initialize(configuration)
8
+ def initialize(configuration, parser_options: {})
9
9
  @configuration = configuration
10
10
  @options = {
11
11
  cache: {},
12
- }
12
+ }.merge(parser_options)
13
13
  end
14
14
 
15
15
  def analyse(path, ignore_unparseable_files: true)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bibliothecary
4
- VERSION = "12.1.2"
4
+ VERSION = "12.1.3"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bibliothecary
3
3
  version: !ruby/object:Gem::Version
4
- version: 12.1.2
4
+ version: 12.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Nesbitt
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-26 00:00:00.000000000 Z
10
+ date: 2025-03-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: commander