dependabot-javascript 0.296.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/lib/dependabot/bun.rb +49 -0
  3. data/lib/dependabot/javascript/bun/file_fetcher.rb +77 -0
  4. data/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +156 -0
  5. data/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +55 -0
  6. data/lib/dependabot/javascript/bun/file_parser.rb +74 -0
  7. data/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb +138 -0
  8. data/lib/dependabot/javascript/bun/file_updater.rb +75 -0
  9. data/lib/dependabot/javascript/bun/helpers.rb +72 -0
  10. data/lib/dependabot/javascript/bun/package_manager.rb +48 -0
  11. data/lib/dependabot/javascript/bun/requirement.rb +11 -0
  12. data/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +64 -0
  13. data/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb +47 -0
  14. data/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb +450 -0
  15. data/lib/dependabot/javascript/bun/update_checker/library_detector.rb +76 -0
  16. data/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb +203 -0
  17. data/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb +144 -0
  18. data/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +525 -0
  19. data/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb +165 -0
  20. data/lib/dependabot/javascript/bun/update_checker.rb +440 -0
  21. data/lib/dependabot/javascript/bun/version.rb +11 -0
  22. data/lib/dependabot/javascript/shared/constraint_helper.rb +359 -0
  23. data/lib/dependabot/javascript/shared/dependency_files_filterer.rb +164 -0
  24. data/lib/dependabot/javascript/shared/file_fetcher.rb +283 -0
  25. data/lib/dependabot/javascript/shared/file_parser/lockfile_parser.rb +106 -0
  26. data/lib/dependabot/javascript/shared/file_parser.rb +454 -0
  27. data/lib/dependabot/javascript/shared/file_updater/npmrc_builder.rb +394 -0
  28. data/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb +87 -0
  29. data/lib/dependabot/javascript/shared/file_updater/package_json_updater.rb +376 -0
  30. data/lib/dependabot/javascript/shared/file_updater.rb +179 -0
  31. data/lib/dependabot/javascript/shared/language.rb +45 -0
  32. data/lib/dependabot/javascript/shared/metadata_finder.rb +209 -0
  33. data/lib/dependabot/javascript/shared/native_helpers.rb +21 -0
  34. data/lib/dependabot/javascript/shared/package_manager_detector.rb +72 -0
  35. data/lib/dependabot/javascript/shared/package_name.rb +118 -0
  36. data/lib/dependabot/javascript/shared/registry_helper.rb +190 -0
  37. data/lib/dependabot/javascript/shared/registry_parser.rb +93 -0
  38. data/lib/dependabot/javascript/shared/requirement.rb +144 -0
  39. data/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb +79 -0
  40. data/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb +87 -0
  41. data/lib/dependabot/javascript/shared/update_checker/registry_finder.rb +358 -0
  42. data/lib/dependabot/javascript/shared/version.rb +133 -0
  43. data/lib/dependabot/javascript/shared/version_selector.rb +60 -0
  44. data/lib/dependabot/javascript.rb +39 -0
  45. metadata +327 -0
