dependabot-bun 0.296.2 → 0.297.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/.eslintrc +11 -0
  3. data/helpers/README.md +29 -0
  4. data/helpers/build +26 -0
  5. data/helpers/jest.config.js +5 -0
  6. data/helpers/lib/npm/conflicting-dependency-parser.js +78 -0
  7. data/helpers/lib/npm/index.js +9 -0
  8. data/helpers/lib/npm/vulnerability-auditor.js +291 -0
  9. data/helpers/lib/npm6/helpers.js +25 -0
  10. data/helpers/lib/npm6/index.js +9 -0
  11. data/helpers/lib/npm6/peer-dependency-checker.js +111 -0
  12. data/helpers/lib/npm6/remove-dependencies-from-lockfile.js +22 -0
  13. data/helpers/lib/npm6/subdependency-updater.js +78 -0
  14. data/helpers/lib/npm6/updater.js +199 -0
  15. data/helpers/lib/pnpm/index.js +5 -0
  16. data/helpers/lib/pnpm/lockfile-parser.js +82 -0
  17. data/helpers/lib/yarn/conflicting-dependency-parser.js +176 -0
  18. data/helpers/lib/yarn/fix-duplicates.js +80 -0
  19. data/helpers/lib/yarn/helpers.js +54 -0
  20. data/helpers/lib/yarn/index.js +14 -0
  21. data/helpers/lib/yarn/lockfile-parser.js +21 -0
  22. data/helpers/lib/yarn/peer-dependency-checker.js +132 -0
  23. data/helpers/lib/yarn/replace-lockfile-declaration.js +57 -0
  24. data/helpers/lib/yarn/subdependency-updater.js +83 -0
  25. data/helpers/lib/yarn/updater.js +209 -0
  26. data/helpers/package-lock.json +28519 -0
  27. data/helpers/package.json +29 -0
  28. data/helpers/patches/npm++pacote+9.5.12.patch +14 -0
  29. data/helpers/run.js +30 -0
  30. data/helpers/test/npm6/conflicting-dependency-parser.test.js +66 -0
  31. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package-lock.json +591 -0
  32. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
  33. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package-lock.json +188 -0
  34. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
  35. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package-lock.json +27 -0
  36. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
  37. data/helpers/test/npm6/fixtures/updater/original/package-lock.json +16 -0
  38. data/helpers/test/npm6/fixtures/updater/original/package.json +9 -0
  39. data/helpers/test/npm6/fixtures/updater/updated/package-lock.json +16 -0
  40. data/helpers/test/npm6/helpers.js +21 -0
  41. data/helpers/test/npm6/updater.test.js +30 -0
  42. data/helpers/test/pnpm/fixtures/parser/empty_version/pnpm-lock.yaml +72 -0
  43. data/helpers/test/pnpm/fixtures/parser/no_lockfile_change/pnpm-lock.yaml +2744 -0
  44. data/helpers/test/pnpm/fixtures/parser/only_dev_dependencies/pnpm-lock.yaml +16 -0
  45. data/helpers/test/pnpm/fixtures/parser/peer_disambiguation/pnpm-lock.yaml +855 -0
  46. data/helpers/test/pnpm/lockfile-parser.test.js +62 -0
  47. data/helpers/test/yarn/conflicting-dependency-parser.test.js +83 -0
  48. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
  49. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/yarn.lock +496 -0
  50. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/package.json +14 -0
  51. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/yarn.lock +21 -0
  52. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
  53. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/yarn.lock +183 -0
  54. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
  55. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/yarn.lock +21 -0
  56. data/helpers/test/yarn/fixtures/updater/illegal_character/package.json +8 -0
  57. data/helpers/test/yarn/fixtures/updater/illegal_character/yarn.lock +14 -0
  58. data/helpers/test/yarn/fixtures/updater/original/package.json +6 -0
  59. data/helpers/test/yarn/fixtures/updater/original/yarn.lock +11 -0
  60. data/helpers/test/yarn/fixtures/updater/updated/yarn.lock +12 -0
  61. data/helpers/test/yarn/fixtures/updater/with-version-comments/package.json +5 -0
  62. data/helpers/test/yarn/fixtures/updater/with-version-comments/yarn.lock +13 -0
  63. data/helpers/test/yarn/helpers.js +18 -0
  64. data/helpers/test/yarn/updater.test.js +117 -0
  65. data/lib/dependabot/bun/bun_package_manager.rb +47 -0
  66. data/lib/dependabot/bun/constraint_helper.rb +359 -0
  67. data/lib/dependabot/bun/dependency_files_filterer.rb +157 -0
  68. data/lib/dependabot/bun/file_fetcher/path_dependency_builder.rb +184 -0
  69. data/lib/dependabot/bun/file_fetcher.rb +402 -0
  70. data/lib/dependabot/bun/file_parser/bun_lock.rb +140 -0
  71. data/lib/dependabot/bun/file_parser/lockfile_parser.rb +105 -0
  72. data/lib/dependabot/bun/file_parser.rb +477 -0
  73. data/lib/dependabot/bun/file_updater/bun_lockfile_updater.rb +144 -0
  74. data/lib/dependabot/bun/file_updater/npmrc_builder.rb +256 -0
  75. data/lib/dependabot/bun/file_updater/package_json_preparer.rb +88 -0
  76. data/lib/dependabot/bun/file_updater/package_json_updater.rb +378 -0
  77. data/lib/dependabot/bun/file_updater.rb +203 -0
  78. data/lib/dependabot/bun/helpers.rb +93 -0
  79. data/lib/dependabot/bun/language.rb +45 -0
  80. data/lib/dependabot/bun/metadata_finder.rb +214 -0
  81. data/lib/dependabot/bun/native_helpers.rb +19 -0
  82. data/lib/dependabot/bun/package_manager.rb +280 -0
  83. data/lib/dependabot/bun/package_name.rb +118 -0
  84. data/lib/dependabot/bun/pnpm_package_manager.rb +55 -0
  85. data/lib/dependabot/bun/registry_helper.rb +188 -0
  86. data/lib/dependabot/bun/registry_parser.rb +93 -0
  87. data/lib/dependabot/bun/requirement.rb +146 -0
  88. data/lib/dependabot/bun/sub_dependency_files_filterer.rb +82 -0
  89. data/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb +59 -0
  90. data/lib/dependabot/bun/update_checker/dependency_files_builder.rb +79 -0
  91. data/lib/dependabot/bun/update_checker/latest_version_finder.rb +448 -0
  92. data/lib/dependabot/bun/update_checker/library_detector.rb +76 -0
  93. data/lib/dependabot/bun/update_checker/registry_finder.rb +279 -0
  94. data/lib/dependabot/bun/update_checker/requirements_updater.rb +206 -0
  95. data/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb +154 -0
  96. data/lib/dependabot/bun/update_checker/version_resolver.rb +583 -0
  97. data/lib/dependabot/bun/update_checker/vulnerability_auditor.rb +164 -0
  98. data/lib/dependabot/bun/update_checker.rb +455 -0
  99. data/lib/dependabot/bun/version.rb +138 -0
  100. data/lib/dependabot/bun/version_selector.rb +61 -0
  101. data/lib/dependabot/bun.rb +337 -35
  102. metadata +108 -65
  103. data/lib/dependabot/javascript/bun/file_fetcher.rb +0 -77
  104. data/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +0 -156
  105. data/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +0 -55
  106. data/lib/dependabot/javascript/bun/file_parser.rb +0 -74
  107. data/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb +0 -138
  108. data/lib/dependabot/javascript/bun/file_updater.rb +0 -75
  109. data/lib/dependabot/javascript/bun/helpers.rb +0 -72
  110. data/lib/dependabot/javascript/bun/package_manager.rb +0 -48
  111. data/lib/dependabot/javascript/bun/requirement.rb +0 -11
  112. data/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +0 -64
  113. data/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb +0 -47
  114. data/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb +0 -450
  115. data/lib/dependabot/javascript/bun/update_checker/library_detector.rb +0 -76
  116. data/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb +0 -203
  117. data/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb +0 -144
  118. data/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +0 -525
  119. data/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb +0 -165
  120. data/lib/dependabot/javascript/bun/update_checker.rb +0 -440
  121. data/lib/dependabot/javascript/bun/version.rb +0 -11
  122. data/lib/dependabot/javascript/shared/constraint_helper.rb +0 -359
  123. data/lib/dependabot/javascript/shared/dependency_files_filterer.rb +0 -164
  124. data/lib/dependabot/javascript/shared/file_fetcher.rb +0 -283
  125. data/lib/dependabot/javascript/shared/file_parser/lockfile_parser.rb +0 -106
  126. data/lib/dependabot/javascript/shared/file_parser.rb +0 -454
  127. data/lib/dependabot/javascript/shared/file_updater/npmrc_builder.rb +0 -394
  128. data/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb +0 -87
  129. data/lib/dependabot/javascript/shared/file_updater/package_json_updater.rb +0 -376
  130. data/lib/dependabot/javascript/shared/file_updater.rb +0 -179
  131. data/lib/dependabot/javascript/shared/language.rb +0 -45
  132. data/lib/dependabot/javascript/shared/metadata_finder.rb +0 -209
  133. data/lib/dependabot/javascript/shared/native_helpers.rb +0 -21
  134. data/lib/dependabot/javascript/shared/package_manager_detector.rb +0 -72
  135. data/lib/dependabot/javascript/shared/package_name.rb +0 -118
  136. data/lib/dependabot/javascript/shared/registry_helper.rb +0 -190
  137. data/lib/dependabot/javascript/shared/registry_parser.rb +0 -93
  138. data/lib/dependabot/javascript/shared/requirement.rb +0 -144
  139. data/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb +0 -79
  140. data/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb +0 -87
  141. data/lib/dependabot/javascript/shared/update_checker/registry_finder.rb +0 -358
  142. data/lib/dependabot/javascript/shared/version.rb +0 -133
  143. data/lib/dependabot/javascript/shared/version_selector.rb +0 -60
  144. data/lib/dependabot/javascript.rb +0 -39
