dependabot-common 0.400.0 → 0.402.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: ca5d9fe2c8d7aed67f4d924ed8ccbe5ebe3c943ede793a6839d9d56f3451f042
4
- data.tar.gz: ccfff2df64d29f5aadc3da412298efd882fd871cce94a84cba03f80e2f8540d0
3
+ metadata.gz: fc220d7e1f8b9cec96db4b1c7370be208280362af746d10f95b870dc5b8ea157
4
+ data.tar.gz: 28408b1720f78b67fffca107bc3ddc8ba53a44db8d54558de1c66b741f470d90
5
5
  SHA512:
6
- metadata.gz: 315d65c2835be9e73dde08b837325d7091542e02035192f96e866ec84d521eedb18707da905e84c1c7a5ac9ec38e1660c1241eb607ed0eddbe0120be78a9c7ce
7
- data.tar.gz: 5978d638b88f6fc39395421fb3f96c341205f80d9ecec77faaddb53adc9b28f2c719f7c0062987a26d6820baec24c40f28d8412fc94f6250d63cc11c32770e00
6
+ metadata.gz: b87cf3edd3ac55c71a51357328b8ed27ff692112fa005db0df5c99c5321031294a8f393a5ef9c98a1c51e99a91d97ca85bc966819b1fffeae4e5eab96f1845f2
7
+ data.tar.gz: dd31e7780197c72eb73c3cca592cc71c6868db3d65e111d9217e3f8afa7823f277efec547c9abb39b3e201f25ddc148739cebb237c2ca065f8b4c3b46504d81a
@@ -256,6 +256,13 @@ module Dependabot
256
256
  "error-type": "dependency_file_not_resolvable",
257
257
  "error-detail": { message: error.message }
258
258
  }
259
+ # TODO: switch to a dedicated error type once dependabot-api accepts one.
260
+ # Until then, reuse a type whose message is shown to users.
261
+ when Dependabot::DependencyChecksumMismatch
262
+ {
263
+ "error-type": "dependency_file_not_resolvable",
264
+ "error-detail": { message: error.message }
265
+ }
259
266
  when Dependabot::BranchNameFormattingError