@@ -0,0 +1,76 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "excon"
5
+
6
+ module Dependabot
7
+ module Javascript
8
+ module Bun
9
+ class UpdateChecker
10
+ class LibraryDetector
11
+ def initialize(package_json_file:, credentials:, dependency_files:)
12
+ @package_json_file = package_json_file
13
+ @credentials = credentials
14
+ @dependency_files = dependency_files
15
+ end
16
+
17
+ def library?
18
+ return false unless package_json_may_be_for_library?
19
+
20
+ npm_response_matches_package_json?
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :package_json_file
26
+ attr_reader :credentials
27
+ attr_reader :dependency_files
28
+
29
+ def package_json_may_be_for_library?
30
+ return false unless project_name
31
+ return false if project_name.match?(/\{\{.*\}\}/)
32
+ return false unless parsed_package_json["version"]
33
+ return false if parsed_package_json["private"]
34
+
35
+ true
36
+ end
37
+
38
+ def npm_response_matches_package_json?
39
+ project_description = parsed_package_json["description"]
40
+ return false unless project_description
41
+
42
+ # Check if the project is listed on npm. If it is, it's a library
43
+ url = "#{registry.chomp('/')}/#{escaped_project_name}"
44
+ @project_npm_response ||= Dependabot::RegistryClient.get(url: url)
45
+ return false unless @project_npm_response.status == 200
46
+
47
+ @project_npm_response.body.dup.force_encoding("UTF-8").encode
48
+ .include?(project_description)
49
+ rescue Excon::Error::Socket, Excon::Error::Timeout, URI::InvalidURIError
50
+ false
51
+ end
52
+
53
+ def project_name
54
+ parsed_package_json.fetch("name", nil)
55
+ end
56
+
57
+ def escaped_project_name
58
+ project_name&.gsub("/", "%2F")
59
+ end
60
+
61
+ def parsed_package_json
62
+ @parsed_package_json ||= JSON.parse(package_json_file.content)
63
+ end
64
+
65
+ def registry
66
+ Dependabot::Javascript::Shared::UpdateChecker::RegistryFinder.new(
67
+ dependency: nil,
68
+ credentials: credentials,
69
+ rc_file: dependency_files.find { |f| f.name.end_with?(".npmrc") }
70
+ ).registry_from_rc(project_name)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,203 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ ################################################################################
5
+ # For more details on npm version constraints, see: #
6
+ # https://docs.npmjs.com/misc/semver #
7
+ ################################################################################
8
+
9
+ module Dependabot
10
+ module Javascript
11
+ module Bun
12
+ class UpdateChecker
13
+ class RequirementsUpdater
14
+ VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)*/
15
+ SEPARATOR = /(?<=[a-zA-Z0-9*])[\s|]+(?![\s|-])/
16
+ ALLOWED_UPDATE_STRATEGIES = T.let(
17
+ [
18
+ RequirementsUpdateStrategy::LockfileOnly,
19
+ RequirementsUpdateStrategy::WidenRanges,
20
+ RequirementsUpdateStrategy::BumpVersions,
21
+ RequirementsUpdateStrategy::BumpVersionsIfNecessary
22
+ ].freeze,
23
+ T::Array[Dependabot::RequirementsUpdateStrategy]
24
+ )
25
+
26
+ def initialize(requirements:, updated_source:, update_strategy:,
27
+ latest_resolvable_version:)
28
+ @requirements = requirements
29
+ @updated_source = updated_source
30
+ @update_strategy = update_strategy
31
+
32
+ check_update_strategy
33
+
34
+ return unless latest_resolvable_version
35
+
36
+ @latest_resolvable_version =
37
+ version_class.new(latest_resolvable_version)
38
+ end
39
+
40
+ def updated_requirements
41
+ return requirements if update_strategy.lockfile_only?
42
+
43
+ requirements.map do |req|
44
+ req = req.merge(source: updated_source)
45
+ next req unless latest_resolvable_version
46
+ next initial_req_after_source_change(req) unless req[:requirement]
47
+ next req if req[:requirement].match?(/^([A-Za-uw-z]|v[^\d])/)
48
+
49
+ case update_strategy
50
+ when RequirementsUpdateStrategy::WidenRanges then widen_requirement(req)
51
+ when RequirementsUpdateStrategy::BumpVersions then update_version_requirement(req)
52
+ when RequirementsUpdateStrategy::BumpVersionsIfNecessary
53
+ update_version_requirement_if_needed(req)
54
+ else raise "Unexpected update strategy: #{update_strategy}"
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :requirements
62
+ attr_reader :updated_source
63
+ attr_reader :update_strategy
64
+ attr_reader :latest_resolvable_version
65
+
66
+ def check_update_strategy
67
+ return if ALLOWED_UPDATE_STRATEGIES.include?(update_strategy)
68
+
69
+ raise "Unknown update strategy: #{update_strategy}"
70
+ end
71
+
72
+ def updating_from_git_to_npm?
73
+ return false unless updated_source.nil?
74
+
75
+ original_source = requirements.filter_map { |r| r[:source] }.first
76
+ original_source&.fetch(:type) == "git"
77
+ end
78
+
79
+ def initial_req_after_source_change(req)
80
+ return req unless updating_from_git_to_npm?
81
+ return req unless req[:requirement].nil?
82
+
83
+ req.merge(requirement: "^#{latest_resolvable_version}")
84
+ end
85
+
86
+ def update_version_requirement(req)
87
+ current_requirement = req[:requirement]
88
+
89
+ if current_requirement.match?(/(<|-\s)/i)
90
+ ruby_req = ruby_requirements(current_requirement).first
91
+ return req if ruby_req.satisfied_by?(latest_resolvable_version)
92
+
93
+ updated_req = update_range_requirement(current_requirement)
94
+ return req.merge(requirement: updated_req)
95
+ end
96
+
97
+ reqs = current_requirement.strip.split(SEPARATOR).map(&:strip)
98
+ req.merge(requirement: update_version_string(reqs.first))
99
+ end
100
+
101
+ def update_version_requirement_if_needed(req)
102
+ current_requirement = req[:requirement]
103
+ version = latest_resolvable_version
104
+ return req if current_requirement.strip == ""
105
+
106
+ ruby_reqs = ruby_requirements(current_requirement)
107
+ return req if ruby_reqs.any? { |r| r.satisfied_by?(version) }
108
+
109
+ update_version_requirement(req)
110
+ end
111
+
112
+ def widen_requirement(req)
113
+ current_requirement = req[:requirement]
114
+ version = latest_resolvable_version
115
+ return req if current_requirement.strip == ""
116
+
117
+ ruby_reqs = ruby_requirements(current_requirement)
118
+ return req if ruby_reqs.any? { |r| r.satisfied_by?(version) }
119
+
120
+ reqs = current_requirement.strip.split(SEPARATOR).map(&:strip)
121
+
122
+ updated_requirement =
123
+ if reqs.any? { |r| r.match?(/(<|-\s)/i) }
124
+ update_range_requirement(current_requirement)
125
+ elsif current_requirement.strip.split(SEPARATOR).count == 1
126
+ update_version_string(current_requirement)
127
+ else
128
+ current_requirement
129
+ end
130
+
131
+ req.merge(requirement: updated_requirement)
132
+ end
133
+
134
+ def ruby_requirements(requirement_string)
135
+ Dependabot::Javascript::Shared::Requirement
136
+ .requirements_array(requirement_string)
137
+ end
138
+
139
+ def update_range_requirement(req_string)
140
+ range_requirements =
141
+ req_string.split(SEPARATOR).select { |r| r.match?(/<|(\s+-\s+)/) }
142
+
143
+ if range_requirements.count == 1
144
+ range_requirement = range_requirements.first
145
+ versions = range_requirement.scan(VERSION_REGEX)
146
+ upper_bound = versions.map { |v| version_class.new(v) }.max
147
+ new_upper_bound = update_greatest_version(
148
+ upper_bound,
149
+ latest_resolvable_version
150
+ )
151
+
152
+ req_string.sub(
153
+ upper_bound.to_s,
154
+ new_upper_bound.to_s
155
+ )
156
+ else
157
+ req_string + " || ^#{latest_resolvable_version}"
158
+ end
159
+ end
160
+
161
+ def update_version_string(req_string)
162
+ req_string
163
+ .sub(VERSION_REGEX) do |old_version|
164
+ if old_version.match?(/\d-/) ||
165
+ latest_resolvable_version.to_s.match?(/\d-/)
166
+ latest_resolvable_version.to_s
167
+ else
168
+ old_parts = old_version.split(".")
169
+ new_parts = latest_resolvable_version.to_s.split(".")
170
+ .first(old_parts.count)
171
+ new_parts.map.with_index do |part, i|
172
+ old_parts[i].match?(/^x\b/) ? "x" : part
173
+ end.join(".")
174
+ end
175
+ end
176
+ end
177
+
178
+ def update_greatest_version(old_version, version_to_be_permitted)
179
+ version = version_class.new(old_version)
180
+ version = version.release if version.prerelease?
181
+
182
+ index_to_update =
183
+ version.segments.map.with_index { |seg, i| seg.zero? ? 0 : i }.max
184
+
185
+ version.segments.map.with_index do |_, index|
186
+ if index < index_to_update
187
+ version_to_be_permitted.segments[index]
188
+ elsif index == index_to_update
189
+ version_to_be_permitted.segments[index] + 1
190
+ else
191
+ 0
192
+ end
193
+ end.join(".")
194
+ end
195
+
196
+ def version_class
197
+ Dependabot::Javascript::Shared::Version
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,144 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class UpdateChecker
8
+ class SubdependencyVersionResolver
9
+ def initialize(dependency:, credentials:, dependency_files:,
10
+ ignored_versions:, latest_allowable_version:, repo_contents_path:)
11
+ @dependency = dependency
12
+ @credentials = credentials
13
+ @dependency_files = dependency_files
14
+ @ignored_versions = ignored_versions
15
+ @latest_allowable_version = latest_allowable_version
16
+ @repo_contents_path = repo_contents_path
17
+ end
18
+
19
+ def latest_resolvable_version
20
+ raise "Not a subdependency!" if dependency.requirements.any?
21
+ return if bundled_dependency?
22
+
23
+ base_dir = dependency_files.first.directory
24
+ SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
25
+ dependency_files_builder.write_temporary_dependency_files
26
+
27
+ updated_lockfiles = filtered_lockfiles.map do |lockfile|
28
+ updated_content = update_subdependency_in_lockfile(lockfile)
29
+ updated_lockfile = lockfile.dup
30
+ updated_lockfile.content = updated_content
31
+ updated_lockfile
32
+ end
33
+
34
+ version_from_updated_lockfiles(updated_lockfiles)
35
+ end
36
+ rescue SharedHelpers::HelperSubprocessFailed
37
+ # TODO: Move error handling logic from the FileUpdater to this class
38
+
39
+ # Return nil (no update possible) if an unknown error occurred
40
+ nil
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :dependency
46
+ attr_reader :credentials
47
+ attr_reader :dependency_files
48
+ attr_reader :ignored_versions
49
+ attr_reader :latest_allowable_version
50
+ attr_reader :repo_contents_path
51
+
52
+ def update_subdependency_in_lockfile(lockfile)
53
+ lockfile_name = Pathname.new(lockfile.name).basename.to_s
54
+ path = Pathname.new(lockfile.name).dirname.to_s
55
+
56
+ updated_files = run_bun_updater(path, lockfile_name) if lockfile.name.end_with?("bun.lock")
57
+
58
+ updated_files.fetch(lockfile_name)
59
+ end
60
+
61
+ def version_from_updated_lockfiles(updated_lockfiles)
62
+ updated_files = dependency_files -
63
+ dependency_files_builder.lockfiles +
64
+ updated_lockfiles
65
+
66
+ updated_version = NpmAndYarn::FileParser.new(
67
+ dependency_files: updated_files,
68
+ source: nil,
69
+ credentials: credentials
70
+ ).parse.find { |d| d.name == dependency.name }&.version
71
+ return unless updated_version
72
+
73
+ version_class.new(updated_version)
74
+ end
75
+
76
+ def run_bun_updater(path, lockfile_name)
77
+ SharedHelpers.with_git_configured(credentials: credentials) do
78
+ Dir.chdir(path) do
79
+ Helpers.run_bun_command(
80
+ "update #{dependency.name} --save-text-lockfile",
81
+ fingerprint: "update <dependency_name> --save-text-lockfile"
82
+ )
83
+ { lockfile_name => File.read(lockfile_name) }
84
+ end
85
+ end
86
+ end
87
+
88
+ def version_class
89
+ dependency.version_class
90
+ end
91
+
92
+ def updated_dependency
93
+ Dependabot::Dependency.new(
94
+ name: dependency.name,
95
+ version: latest_allowable_version,
96
+ previous_version: dependency.version,
97
+ requirements: [],
98
+ package_manager: dependency.package_manager
99
+ )
100
+ end
101
+
102
+ def filtered_lockfiles
103
+ @filtered_lockfiles ||=
104
+ Dependabot::Javascript::Shared::SubDependencyFilesFilterer.new(
105
+ dependency_files: dependency_files,
106
+ updated_dependencies: [updated_dependency]
107
+ ).files_requiring_update
108
+ end
109
+
110
+ def dependency_files_builder
111
+ @dependency_files_builder ||=
112
+ DependencyFilesBuilder.new(
113
+ dependency: dependency,
114
+ dependency_files: dependency_files,
115
+ credentials: credentials
116
+ )
117
+ end
118
+
119
+ # TODO: We should try and fix this by updating the parent that's not
120
+ # bundled. For this case: `chokidar > fsevents > node-pre-gyp > tar` we
121
+ # would need to update `fsevents`
122
+ #
123
+ # We shouldn't update bundled sub-dependencies as they have been bundled
124
+ # into the release at an exact version by a parent using
125
+ # `bundledDependencies`.
126
+ #
127
+ # For example, fsevents < 2 bundles node-pre-gyp meaning all it's
128
+ # sub-dependencies get bundled into the release tarball at publish time
129
+ # so you always get the same sub-dependency versions if you re-install a
130
+ # specific version of fsevents.
131
+ #
132
+ # Updating the sub-dependency by deleting the entry works but it gets
133
+ # removed from the bundled set of dependencies and moved top level
134
+ # resulting in a bunch of package duplication which is pretty confusing.
135
+ def bundled_dependency?
136
+ dependency.subdependency_metadata
137
+ &.any? { |h| h.fetch(:npm_bundled, false) } ||
138
+ false
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end