dependabot-go_modules 0.87.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/helpers/Makefile +9 -0
- data/helpers/build +26 -0
- data/helpers/go.mod +13 -0
- data/helpers/go.sum +6 -0
- data/helpers/importresolver/main.go +34 -0
- data/helpers/main.go +77 -0
- data/helpers/updatechecker/main.go +107 -0
- data/helpers/updater/go.mod +3 -0
- data/helpers/updater/go.sum +2 -0
- data/helpers/updater/helpers.go +57 -0
- data/helpers/updater/main.go +48 -0
- data/lib/dependabot/go_modules.rb +11 -0
- data/lib/dependabot/go_modules/file_fetcher.rb +66 -0
- data/lib/dependabot/go_modules/file_parser.rb +131 -0
- data/lib/dependabot/go_modules/file_updater.rb +73 -0
- data/lib/dependabot/go_modules/file_updater/go_mod_updater.rb +80 -0
- data/lib/dependabot/go_modules/metadata_finder.rb +58 -0
- data/lib/dependabot/go_modules/native_helpers.rb +20 -0
- data/lib/dependabot/go_modules/path_converter.rb +72 -0
- data/lib/dependabot/go_modules/requirement.rb +148 -0
- data/lib/dependabot/go_modules/update_checker.rb +114 -0
- data/lib/dependabot/go_modules/version.rb +43 -0
- metadata +191 -0
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
require "dependabot/dependency"
|
5
|
+
require "dependabot/file_parsers/base/dependency_set"
|
6
|
+
require "dependabot/go_modules/path_converter"
|
7
|
+
require "dependabot/errors"
|
8
|
+
require "dependabot/file_parsers"
|
9
|
+
require "dependabot/file_parsers/base"
|
10
|
+
|
11
|
+
module Dependabot
|
12
|
+
module GoModules
|
13
|
+
class FileParser < Dependabot::FileParsers::Base
|
14
|
+
GIT_VERSION_REGEX = /^v\d+\.\d+\.\d+-.*-(?<sha>[0-9a-f]{12})$/.freeze
|
15
|
+
|
16
|
+
def parse
|
17
|
+
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
18
|
+
|
19
|
+
i = 0
|
20
|
+
chunks = module_info(go_mod).lines.
|
21
|
+
group_by { |line| line == "{\n" ? i += 1 : i }
|
22
|
+
deps = chunks.values.map { |chunk| JSON.parse(chunk.join) }
|
23
|
+
|
24
|
+
deps.each do |dep|
|
25
|
+
# The project itself appears in this list as "Main"
|
26
|
+
next if dep["Main"]
|
27
|
+
|
28
|
+
dependency = dependency_from_details(dep)
|
29
|
+
dependency_set << dependency if dependency
|
30
|
+
end
|
31
|
+
|
32
|
+
dependency_set.dependencies
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def go_mod
|
38
|
+
@go_mod ||= get_original_file("go.mod")
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_required_files
|
42
|
+
raise "No go.mod!" unless go_mod
|
43
|
+
end
|
44
|
+
|
45
|
+
def dependency_from_details(details)
|
46
|
+
source =
|
47
|
+
if rev_identifier?(details) then git_source(details)
|
48
|
+
else { type: "default", source: details["Path"] }
|
49
|
+
end
|
50
|
+
|
51
|
+
version = details["Version"]&.sub(/^v?/, "")
|
52
|
+
|
53
|
+
reqs = [{
|
54
|
+
requirement: rev_identifier?(details) ? nil : details["Version"],
|
55
|
+
file: go_mod.name,
|
56
|
+
source: source,
|
57
|
+
groups: []
|
58
|
+
}]
|
59
|
+
|
60
|
+
Dependency.new(
|
61
|
+
name: details["Path"],
|
62
|
+
version: version,
|
63
|
+
requirements: details["Indirect"] ? [] : reqs,
|
64
|
+
package_manager: "dep"
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def module_info(go_mod)
|
69
|
+
@module_info ||=
|
70
|
+
SharedHelpers.in_a_temporary_directory do |path|
|
71
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
72
|
+
File.write("go.mod", go_mod.content)
|
73
|
+
|
74
|
+
command = "GO111MODULE=on go mod edit -print > /dev/null"
|
75
|
+
command += " && GO111MODULE=on go list -m -json all"
|
76
|
+
stdout, stderr, status = Open3.capture3(command)
|
77
|
+
handle_parser_error(path, stderr) unless status.success?
|
78
|
+
stdout
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_parser_error(path, stderr)
|
84
|
+
case stderr
|
85
|
+
when /go: .*: unknown revision/
|
86
|
+
line = stderr.lines.grep(/unknown revision/).first
|
87
|
+
raise Dependabot::DependencyFileNotResolvable, line.strip
|
88
|
+
when /go: .*: unrecognized import path/
|
89
|
+
line = stderr.lines.grep(/unrecognized import/).first
|
90
|
+
raise Dependabot::DependencyFileNotResolvable, line.strip
|
91
|
+
when /go: errors parsing go.mod/
|
92
|
+
msg = stderr.gsub(path.to_s, "").strip
|
93
|
+
raise Dependabot::DependencyFileNotParseable.new(go_mod.path, msg)
|
94
|
+
else
|
95
|
+
msg = stderr.gsub(path.to_s, "").strip
|
96
|
+
raise Dependabot::DependencyFileNotParseable.new(go_mod.path, msg)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def rev_identifier?(dep)
|
101
|
+
dep["Version"]&.match?(GIT_VERSION_REGEX)
|
102
|
+
end
|
103
|
+
|
104
|
+
def git_source(dep)
|
105
|
+
url = PathConverter.git_url_for_path(dep["Path"])
|
106
|
+
|
107
|
+
# Currently, we have no way of knowing whether the commit tagged
|
108
|
+
# is being used because a branch is being followed or because a
|
109
|
+
# particular ref is in use. We *assume* that a particular ref is in
|
110
|
+
# use (which means we'll only propose updates when its included in
|
111
|
+
# a release)
|
112
|
+
{
|
113
|
+
type: "git",
|
114
|
+
url: url || dep["Path"],
|
115
|
+
ref: git_revision(dep),
|
116
|
+
branch: nil
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
def git_revision(dep)
|
121
|
+
raw_version = dep.fetch("Version")
|
122
|
+
return raw_version unless raw_version.match?(GIT_VERSION_REGEX)
|
123
|
+
|
124
|
+
raw_version.match(GIT_VERSION_REGEX).named_captures.fetch("sha")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
Dependabot::FileParsers.
|
131
|
+
register("go_modules", Dependabot::GoModules::FileParser)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/shared_helpers"
|
4
|
+
require "dependabot/file_updaters"
|
5
|
+
require "dependabot/file_updaters/base"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module GoModules
|
9
|
+
class FileUpdater < Dependabot::FileUpdaters::Base
|
10
|
+
require_relative "file_updater/go_mod_updater"
|
11
|
+
|
12
|
+
def self.updated_files_regex
|
13
|
+
[
|
14
|
+
/^go\.mod$/,
|
15
|
+
/^go\.sum$/
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
def updated_dependency_files
|
20
|
+
updated_files = []
|
21
|
+
|
22
|
+
if go_mod && file_changed?(go_mod)
|
23
|
+
updated_files <<
|
24
|
+
updated_file(
|
25
|
+
file: go_mod,
|
26
|
+
content: file_updater.updated_go_mod_content
|
27
|
+
)
|
28
|
+
|
29
|
+
if go_sum && go_sum.content != file_updater.updated_go_sum_content
|
30
|
+
updated_files <<
|
31
|
+
updated_file(
|
32
|
+
file: go_sum,
|
33
|
+
content: file_updater.updated_go_sum_content
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
raise "No files changed!" if updated_files.none?
|
39
|
+
|
40
|
+
updated_files
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def check_required_files
|
46
|
+
return if go_mod
|
47
|
+
|
48
|
+
raise "No go.mod!"
|
49
|
+
end
|
50
|
+
|
51
|
+
def go_mod
|
52
|
+
@go_mod ||= get_original_file("go.mod")
|
53
|
+
end
|
54
|
+
|
55
|
+
def go_sum
|
56
|
+
@go_sum ||= get_original_file("go.sum")
|
57
|
+
end
|
58
|
+
|
59
|
+
def file_updater
|
60
|
+
@file_updater ||=
|
61
|
+
GoModUpdater.new(
|
62
|
+
dependencies: dependencies,
|
63
|
+
go_mod: go_mod,
|
64
|
+
go_sum: go_sum,
|
65
|
+
credentials: credentials
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Dependabot::FileUpdaters.
|
73
|
+
register("go_modules", Dependabot::GoModules::FileUpdater)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/utils/go/shared_helper"
|
4
|
+
require "dependabot/go_modules/file_updater"
|
5
|
+
require "dependabot/go_modules/native_helpers"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module GoModules
|
9
|
+
class FileUpdater
|
10
|
+
class GoModUpdater
|
11
|
+
def initialize(dependencies:, go_mod:, go_sum:, credentials:)
|
12
|
+
@dependencies = dependencies
|
13
|
+
@go_mod = go_mod
|
14
|
+
@go_sum = go_sum
|
15
|
+
@credentials = credentials
|
16
|
+
end
|
17
|
+
|
18
|
+
def updated_go_mod_content
|
19
|
+
@updated_go_mod_content ||=
|
20
|
+
SharedHelpers.in_a_temporary_directory do
|
21
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
22
|
+
File.write("go.mod", go_mod.content)
|
23
|
+
|
24
|
+
deps = dependencies.map do |dep|
|
25
|
+
{
|
26
|
+
name: dep.name,
|
27
|
+
version: "v" + dep.version.sub(/^v/i, ""),
|
28
|
+
indirect: dep.requirements.empty?
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
SharedHelpers.run_helper_subprocess(
|
33
|
+
command: "GO111MODULE=on #{NativeHelpers.helper_path}",
|
34
|
+
function: "updateDependencyFile",
|
35
|
+
args: { dependencies: deps }
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def updated_go_sum_content
|
42
|
+
return nil unless go_sum
|
43
|
+
|
44
|
+
# This needs to be run separately so we don't nest subprocess calls
|
45
|
+
updated_go_mod_content
|
46
|
+
|
47
|
+
@updated_go_sum_content ||=
|
48
|
+
SharedHelpers.in_a_temporary_directory do
|
49
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
50
|
+
File.write("go.mod", updated_go_mod_content)
|
51
|
+
File.write("go.sum", go_sum.content)
|
52
|
+
File.write("main.go", dummy_main_go)
|
53
|
+
|
54
|
+
`GO111MODULE=on go get -d`
|
55
|
+
unless $CHILD_STATUS.success?
|
56
|
+
raise Dependabot::DependencyFileNotParseable, go_sum.path
|
57
|
+
end
|
58
|
+
|
59
|
+
File.read("go.sum")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def dummy_main_go
|
67
|
+
lines = ["package main", "import ("]
|
68
|
+
dependencies.each do |dep|
|
69
|
+
lines << "_ \"#{dep.name}\""
|
70
|
+
end
|
71
|
+
lines << ")"
|
72
|
+
lines << "func main() {}"
|
73
|
+
lines.join("\n")
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :dependencies, :go_mod, :go_sum, :credentials
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/metadata_finders"
|
4
|
+
require "dependabot/metadata_finders/base"
|
5
|
+
require "dependabot/go_modules/path_converter"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module GoModules
|
9
|
+
class MetadataFinder < Dependabot::MetadataFinders::Base
|
10
|
+
private
|
11
|
+
|
12
|
+
def look_up_source
|
13
|
+
return look_up_git_dependency_source if git_dependency?
|
14
|
+
|
15
|
+
path_str = (specified_source_string || dependency.name)
|
16
|
+
url = Dependabot::GoModules::PathConverter.
|
17
|
+
git_url_for_path_without_go_helper(path_str)
|
18
|
+
Source.from_url(url) if url
|
19
|
+
end
|
20
|
+
|
21
|
+
def git_dependency?
|
22
|
+
return false unless declared_source_details
|
23
|
+
|
24
|
+
dependency_type =
|
25
|
+
declared_source_details.fetch(:type, nil) ||
|
26
|
+
declared_source_details.fetch("type")
|
27
|
+
|
28
|
+
dependency_type == "git"
|
29
|
+
end
|
30
|
+
|
31
|
+
def look_up_git_dependency_source
|
32
|
+
specified_url =
|
33
|
+
declared_source_details.fetch(:url, nil) ||
|
34
|
+
declared_source_details.fetch("url")
|
35
|
+
|
36
|
+
Source.from_url(specified_url)
|
37
|
+
end
|
38
|
+
|
39
|
+
def specified_source_string
|
40
|
+
declared_source_details&.fetch(:source, nil) ||
|
41
|
+
declared_source_details&.fetch("source", nil)
|
42
|
+
end
|
43
|
+
|
44
|
+
def declared_source_details
|
45
|
+
sources = dependency.requirements.
|
46
|
+
map { |r| r.fetch(:source) }.
|
47
|
+
uniq.compact
|
48
|
+
|
49
|
+
raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
|
50
|
+
|
51
|
+
sources.first
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
Dependabot::MetadataFinders.
|
58
|
+
register("go_modules", Dependabot::GoModules::MetadataFinder)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dependabot
|
4
|
+
module GoModules
|
5
|
+
module NativeHelpers
|
6
|
+
def self.helper_path
|
7
|
+
clean_path(File.join(native_helpers_root, "go_modules/bin/helper"))
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.native_helpers_root
|
11
|
+
default_path = File.join(__dir__, "../../../helpers/install-dir")
|
12
|
+
ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", default_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.clean_path(path)
|
16
|
+
Pathname.new(path).cleanpath.to_path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "excon"
|
4
|
+
require "nokogiri"
|
5
|
+
|
6
|
+
require "dependabot/shared_helpers"
|
7
|
+
require "dependabot/source"
|
8
|
+
require "dependabot/go_modules/native_helpers"
|
9
|
+
|
10
|
+
module Dependabot
|
11
|
+
module GoModules
|
12
|
+
module PathConverter
|
13
|
+
def self.git_url_for_path(path)
|
14
|
+
# Save a query by manually converting golang.org/x names
|
15
|
+
import_path = path.gsub(%r{^golang\.org/x}, "github.com/golang")
|
16
|
+
|
17
|
+
SharedHelpers.run_helper_subprocess(
|
18
|
+
command: NativeHelpers.helper_path,
|
19
|
+
function: "getVcsRemoteForImport",
|
20
|
+
args: { import: import_path }
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Used in dependabot-backend, which doesn't have access to any Go
|
25
|
+
# helpers.
|
26
|
+
# TODO: remove the need for this.
|
27
|
+
def self.git_url_for_path_without_go_helper(path)
|
28
|
+
# Save a query by manually converting golang.org/x names
|
29
|
+
tmp_path = path.gsub(%r{^golang\.org/x}, "github.com/golang")
|
30
|
+
|
31
|
+
# Currently, Dependabot::Source.new will return `nil` if it can't
|
32
|
+
# find a git SCH associated with a path. If it is ever extended to
|
33
|
+
# handle non-git sources we'll need to add an additional check here.
|
34
|
+
return Source.from_url(tmp_path).url if Source.from_url(tmp_path)
|
35
|
+
return "https://#{tmp_path}" if tmp_path.end_with?(".git")
|
36
|
+
return unless (metadata_response = fetch_path_metadata(path))
|
37
|
+
|
38
|
+
# Look for a GitHub, Bitbucket or GitLab URL in the response
|
39
|
+
metadata_response.scan(Dependabot::Source::SOURCE_REGEX) do
|
40
|
+
source_url = Regexp.last_match.to_s
|
41
|
+
return Source.from_url(source_url).url
|
42
|
+
end
|
43
|
+
|
44
|
+
# If none are found, parse the response and return the go-import path
|
45
|
+
doc = Nokogiri::XML(metadata_response)
|
46
|
+
doc.remove_namespaces!
|
47
|
+
import_details =
|
48
|
+
doc.xpath("//meta").
|
49
|
+
find { |n| n.attributes["name"]&.value == "go-import" }&.
|
50
|
+
attributes&.fetch("content")&.value&.split(/\s+/)
|
51
|
+
return unless import_details && import_details[1] == "git"
|
52
|
+
|
53
|
+
import_details[2]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.fetch_path_metadata(path)
|
57
|
+
# TODO: This is not robust! Instead, we should shell out to Go and
|
58
|
+
# use https://github.com/Masterminds/vcs.
|
59
|
+
response = Excon.get(
|
60
|
+
"https://#{path}?go-get=1",
|
61
|
+
idempotent: true,
|
62
|
+
**SharedHelpers.excon_defaults
|
63
|
+
)
|
64
|
+
|
65
|
+
return unless response.status == 200
|
66
|
+
|
67
|
+
response.body
|
68
|
+
end
|
69
|
+
private_class_method :fetch_path_metadata
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
################################################################################
|
4
|
+
# For more details on Go version constraints, see: #
|
5
|
+
# - https://github.com/Masterminds/semver #
|
6
|
+
# - https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md #
|
7
|
+
################################################################################
|
8
|
+
|
9
|
+
require "dependabot/go_modules/version"
|
10
|
+
|
11
|
+
module Dependabot
|
12
|
+
module GoModules
|
13
|
+
class Requirement < Gem::Requirement
|
14
|
+
WILDCARD_REGEX = /(?:\.|^)[xX*]/.freeze
|
15
|
+
OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|{2}/.freeze
|
16
|
+
|
17
|
+
# Override the version pattern to allow a 'v' prefix
|
18
|
+
quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|")
|
19
|
+
version_pattern = "v?#{Version::VERSION_PATTERN}"
|
20
|
+
|
21
|
+
PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*"
|
22
|
+
PATTERN = /\A#{PATTERN_RAW}\z/.freeze
|
23
|
+
|
24
|
+
# Use GoModules::Version rather than Gem::Version to ensure that
|
25
|
+
# pre-release versions aren't transformed.
|
26
|
+
def self.parse(obj)
|
27
|
+
return ["=", Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
28
|
+
|
29
|
+
unless (matches = PATTERN.match(obj.to_s))
|
30
|
+
msg = "Illformed requirement [#{obj.inspect}]"
|
31
|
+
raise BadRequirementError, msg
|
32
|
+
end
|
33
|
+
|
34
|
+
return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
|
35
|
+
|
36
|
+
[matches[1] || "=", Version.new(matches[2])]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns an array of requirements. At least one requirement from the
|
40
|
+
# returned array must be satisfied for a version to be valid.
|
41
|
+
def self.requirements_array(requirement_string)
|
42
|
+
return [new(nil)] if requirement_string.nil?
|
43
|
+
|
44
|
+
requirement_string.strip.split(OR_SEPARATOR).map do |req_string|
|
45
|
+
new(req_string)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(*requirements)
|
50
|
+
requirements = requirements.flatten.flat_map do |req_string|
|
51
|
+
req_string.split(",").map do |r|
|
52
|
+
convert_go_constraint_to_ruby_constraint(r.strip)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
super(requirements)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def convert_go_constraint_to_ruby_constraint(req_string)
|
62
|
+
req_string = req_string
|
63
|
+
req_string = convert_wildcard_characters(req_string)
|
64
|
+
|
65
|
+
if req_string.match?(WILDCARD_REGEX)
|
66
|
+
ruby_range(req_string.gsub(WILDCARD_REGEX, "").gsub(/^[^\d]/, ""))
|
67
|
+
elsif req_string.match?(/^~[^>]/) then convert_tilde_req(req_string)
|
68
|
+
elsif req_string.include?(" - ") then convert_hyphen_req(req_string)
|
69
|
+
elsif req_string.match?(/^[\dv^]/) then convert_caret_req(req_string)
|
70
|
+
elsif req_string.match?(/[<=>]/) then req_string
|
71
|
+
else ruby_range(req_string)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def convert_wildcard_characters(req_string)
|
76
|
+
if req_string.match?(/^[\dv^>~]/)
|
77
|
+
replace_wildcard_in_lower_bound(req_string)
|
78
|
+
elsif req_string.start_with?("<")
|
79
|
+
parts = req_string.split(".")
|
80
|
+
parts.map.with_index do |part, index|
|
81
|
+
next "0" if part.match?(WILDCARD_REGEX)
|
82
|
+
next part.to_i + 1 if parts[index + 1]&.match?(WILDCARD_REGEX)
|
83
|
+
|
84
|
+
part
|
85
|
+
end.join(".")
|
86
|
+
else
|
87
|
+
req_string
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def replace_wildcard_in_lower_bound(req_string)
|
92
|
+
after_wildcard = false
|
93
|
+
|
94
|
+
if req_string.start_with?("~")
|
95
|
+
req_string = req_string.gsub(/(?:(?:\.|^)[xX*])(\.[xX*])+/, "")
|
96
|
+
end
|
97
|
+
|
98
|
+
req_string.split(".").
|
99
|
+
map do |part|
|
100
|
+
part.split("-").map.with_index do |p, i|
|
101
|
+
# Before we hit a wildcard we just return the existing part
|
102
|
+
next p unless p.match?(WILDCARD_REGEX) || after_wildcard
|
103
|
+
|
104
|
+
# On or after a wildcard we replace the version part with zero
|
105
|
+
after_wildcard = true
|
106
|
+
i.zero? ? "0" : "a"
|
107
|
+
end.join("-")
|
108
|
+
end.join(".")
|
109
|
+
end
|
110
|
+
|
111
|
+
def convert_tilde_req(req_string)
|
112
|
+
version = req_string.gsub(/^~/, "")
|
113
|
+
parts = version.split(".")
|
114
|
+
parts << "0" if parts.count < 3
|
115
|
+
"~> #{parts.join('.')}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def convert_hyphen_req(req_string)
|
119
|
+
lower_bound, upper_bound = req_string.split(/\s+-\s+/)
|
120
|
+
[">= #{lower_bound}", "<= #{upper_bound}"]
|
121
|
+
end
|
122
|
+
|
123
|
+
def ruby_range(req_string)
|
124
|
+
parts = req_string.split(".")
|
125
|
+
|
126
|
+
# If we have three or more parts then this is an exact match
|
127
|
+
return req_string if parts.count >= 3
|
128
|
+
|
129
|
+
# If we have no parts then the version is completely unlocked
|
130
|
+
return ">= 0" if parts.count.zero?
|
131
|
+
|
132
|
+
# If we have fewer than three parts we do a partial match
|
133
|
+
parts << "0"
|
134
|
+
"~> #{parts.join('.')}"
|
135
|
+
end
|
136
|
+
|
137
|
+
# Note: Dep's caret notation implementation doesn't distinguish between
|
138
|
+
# pre and post-1.0.0 requirements (unlike in JS)
|
139
|
+
def convert_caret_req(req_string)
|
140
|
+
version = req_string.gsub(/^\^?v?/, "")
|
141
|
+
parts = version.split(".")
|
142
|
+
upper_bound = [parts.first.to_i + 1, 0, 0, "a"].map(&:to_s).join(".")
|
143
|
+
|
144
|
+
[">= #{version}", "< #{upper_bound}"]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|