@@ -0,0 +1,45 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/bun/package_manager"
5
+
6
+ module Dependabot
7
+ module Bun
8
+ class Language < Ecosystem::VersionManager
9
+ extend T::Sig
10
+ NAME = "node"
11
+
12
+ SUPPORTED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
13
+
14
+ DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
15
+
16
+ sig do
17
+ params(
18
+ detected_version: T.nilable(String),
19
+ raw_version: T.nilable(String),
20
+ requirement: T.nilable(Dependabot::Bun::Requirement)
21
+ ).void
22
+ end
23
+ def initialize(detected_version: nil, raw_version: nil, requirement: nil)
24
+ super(
25
+ name: NAME,
26
+ detected_version: detected_version ? Version.new(detected_version) : nil,
27
+ version: raw_version ? Version.new(raw_version) : nil,
28
+ deprecated_versions: DEPRECATED_VERSIONS,
29
+ supported_versions: SUPPORTED_VERSIONS,
30
+ requirement: requirement
31
+ )
32
+ end
33
+
34
+ sig { override.returns(T::Boolean) }
35
+ def deprecated?
36
+ false
37
+ end
38
+
39
+ sig { override.returns(T::Boolean) }
40
+ def unsupported?
41
+ false
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,214 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "excon"
5
+ require "time"
6
+
7
+ require "dependabot/metadata_finders"
8
+ require "dependabot/metadata_finders/base"
9
+ require "dependabot/registry_client"
10
+ require "dependabot/bun/update_checker/registry_finder"
11
+ require "dependabot/bun/version"
12
+
13
+ module Dependabot
14
+ module Bun
15
+ class MetadataFinder < Dependabot::MetadataFinders::Base
16
+ def homepage_url
17
+ # Attempt to use version_listing first, as fetching the entire listing
18
+ # array can be slow (if it's large)
19
+ return latest_version_listing["homepage"] if latest_version_listing["homepage"]
20
+
21
+ listing = all_version_listings.find { |_, l| l["homepage"] }
22
+ listing&.last&.fetch("homepage", nil) || super
23
+ end
24
+
25
+ def maintainer_changes
26
+ return unless npm_releaser
27
+ return unless npm_listing.dig("time", dependency.version)
28
+ return if previous_releasers.include?(npm_releaser)
29
+
30
+ "This version was pushed to npm by " \
31
+ "[#{npm_releaser}](https://www.npmjs.com/~#{npm_releaser}), a new " \
32
+ "releaser for #{dependency.name} since your current version."
33
+ end
34
+
35
+ private
36
+
37
+ def look_up_source
38
+ return find_source_from_registry if new_source.nil?
39
+
40
+ source_type = new_source[:type] || new_source.fetch("type")
41
+
42
+ case source_type
43
+ when "git" then find_source_from_git_url
44
+ when "registry" then find_source_from_registry
45
+ else raise "Unexpected source type: #{source_type}"
46
+ end
47
+ end
48
+
49
+ def npm_releaser
50
+ all_version_listings
51
+ .find { |v, _| v == dependency.version }
52
+ &.last&.fetch("_npmUser", nil)&.fetch("name", nil)
53
+ end
54
+
55
+ def previous_releasers
56
+ times = npm_listing.fetch("time")
57
+
58
+ cutoff =
59
+ if dependency.previous_version && times[dependency.previous_version]
60
+ Time.parse(times[dependency.previous_version])
61
+ elsif times[dependency.version]
62
+ Time.parse(times[dependency.version]) - 1
63
+ end
64
+ return unless cutoff
65
+
66
+ all_version_listings
67
+ .reject { |v, _| Time.parse(times[v]) > cutoff }
68
+ .filter_map { |_, d| d.fetch("_npmUser", nil)&.fetch("name", nil) }
69
+ end
70
+
71
+ def find_source_from_registry
72
+ # Attempt to use version_listing first, as fetching the entire listing
73
+ # array can be slow (if it's large)
74
+ potential_sources =
75
+ [
76
+ get_source(latest_version_listing["repository"]),
77
+ get_source(latest_version_listing["homepage"]),
78
+ get_source(latest_version_listing["bugs"])
79
+ ].compact
80
+
81
+ return potential_sources.first if potential_sources.any?
82
+
83
+ potential_sources =
84
+ all_version_listings.flat_map do |_, listing|
85
+ [
86
+ get_source(listing["repository"]),
87
+ get_source(listing["homepage"]),
88
+ get_source(listing["bugs"])
89
+ ]
90
+ end.compact
91
+
92
+ potential_sources.first
93
+ end
94
+
95
+ def new_source
96
+ sources = dependency.requirements
97
+ .map { |r| r.fetch(:source) }.uniq.compact
98
+ .sort_by { |source| UpdateChecker::RegistryFinder.central_registry?(source[:url]) ? 1 : 0 }
99
+
100
+ sources.first
101
+ end
102
+
103
+ def get_source(details)
104
+ potential_url = get_url(details)
105
+ return unless potential_url
106
+
107
+ potential_source = Source.from_url(potential_url)
108
+ return unless potential_source
109
+
110
+ potential_source.directory = get_directory(details)
111
+ potential_source
112
+ end
113
+
114
+ def get_url(details)
115
+ url =
116
+ case details
117
+ when String then details
118
+ when Hash then details.fetch("url", nil)
119
+ end
120
+ return url unless url&.match?(%r{^[\w.-]+/[\w.-]+$})
121
+
122
+ "https://github.com/" + url
123
+ end
124
+
125
+ def get_directory(details)
126
+ # Only return a directory if it is explicitly specified
127
+ return unless details.is_a?(Hash)
128
+
129
+ details.fetch("directory", nil)
130
+ end
131
+
132
+ def find_source_from_git_url
133
+ url = new_source[:url] || new_source.fetch("url")
134
+ Source.from_url(url)
135
+ end
136
+
137
+ def latest_version_listing
138
+ return @latest_version_listing if defined?(@latest_version_listing)
139
+
140
+ response = Dependabot::RegistryClient.get(url: "#{dependency_url}/latest", headers: registry_auth_headers)
141
+ return @latest_version_listing = JSON.parse(response.body) if response.status == 200
142
+
143
+ @latest_version_listing = {}
144
+ rescue JSON::ParserError, Excon::Error::Timeout
145
+ @latest_version_listing = {}
146
+ end
147
+
148
+ def all_version_listings
149
+ return [] if npm_listing["versions"].nil?
150
+
151
+ npm_listing["versions"]
152
+ .reject { |_, details| details["deprecated"] }
153
+ .sort_by { |version, _| Bun::Version.new(version) }
154
+ .reverse
155
+ end
156
+
157
+ def npm_listing
158
+ return @npm_listing unless @npm_listing.nil?
159
+
160
+ response = Dependabot::RegistryClient.get(url: dependency_url, headers: registry_auth_headers)
161
+ return @npm_listing = {} if response.status >= 500
162
+
163
+ begin
164
+ @npm_listing = JSON.parse(response.body)
165
+ rescue JSON::ParserError
166
+ raise unless non_standard_registry?
167
+
168
+ @npm_listing = {}
169
+ end
170
+ rescue Excon::Error::Timeout
171
+ @npm_listing = {}
172
+ end
173
+
174
+ def dependency_url
175
+ registry_url =
176
+ if new_source.nil? then "https://registry.npmjs.org"
177
+ else
178
+ new_source.fetch(:url)
179
+ end
180
+
181
+ # NPM registries expect slashes to be escaped
182
+ escaped_dependency_name = dependency.name.gsub("/", "%2F")
183
+ "#{registry_url}/#{escaped_dependency_name}"
184
+ end
185
+
186
+ def registry_auth_headers
187
+ return {} unless auth_token
188
+
189
+ { "Authorization" => "Bearer #{auth_token}" }
190
+ end
191
+
192
+ def dependency_registry
193
+ if new_source.nil? then "registry.npmjs.org"
194
+ else
195
+ new_source.fetch(:url).gsub("https://", "").gsub("http://", "")
196
+ end
197
+ end
198
+
199
+ def auth_token
200
+ credentials
201
+ .select { |cred| cred["type"] == "npm_registry" }
202
+ .find { |cred| cred["registry"] == dependency_registry }
203
+ &.fetch("token", nil)
204
+ end
205
+
206
+ def non_standard_registry?
207
+ dependency_registry != "registry.npmjs.org"
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ Dependabot::MetadataFinders
214
+ .register("bun", Dependabot::Bun::MetadataFinder)
@@ -0,0 +1,19 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Bun
6
+ module NativeHelpers
7
+ def self.helper_path
8
+ "node #{File.join(native_helpers_root, 'run.js')}"
9
+ end
10
+
11
+ def self.native_helpers_root
12
+ helpers_root = ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", nil)
13
+ return File.join(helpers_root, "bun") unless helpers_root.nil?
14
+
15
+ File.join(__dir__, "../../../helpers")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,280 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/shared_helpers"
5
+ require "dependabot/ecosystem"
6
+ require "dependabot/bun/requirement"
7
+ require "dependabot/bun/version_selector"
8
+ require "dependabot/bun/registry_helper"
9
+ require "dependabot/bun/bun_package_manager"
10
+ require "dependabot/bun/language"
11
+ require "dependabot/bun/constraint_helper"
12
+
13
+ module Dependabot
14
+ module Bun
15
+ ECOSYSTEM = "bun"
16
+ MANIFEST_FILENAME = "package.json"
17
+ LERNA_JSON_FILENAME = "lerna.json"
18
+ PACKAGE_MANAGER_VERSION_REGEX = /
19
+ ^ # Start of string
20
+ (?<major>\d+) # Major version (required, numeric)
21
+ \. # Separator between major and minor versions
22
+ (?<minor>\d+) # Minor version (required, numeric)
23
+ \. # Separator between minor and patch versions
24
+ (?<patch>\d+) # Patch version (required, numeric)
25
+ ( # Start pre-release section
26
+ -(?<pre_release>[a-zA-Z0-9.]+) # Pre-release label (optional, alphanumeric or dot-separated)
27
+ )?
28
+ ( # Start build metadata section
29
+ \+(?<build>[a-zA-Z0-9.]+) # Build metadata (optional, alphanumeric or dot-separated)
30
+ )?
31
+ $ # End of string
32
+ /x # Extended mode for readability
33
+
34
+ VALID_REQUIREMENT_CONSTRAINT = /
35
+ ^ # Start of string
36
+ (?<operator>=|>|>=|<|<=|~>|\\^) # Allowed operators
37
+ \s* # Optional whitespace
38
+ (?<major>\d+) # Major version (required)
39
+ (\.(?<minor>\d+))? # Minor version (optional)
40
+ (\.(?<patch>\d+))? # Patch version (optional)
41
+ ( # Start pre-release section
42
+ -(?<pre_release>[a-zA-Z0-9.]+) # Pre-release label (optional)
43
+ )?
44
+ ( # Start build metadata section
45
+ \+(?<build>[a-zA-Z0-9.]+) # Build metadata (optional)
46
+ )?
47
+ $ # End of string
48
+ /x # Extended mode for readability
49
+
50
+ MANIFEST_PACKAGE_MANAGER_KEY = "packageManager"
51
+ MANIFEST_ENGINES_KEY = "engines"
52
+
53
+ # Error malformed version number string
54
+ ERROR_MALFORMED_VERSION_NUMBER = "Malformed version number"
55
+
56
+ class PackageManagerHelper
57
+ extend T::Sig
58
+ extend T::Helpers
59
+
60
+ sig do
61
+ params(
62
+ package_json: T.nilable(T::Hash[String, T.untyped]),
63
+ lockfiles: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)],
64
+ registry_config_files: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)],
65
+ credentials: T.nilable(T::Array[Dependabot::Credential])
66
+ ).void
67
+ end
68
+ def initialize(package_json, lockfiles, registry_config_files, credentials)
69
+ @package_json = package_json
70
+ @lockfiles = lockfiles
71
+ @registry_helper = T.let(
72
+ RegistryHelper.new(registry_config_files, credentials),
73
+ Dependabot::Bun::RegistryHelper
74
+ )
75
+
76
+ @manifest_package_manager = T.let(package_json&.fetch(MANIFEST_PACKAGE_MANAGER_KEY, nil), T.nilable(String))
77
+ @engines = T.let(package_json&.fetch(MANIFEST_ENGINES_KEY, nil), T.nilable(T::Hash[String, T.untyped]))
78
+
79
+ @installed_versions = T.let({}, T::Hash[String, String])
80
+ @registries = T.let({}, T::Hash[String, String])
81
+
82
+ @language = T.let(nil, T.nilable(Ecosystem::VersionManager))
83
+ @language_requirement = T.let(nil, T.nilable(Requirement))
84
+ end
85
+
86
+ sig { returns(Ecosystem::VersionManager) }
87
+ def package_manager
88
+ package_manager_by_name(ECOSYSTEM)
89
+ end
90
+
91
+ sig { returns(Ecosystem::VersionManager) }
92
+ def language
93
+ @language ||= Language.new(
94
+ raw_version: Helpers.node_version,
95
+ requirement: language_requirement
96
+ )
97
+ end
98
+
99
+ sig { returns(T.nilable(Requirement)) }
100
+ def language_requirement
101
+ @language_requirement ||= find_engine_constraints_as_requirement(Language::NAME)
102
+ end
103
+
104
+ # rubocop:disable Metrics/PerceivedComplexity
105
+ # rubocop:disable Metrics/AbcSize
106
+ sig { params(name: String).returns(T.nilable(Requirement)) }
107
+ def find_engine_constraints_as_requirement(name)
108
+ Dependabot.logger.info("Processing engine constraints for #{name}")
109
+
110
+ return nil unless @engines.is_a?(Hash) && @engines[name]
111
+
112
+ raw_constraint = @engines[name].to_s.strip
113
+ return nil if raw_constraint.empty?
114
+
115
+ if Dependabot::Experiments.enabled?(:enable_engine_version_detection)
116
+ constraints = ConstraintHelper.extract_ruby_constraints(raw_constraint)
117
+ # When constraints are invalid we return constraints array nil
118
+ if constraints.nil?
119
+ Dependabot.logger.warn(
120
+ "Unrecognized constraint format for #{name}: #{raw_constraint}"
121
+ )
122
+ end
123
+ else
124
+ raw_constraints = raw_constraint.split
125
+ constraints = raw_constraints.map do |constraint|
126
+ case constraint
127
+ when /^\d+$/
128
+ ">=#{constraint}.0.0 <#{constraint.to_i + 1}.0.0"
129
+ when /^\d+\.\d+$/
130
+ ">=#{constraint} <#{constraint.split('.').first.to_i + 1}.0.0"
131
+ when /^\d+\.\d+\.\d+$/
132
+ "=#{constraint}"
133
+ else
134
+ Dependabot.logger.warn("Unrecognized constraint format for #{name}: #{constraint}")
135
+ constraint
136
+ end
137
+ end
138
+
139
+ end
140
+
141
+ if constraints && !constraints.empty?
142
+ Dependabot.logger.info("Parsed constraints for #{name}: #{constraints.join(', ')}")
143
+ Requirement.new(constraints)
144
+ end
145
+ rescue StandardError => e
146
+ Dependabot.logger.error("Error processing constraints for #{name}: #{e.message}")
147
+ nil
148
+ end
149
+ # rubocop:enable Metrics/AbcSize
150
+ # rubocop:enable Metrics/PerceivedComplexity
151
+
152
+ # rubocop:disable Metrics/CyclomaticComplexity
153
+ # rubocop:disable Metrics/PerceivedComplexity
154
+ sig { params(name: String).returns(T.nilable(T.any(Integer, String))) }
155
+ def setup(name)
156
+ # we prioritize version mentioned in "packageManager" instead of "engines"
157
+ # i.e. if { engines : "pnpm" : "6" } and { packageManager: "pnpm@6.0.2" },
158
+ # we go for the specificity mentioned in packageManager (6.0.2)
159
+
160
+ unless @manifest_package_manager&.start_with?("#{name}@") ||
161
+ (@manifest_package_manager&.==name.to_s) ||
162
+ @manifest_package_manager.nil?
163
+ return
164
+ end
165
+
166
+ return package_manager.version.to_s if package_manager.deprecated? || package_manager.unsupported?
167
+
168
+ if @engines && @manifest_package_manager.nil?
169
+ # if "packageManager" doesn't exists in manifest file,
170
+ # we check if we can extract "engines" information
171
+ version = check_engine_version(name)
172
+
173
+ elsif @manifest_package_manager&.==name.to_s
174
+ # if "packageManager" is found but no version is specified (i.e. pnpm@1.2.3),
175
+ # we check if we can get "engines" info to override default version
176
+ version = check_engine_version(name) if @engines
177
+
178
+ elsif @manifest_package_manager&.start_with?("#{name}@")
179
+ # if "packageManager" info has version specification i.e. yarn@3.3.1
180
+ # we go with the version in "packageManager"
181
+ Dependabot.logger.info(
182
+ "Found \"#{MANIFEST_PACKAGE_MANAGER_KEY}\" : \"#{@manifest_package_manager}\". " \
183
+ "Skipped checking \"#{MANIFEST_ENGINES_KEY}\"."
184
+ )
185
+ end
186
+
187
+ version ||= requested_version(name)
188
+ version ||= guessed_version(name)
189
+
190
+ version
191
+ end
192
+ # rubocop:enable Metrics/CyclomaticComplexity
193
+ # rubocop:enable Metrics/PerceivedComplexity
194
+ sig { params(name: String).returns(T.nilable(String)) }
195
+ def detect_version(name)
196
+ # Prioritize version mentioned in "packageManager" instead of "engines"
197
+ if @manifest_package_manager&.start_with?("#{name}@")
198
+ detected_version = @manifest_package_manager.split("@").last.to_s
199
+ end
200
+
201
+ # If "packageManager" has no version specified, check if we can extract "engines" information
202
+ detected_version ||= check_engine_version(name) if detected_version.to_s.empty?
203
+
204
+ # If neither "packageManager" nor "engines" have versions, infer version from lockfileVersion
205
+ detected_version ||= guessed_version(name) if detected_version.to_s.empty?
206
+
207
+ # Strip and validate version format
208
+ detected_version_string = detected_version.to_s.strip
209
+
210
+ # Ensure detected_version is neither "0" nor invalid format
211
+ return if detected_version_string == "0" || !detected_version_string.match?(ConstraintHelper::VERSION_REGEX)
212
+
213
+ detected_version_string
214
+ end
215
+
216
+ sig { params(name: String).returns(Ecosystem::VersionManager) }
217
+ def package_manager_by_name(name)
218
+ detected_version = detect_version(name)
219
+
220
+ # if we have a detected version, we check if it is deprecated or unsupported
221
+ if detected_version
222
+ package_manager = BunPackageManager.new(
223
+ detected_version: detected_version.to_s
224
+ )
225
+ return package_manager if package_manager.deprecated? || package_manager.unsupported?
226
+ end
227
+
228
+ BunPackageManager.new(
229
+ detected_version: detected_version,
230
+ raw_version: installed_version
231
+ )
232
+ end
233
+
234
+ sig { returns(T.nilable(String)) }
235
+ def installed_version
236
+ Helpers.bun_version
237
+ end
238
+
239
+ private
240
+
241
+ sig { params(name: String).returns(T.nilable(String)) }
242
+ def requested_version(name)
243
+ return unless @manifest_package_manager
244
+
245
+ match = @manifest_package_manager.match(/^#{name}@(?<version>\d+.\d+.\d+)/)
246
+ return unless match
247
+
248
+ Dependabot.logger.info("Requested version #{match['version']}")
249
+ match["version"]
250
+ end
251
+
252
+ sig { params(name: String).returns(T.nilable(T.any(Integer, String))) }
253
+ def guessed_version(name)
254
+ lockfile = @lockfiles[name.to_sym]
255
+ return unless lockfile
256
+
257
+ version = Helpers.send(:"#{name}_version_numeric", lockfile)
258
+
259
+ Dependabot.logger.info("Guessed version info \"#{name}\" : \"#{version}\"")
260
+
261
+ version
262
+ end
263
+
264
+ sig { params(name: T.untyped).returns(T.nilable(String)) }
265
+ def check_engine_version(name)
266
+ return if @package_json.nil?
267
+
268
+ version_selector = VersionSelector.new
269
+
270
+ engine_versions = version_selector.setup(@package_json, name, BunPackageManager::SUPPORTED_VERSIONS)
271
+
272
+ return if engine_versions.empty?
273
+
274
+ version = engine_versions[name]
275
+ Dependabot.logger.info("Returned (#{MANIFEST_ENGINES_KEY}) info \"#{name}\" : \"#{version}\"")
276
+ version
277
+ end
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,118 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Dependabot
7
+ module Bun
8
+ class PackageName
9
+ extend T::Sig
10
+
11
+ # NPM package naming rules are defined by the following projects:
12
+ # - https://github.com/npm/npm-user-validate
13
+ # - https://github.com/npm/validate-npm-package-name
14
+ PACKAGE_NAME_REGEX = %r{
15
+ \A # beginning of string
16
+ (?=.{1,214}\z) # enforce length (1 - 214)
17
+ (@(?<scope> # capture 'scope' if present
18
+ [a-z0-9\-\_\.\!\~\*\'\(\)]+ # URL-safe characters in scope
19
+ )\/)? # scope must be followed by slash
20
+ (?<name> # capture package name
21
+ (?(<scope>) # if scope is present
22
+ [a-z0-9\-\_\.\!\~\*\'\(\)]+ # scoped names can start with any URL-safe character
23
+ | # if no scope
24
+ [a-z0-9\-\!\~\*\'\(\)] # non-scoped names cannot start with . or _
25
+ [a-z0-9\-\_\.\!\~\*\'\(\)]* # subsequent characters can be any URL-safe character
26
+ )
27
+ )
28
+ \z # end of string
29
+ }xi # multi-line/case-insensitive
30
+
31
+ TYPES_PACKAGE_NAME_REGEX = %r{
32
+ \A # beginning of string
33
+ @types\/ # starts with @types/
34
+ ((?<scope>.+)__)? # capture scope
35
+ (?<name>.+) # capture name
36
+ \z # end of string
37
+ }xi # multi-line/case-insensitive
38
+
39
+ class InvalidPackageName < StandardError; end
40
+
41
+ sig { params(string: String).void }
42
+ def initialize(string)
43
+ match = PACKAGE_NAME_REGEX.match(string.to_s)
44
+ raise InvalidPackageName unless match
45
+
46
+ @scope = T.let(match[:scope], T.nilable(String))
47
+ @name = T.let(match[:name], T.nilable(String))
48
+ end
49
+
50
+ sig { returns(String) }
51
+ def to_s
52
+ if scoped?
53
+ "@#{@scope}/#{@name}"
54
+ else
55
+ @name.to_s
56
+ end
57
+ end
58
+
59
+ sig { params(other: PackageName).returns(T::Boolean) }
60
+ def eql?(other)
61
+ self.class == other.class && to_s == other.to_s
62
+ end
63
+
64
+ sig { returns(Integer) }
65
+ def hash
66
+ to_s.downcase.hash
67
+ end
68
+
69
+ sig { params(other: PackageName).returns(T.nilable(Integer)) }
70
+ def <=>(other)
71
+ to_s.casecmp(other.to_s)
72
+ end
73
+
74
+ sig { returns(T.nilable(PackageName)) }
75
+ def library_name
76
+ return unless types_package?
77
+ return @library_name if defined?(@library_name)
78
+
79
+ lib_name =
80
+ begin
81
+ match = T.must(TYPES_PACKAGE_NAME_REGEX.match(to_s))
82
+ if match[:scope]
83
+ self.class.new("@#{match[:scope]}/#{match[:name]}")
84
+ else
85
+ self.class.new(match[:name].to_s)
86
+ end
87
+ end
88
+
89
+ @library_name ||= T.let(lib_name, T.nilable(PackageName))
90
+ end
91
+
92
+ sig { returns(T.nilable(PackageName)) }
93
+ def types_package_name
94
+ return if types_package?
95
+
96
+ @types_package_name ||= T.let(
97
+ if scoped?
98
+ self.class.new("@types/#{@scope}__#{@name}")
99
+ else
100
+ self.class.new("@types/#{@name}")
101
+ end, T.nilable(PackageName)
102
+ )
103
+ end
104
+
105
+ private
106
+
107
+ sig { returns(T::Boolean) }
108
+ def scoped?
109
+ !@scope.nil?
110
+ end
111
+
112
+ sig { returns(T.nilable(T::Boolean)) }
113
+ def types_package?
114
+ "types".casecmp?(@scope)
115
+ end
116
+ end
117
+ end
118
+ end