260
267
  {
261
268
  "error-type": "branch_name_formatting_error",
@@ -720,6 +727,27 @@ module Dependabot
720
727
 
721
728
  class DependencyFileContentNotChanged < DependabotError; end
722
729
 
730
+ # Raised when the package manager refuses to continue because two sources
731
+ # (e.g. a lockfile and a registry) report different checksums for the same
732
+ # dependency version. It deliberately does not inherit from
733
+ # DependencyFileNotResolvable: callers that treat "not resolvable" as "no
734
+ # update available" must not hide this error.
735
+ class DependencyChecksumMismatch < DependabotError
736
+ extend T::Sig
737
+
738
+ # The dependency as the package manager names it, including any platform
739
+ # suffix (e.g. "nokogiri (1.16.0-x86_64-linux)"). Nil when it can't be
740
+ # determined.
741
+ sig { returns(T.nilable(String)) }
742
+ attr_reader :dependency_name
743
+
744
+ sig { params(message: String, dependency_name: T.nilable(String)).void }
745
+ def initialize(message, dependency_name: nil)
746
+ @dependency_name = dependency_name
747
+ super(message)
748
+ end
749
+ end
750
+
723
751
  class BadRequirementError < Gem::Requirement::BadRequirementError; end
724
752
 
725
753
  #######################
@@ -39,15 +39,18 @@ module Dependabot
39
39
  # rubocop:disable-next Performance/DeletePrefix
40
40
  relative_dir = Pathname.new(base_directory).sub(%r{\A/}, "").join(T.must(target_directory))
41
41
 
42
+ # Pass the path as a single argument and use NUL-separated output so
43
+ # paths containing whitespace are preserved. Keep stderr separate so
44
+ # warnings (e.g. a missing target directory) aren't parsed as entries
42
45
  status = T.let(
43
46
  SharedHelpers.run_shell_command(
44
- "git status --untracked-files all --porcelain v1 #{relative_dir}",
45
- fingerprint: "git status --untracked-files all --porcelain v1 <relative_dir>"
47
+ ["git", "status", "--untracked-files=all", "--porcelain=v1", "-z", "--", relative_dir.to_s],
48
+ fingerprint: "git status --untracked-files=all --porcelain=v1 -z -- <relative_dir>",
49
+ stderr_to_stdout: false
46
50
  ),
47
51
  String
48
52
  )
49
- changed_paths = status.split("\n").map(&:split)
50
- changed_paths.filter_map do |type, path|
53
+ parse_status(status).filter_map do |type, path|
51
54
  project_root = Pathname.new(File.expand_path(File.join(Dir.pwd, base_directory)))
52
55
  file_path = Pathname.new(path).expand_path.relative_path_from(project_root)
53
56
 
@@ -62,7 +65,7 @@ module Dependabot
62
65
  operation = Dependabot::DependencyFile::Operation::DELETE if type == "D"
63
66
  operation = Dependabot::DependencyFile::Operation::CREATE if type == "??"
64
67
 
65
- encoded_content, encoding = get_encoded_file_contents(T.must(path), operation)
68
+ encoded_content, encoding = get_encoded_file_contents(path, operation)
66
69
 
67
70
  create_dependency_file(
68
71
  name: file_path.to_s,
@@ -82,6 +85,26 @@ module Dependabot
82
85
  sig { returns(T.nilable(String)) }
83
86
  attr_reader :repo_contents_path
84
87
 
88
+ # Parses `git status --porcelain=v1 -z` output into [type, path] pairs.
89
+ # Each entry is "XY <path>"; renames and copies are followed by an extra
90
+ # entry holding the original path, which we skip.
91
+ sig { params(status: String).returns(T::Array[[String, String]]) }
92
+ def parse_status(status)
93
+ entries = status.split("\0")
94
+ changes = T.let([], T::Array[[String, String]])
95
+
96
+ until entries.empty?
97
+ entry = T.must(entries.shift)
98
+ next if entry.empty?
99
+
100
+ code = T.must(entry[0, 2])
101
+ changes << [code.strip, T.must(entry[3..])]
102
+ entries.shift if code.match?(/[RC]/)
103
+ end
104
+
105
+ changes
106
+ end
107
+
85
108
  sig { returns(T.nilable(String)) }
86
109
  attr_reader :target_directory
87
110
 
@@ -111,8 +134,11 @@ module Dependabot
111
134
  def binary_file?(path)
112
135
  return false unless File.exist?(path)
113
136
 
114
- command = SharedHelpers.escape_command("file -b --mime-encoding #{path}")
115
- encoding = `#{command}`.strip
137
+ encoding = SharedHelpers.run_shell_command(
138
+ ["file", "-b", "--mime-encoding", "--", path],
139
+ fingerprint: "file -b --mime-encoding -- <path>",
140
+ stderr_to_stdout: false
141
+ ).strip
116
142
 
117
143
  !TEXT_ENCODINGS.include?(encoding)
118
144
  end
@@ -13,7 +13,9 @@ module Dependabot
13
13
  extend T::Sig
14
14
 
15
15
  ObjectHash = T.type_alias { T::Hash[String, Object] }
16
+ CatalogEntries = T.type_alias { T::Hash[String, String] }
16
17
  DEPENDENCY_TYPES = %w(dependencies devDependencies optionalDependencies).freeze
18
+ CATALOG_FIELDS = %w(catalog catalogs).freeze
17
19
 
18
20
  sig { params(file: DependencyFile).returns(NpmPackageJson) }
19
21
  def self.from_file(file)
@@ -63,8 +65,62 @@ module Dependabot
63
65
  NpmPackageManagerConfig.from_package_json(@data)
64
66
  end
65
67
 
68
+ # The default catalog that `catalog:` requirements resolve against.
69
+ sig { returns(CatalogEntries) }
70
+ def catalog
71
+ prefix, fields = catalog_fields
72
+ value = fields["catalog"]
73
+ return {} unless value
74
+
75
+ catalog_entries(value, "#{prefix}catalog")
76
+ end
77
+
78
+ # Named catalogs that `catalog:<name>` requirements resolve against,
79
+ # keyed by catalog name. `default` extends the default catalog.
80
+ sig { returns(T::Hash[String, CatalogEntries]) }
81
+ def catalogs
82
+ prefix, fields = catalog_fields
83
+ value = fields["catalogs"]
84
+ return {} unless value
85
+
86
+ field = "#{prefix}catalogs"
87
+ result = T.let({}, T::Hash[String, CatalogEntries])
88
+ object_hash(value, field).each do |name, entries|
89
+ result[name] = catalog_entries(entries, "#{field}.#{name}")
90
+ end
91
+ result
92
+ end
93
+
94
+ # The key of the object Bun reads both catalog fields from, or nil for
95
+ # the root. Bun reads them from `workspaces` when it declares either of
96
+ # them, and ignores the root fields in that case.
97
+ sig { returns(T.nilable(String)) }
98
+ def catalog_container
99
+ workspaces = @data["workspaces"]
100
+ return unless workspaces.is_a?(Hash)
101
+
102
+ "workspaces" if CATALOG_FIELDS.any? { |field| object_hash(workspaces, "workspaces").key?(field) }
103
+ end
104
+
66
105
  private
67
106
 
107
+ sig { returns([String, ObjectHash]) }
108
+ def catalog_fields
109
+ container = catalog_container
110
+ return ["", @data] unless container
111
+
112
+ ["#{container}.", object_hash(@data[container], container)]
113
+ end
114
+
115
+ sig { params(value: Object, field: String).returns(CatalogEntries) }
116
+ def catalog_entries(value, field)
117
+ entries = T.let({}, CatalogEntries)
118
+ object_hash(value, field).each do |name, requirement|
119
+ entries[name] = requirement if requirement.is_a?(String)
120
+ end
121
+ entries
122
+ end
123
+
68
124
  sig { params(value: Object, field: String).returns(ObjectHash) }
69
125
  def object_hash(value, field)
70
126
  raise TypeError, "#{@path}: #{field} must be an object" unless value.is_a?(Hash)
data/lib/dependabot.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Dependabot
5
- VERSION = "0.400.0"
5
+ VERSION = "0.402.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.400.0
4
+ version: 0.402.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -635,7 +635,7 @@ licenses:
635
635
  - MIT
636
636
  metadata:
637
637
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
638
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.400.0
638
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.402.0
639
639
  rdoc_options: []
640
640
  require_paths:
641
641
  - lib