diffend 0.2.16 → 0.2.24
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.github/workflows/ci.yml +12 -2
- data/CHANGELOG.md +59 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +12 -1
- data/{LICENSE → LICENSE.md} +0 -0
- data/lib/diffend.rb +71 -20
- data/lib/diffend/build_bundler_definition.rb +26 -0
- data/lib/diffend/config/fetcher.rb +92 -9
- data/lib/diffend/config/validator.rb +25 -0
- data/lib/diffend/errors.rb +14 -4
- data/lib/diffend/handle_errors/build_exception_payload.rb +30 -0
- data/lib/diffend/handle_errors/display_to_stdout.rb +17 -0
- data/lib/diffend/handle_errors/messages.rb +29 -0
- data/lib/diffend/handle_errors/report.rb +59 -0
- data/lib/diffend/request.rb +165 -0
- data/lib/diffend/voting.rb +37 -8
- data/lib/diffend/voting/versions/local.rb +156 -52
- data/lib/diffend/voting/versions/remote.rb +46 -16
- data/scripts/generate_payload_for_file.rb +1 -1
- metadata +11 -4
- metadata.gz.sig +0 -0
- data/lib/diffend/voting/request.rb +0 -191
data/lib/diffend/errors.rb
CHANGED
|
@@ -7,9 +7,19 @@ module Diffend
|
|
|
7
7
|
BaseError = Class.new(StandardError)
|
|
8
8
|
# Raised when we couldn't find a valid configuration file
|
|
9
9
|
MissingConfigurationFile = Class.new(BaseError)
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
|
|
10
|
+
# Raised when configuration file is empty
|
|
11
|
+
EmptyConfigurationFile = Class.new(BaseError)
|
|
12
|
+
# Raised when configuration file is malformed
|
|
13
|
+
MalformedConfigurationFile = Class.new(BaseError)
|
|
14
|
+
# Raised when project_id is missing in configuration file
|
|
15
|
+
ProjectIdMissingInConfigurationFile = Class.new(BaseError)
|
|
16
|
+
# Raised when shareable_id is missing in configuration file
|
|
17
|
+
ShareableIdMissingInConfigurationFile = Class.new(BaseError)
|
|
18
|
+
# Raised when shareable_key is missing in configuration file
|
|
19
|
+
ShareableKeyMissingInConfigurationFile = Class.new(BaseError)
|
|
20
|
+
# Raised when build_path is missing in configuration file
|
|
21
|
+
BuildPathMissingInConfigurationFile = Class.new(BaseError)
|
|
22
|
+
# Raised when server-side error occurs
|
|
23
|
+
RequestServerError = Class.new(BaseError)
|
|
14
24
|
end
|
|
15
25
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Diffend
|
|
6
|
+
module HandleErrors
|
|
7
|
+
# Module responsible for building exception payload
|
|
8
|
+
module BuildExceptionPayload
|
|
9
|
+
class << self
|
|
10
|
+
# Build exception payload
|
|
11
|
+
#
|
|
12
|
+
# @param exception [Exception, NilClass] expection that was raised
|
|
13
|
+
# @param payload [Hash] with versions to check
|
|
14
|
+
#
|
|
15
|
+
# @return [Hash]
|
|
16
|
+
def call(exception, payload)
|
|
17
|
+
{
|
|
18
|
+
request_id: SecureRandom.uuid,
|
|
19
|
+
payload: payload,
|
|
20
|
+
exception: {
|
|
21
|
+
class: exception&.class,
|
|
22
|
+
message: exception&.message,
|
|
23
|
+
backtrace: exception&.backtrace
|
|
24
|
+
}
|
|
25
|
+
}.freeze
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Diffend
|
|
4
|
+
module HandleErrors
|
|
5
|
+
# Module responsible for displaying exception payload to stdout
|
|
6
|
+
module DisplayToStdout
|
|
7
|
+
class << self
|
|
8
|
+
# Display exception payload to stdout
|
|
9
|
+
#
|
|
10
|
+
# @param exception_payload [Hash]
|
|
11
|
+
def call(exception_payload)
|
|
12
|
+
puts exception_payload.to_json
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Diffend
|
|
4
|
+
module HandleErrors
|
|
5
|
+
module Messages
|
|
6
|
+
PAYLOAD_DUMP = '^^^ Above is the dump of your request ^^^'
|
|
7
|
+
UNHANDLED_EXCEPTION = <<~MSG
|
|
8
|
+
\nSomething went really wrong. We recorded this incident in our system and will review it.\n
|
|
9
|
+
This is a bug, don't hesitate.\n
|
|
10
|
+
Create an issue at https://github.com/diffend-io/diffend-ruby/issues\n
|
|
11
|
+
MSG
|
|
12
|
+
UNSUPPORTED_RESPONSE = <<~MSG
|
|
13
|
+
\nAPI returned an unsupported response. We recorded this incident in our system and will review it.\n
|
|
14
|
+
This is a bug, don't hesitate.\n
|
|
15
|
+
Create an issue at https://github.com/diffend-io/diffend-ruby/issues\n
|
|
16
|
+
MSG
|
|
17
|
+
UNSUPPORTED_VERDICT = <<~MSG
|
|
18
|
+
\nAPI returned an unsupported verdict. We recorded this incident in our system and will review it.\n
|
|
19
|
+
This is a bug, don't hesitate.\n
|
|
20
|
+
Create an issue at https://github.com/diffend-io/diffend-ruby/issues\n
|
|
21
|
+
MSG
|
|
22
|
+
REQUEST_ERROR = <<~MSG
|
|
23
|
+
\nWe were unable to process your request at this time. We recorded this incident in our system and will review it.\n
|
|
24
|
+
If you think that this is a bug, don't hesitate.\n
|
|
25
|
+
Create an issue at https://github.com/diffend-io/diffend-ruby/issues\n
|
|
26
|
+
MSG
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Diffend
|
|
4
|
+
module HandleErrors
|
|
5
|
+
# Module responsible for reporting errors to diffend
|
|
6
|
+
module Report
|
|
7
|
+
class << self
|
|
8
|
+
# Execute request to Diffend
|
|
9
|
+
#
|
|
10
|
+
# @param exception [Exception] expection that was raised
|
|
11
|
+
# @param payload [Hash] with versions to check
|
|
12
|
+
# @param config [OpenStruct] Diffend config
|
|
13
|
+
# @param message [Symbol] message that we want to display
|
|
14
|
+
# @param report [Boolean] if true we will report the issue to diffend
|
|
15
|
+
#
|
|
16
|
+
# @return [Net::HTTPResponse] response from Diffend
|
|
17
|
+
def call(config:, message:, exception: nil, payload: {}, report: false)
|
|
18
|
+
exception_payload = prepare_exception_payload(exception, payload)
|
|
19
|
+
|
|
20
|
+
Bundler.ui.error(Diffend::HandleErrors::Messages::PAYLOAD_DUMP)
|
|
21
|
+
Bundler.ui.error(Diffend::HandleErrors::Messages.const_get(message.to_s.upcase))
|
|
22
|
+
|
|
23
|
+
if report
|
|
24
|
+
Diffend::Request.call(
|
|
25
|
+
config,
|
|
26
|
+
errors_url(config.project_id),
|
|
27
|
+
exception_payload
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
exit 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Prepare exception payload and display it to stdout
|
|
35
|
+
#
|
|
36
|
+
# @param exception [Exception] expection that was raised
|
|
37
|
+
# @param payload [Hash] with versions to check
|
|
38
|
+
#
|
|
39
|
+
# @return [Hash]
|
|
40
|
+
def prepare_exception_payload(exception, payload)
|
|
41
|
+
Diffend::HandleErrors::BuildExceptionPayload
|
|
42
|
+
.call(exception, payload)
|
|
43
|
+
.tap(&Diffend::HandleErrors::DisplayToStdout.method(:call))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Provides diffend errors endpoint url
|
|
47
|
+
#
|
|
48
|
+
# @param project_id [String] diffend project_id
|
|
49
|
+
#
|
|
50
|
+
# @return [String] diffend endpoint
|
|
51
|
+
def errors_url(project_id)
|
|
52
|
+
return ENV['DIFFEND_ERROR_URL'] if ENV.key?('DIFFEND_ERROR_URL')
|
|
53
|
+
|
|
54
|
+
"https://my.diffend.io/api/projects/#{project_id}/errors"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module Diffend
|
|
8
|
+
# Module responsible for doing request to Diffend
|
|
9
|
+
module Request
|
|
10
|
+
# Message displayed when connection issue occured and we will retry
|
|
11
|
+
CONNECTION_MESSAGE = 'We experienced a connection issue, retrying...'
|
|
12
|
+
# List of connection exceptions
|
|
13
|
+
CONNECTION_EXCEPTIONS = [
|
|
14
|
+
Errno::ECONNRESET,
|
|
15
|
+
Errno::ENETUNREACH,
|
|
16
|
+
Errno::EHOSTUNREACH,
|
|
17
|
+
Errno::ECONNREFUSED
|
|
18
|
+
].freeze
|
|
19
|
+
# Message displayed when timeout occured and we will retry
|
|
20
|
+
TIMEOUT_MESSAGE = 'We experienced a connection issue, retrying...'
|
|
21
|
+
# List of timeout exceptions
|
|
22
|
+
TIMEOUT_EXCEPTIONS = [
|
|
23
|
+
Net::OpenTimeout,
|
|
24
|
+
Net::ReadTimeout
|
|
25
|
+
].freeze
|
|
26
|
+
# Message displayed when server issue occured and we will retry
|
|
27
|
+
SERVER_ERROR_MESSAGE = 'We experienced a server-side issue, retrying...'
|
|
28
|
+
# List of server issues
|
|
29
|
+
#
|
|
30
|
+
# 500 - Internal Server Error
|
|
31
|
+
# 502 - Bad Gateway
|
|
32
|
+
# 503 - Service Unavailable
|
|
33
|
+
# 504 - Gateway Timeout
|
|
34
|
+
SERVER_ERRORS = [500, 502, 503, 504].freeze
|
|
35
|
+
# Number of retries
|
|
36
|
+
RETRIES = 3
|
|
37
|
+
# Request headers
|
|
38
|
+
HEADERS = { 'Content-Type': 'application/json' }.freeze
|
|
39
|
+
|
|
40
|
+
private_constant :HEADERS
|
|
41
|
+
|
|
42
|
+
class << self
|
|
43
|
+
# Execute request
|
|
44
|
+
#
|
|
45
|
+
# @param config [OpenStruct] diffend config
|
|
46
|
+
# @param endpoint_url [String]
|
|
47
|
+
# @param payload [Hash]
|
|
48
|
+
#
|
|
49
|
+
# @return [Net::HTTPResponse] response from Diffend
|
|
50
|
+
def call(config, endpoint_url, payload)
|
|
51
|
+
retry_count ||= -1
|
|
52
|
+
|
|
53
|
+
build_http(endpoint_url) do |http, uri|
|
|
54
|
+
response = http.request(build_request(uri, config, payload))
|
|
55
|
+
|
|
56
|
+
if SERVER_ERRORS.include?(response.code.to_i)
|
|
57
|
+
raise Diffend::Errors::RequestServerError, response.code.to_i
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
response
|
|
61
|
+
end
|
|
62
|
+
rescue Diffend::Errors::RequestServerError => e
|
|
63
|
+
retry_count += 1
|
|
64
|
+
|
|
65
|
+
retry if handle_retry(SERVER_ERROR_MESSAGE, retry_count)
|
|
66
|
+
|
|
67
|
+
Diffend::HandleErrors::Report.call(
|
|
68
|
+
exception: e,
|
|
69
|
+
payload: payload,
|
|
70
|
+
config: config,
|
|
71
|
+
message: :request_error
|
|
72
|
+
)
|
|
73
|
+
rescue *CONNECTION_EXCEPTIONS => e
|
|
74
|
+
retry_count += 1
|
|
75
|
+
|
|
76
|
+
retry if handle_retry(CONNECTION_MESSAGE, retry_count)
|
|
77
|
+
|
|
78
|
+
Diffend::HandleErrors::Report.call(
|
|
79
|
+
exception: e,
|
|
80
|
+
payload: payload,
|
|
81
|
+
config: config,
|
|
82
|
+
message: :request_error
|
|
83
|
+
)
|
|
84
|
+
rescue *TIMEOUT_EXCEPTIONS => e
|
|
85
|
+
retry_count += 1
|
|
86
|
+
|
|
87
|
+
retry if handle_retry(TIMEOUT_MESSAGE, retry_count)
|
|
88
|
+
|
|
89
|
+
Diffend::HandleErrors::Report.call(
|
|
90
|
+
exception: e,
|
|
91
|
+
payload: payload,
|
|
92
|
+
config: config,
|
|
93
|
+
message: :request_error
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Handle retry
|
|
98
|
+
#
|
|
99
|
+
# @param message [String] message we want to display
|
|
100
|
+
# @param retry_count [Integer]
|
|
101
|
+
def handle_retry(message, retry_count)
|
|
102
|
+
return false if retry_count == RETRIES
|
|
103
|
+
|
|
104
|
+
Bundler.ui.error(message)
|
|
105
|
+
sleep(exponential_backoff(retry_count))
|
|
106
|
+
|
|
107
|
+
retry_count < RETRIES
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Builds http connection object
|
|
111
|
+
#
|
|
112
|
+
# @param url [String] command endpoint url
|
|
113
|
+
def build_http(url)
|
|
114
|
+
uri = URI(url)
|
|
115
|
+
|
|
116
|
+
Net::HTTP.start(
|
|
117
|
+
uri.host,
|
|
118
|
+
uri.port,
|
|
119
|
+
use_ssl: uri.scheme == 'https',
|
|
120
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE,
|
|
121
|
+
open_timeout: 5,
|
|
122
|
+
read_timeout: 5
|
|
123
|
+
) { |http| yield(http, uri) }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Build http post request and assigns headers and payload
|
|
127
|
+
#
|
|
128
|
+
# @param uri [URI::HTTPS]
|
|
129
|
+
# @param config [OpenStruct] Diffend config
|
|
130
|
+
# @param payload [Hash] with versions to check
|
|
131
|
+
#
|
|
132
|
+
# @return [Net::HTTP::Post]
|
|
133
|
+
def build_request(uri, config, payload)
|
|
134
|
+
Net::HTTP::Post
|
|
135
|
+
.new(uri.request_uri, HEADERS)
|
|
136
|
+
.tap { |request| assign_auth(request, config) }
|
|
137
|
+
.tap { |request| assign_payload(request, payload) }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Assigns basic authorization if provided in the config
|
|
141
|
+
#
|
|
142
|
+
# @param request [Net::HTTP::Post] prepared http post
|
|
143
|
+
# @param config [OpenStruct] Diffend config
|
|
144
|
+
def assign_auth(request, config)
|
|
145
|
+
return unless config
|
|
146
|
+
return unless config.shareable_id
|
|
147
|
+
return unless config.shareable_key
|
|
148
|
+
|
|
149
|
+
request.basic_auth(config.shareable_id, config.shareable_key)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Assigns payload as json
|
|
153
|
+
#
|
|
154
|
+
# @param request [Net::HTTP::Post] prepared http post
|
|
155
|
+
# @param payload [Hash] with versions to check
|
|
156
|
+
def assign_payload(request, payload)
|
|
157
|
+
request.body = JSON.dump(payload: payload)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def exponential_backoff(retry_count)
|
|
161
|
+
2**(retry_count + 1)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
data/lib/diffend/voting.rb
CHANGED
|
@@ -7,23 +7,33 @@ module Diffend
|
|
|
7
7
|
# Build verdict
|
|
8
8
|
#
|
|
9
9
|
# @param command [String] either install or update
|
|
10
|
+
# @param config [OpenStruct] diffend config
|
|
10
11
|
# @param definition [Bundler::Definition] definition for your source
|
|
11
|
-
def call(command, definition)
|
|
12
|
+
def call(command, config, definition)
|
|
12
13
|
Versions::Remote
|
|
13
|
-
.call(command, definition)
|
|
14
|
-
.tap { |response| build_message(command, response) }
|
|
14
|
+
.call(command, config, definition)
|
|
15
|
+
.tap { |response| build_message(command, config, response) }
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
# @param command [String] either install or update
|
|
19
|
+
# @param config [OpenStruct] diffend config
|
|
20
|
+
# @param response [Hash] response from diffend API
|
|
21
|
+
def build_message(command, config, response)
|
|
18
22
|
if response.key?('error')
|
|
19
23
|
build_error(response)
|
|
20
24
|
elsif response.key?('action')
|
|
21
|
-
build_verdict(command, response)
|
|
25
|
+
build_verdict(command, config, response)
|
|
22
26
|
else
|
|
23
|
-
|
|
27
|
+
Diffend::HandleErrors::Report.call(
|
|
28
|
+
config: config,
|
|
29
|
+
message: :unsupported_response,
|
|
30
|
+
payload: response,
|
|
31
|
+
report: true
|
|
32
|
+
)
|
|
24
33
|
end
|
|
25
34
|
end
|
|
26
35
|
|
|
36
|
+
# @param response [Hash] response from diffend API
|
|
27
37
|
def build_error(response)
|
|
28
38
|
build_error_message(response)
|
|
29
39
|
.tap(&Bundler.ui.method(:error))
|
|
@@ -31,7 +41,10 @@ module Diffend
|
|
|
31
41
|
exit 1
|
|
32
42
|
end
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
# @param command [String] either install or update
|
|
45
|
+
# @param config [OpenStruct] diffend config
|
|
46
|
+
# @param response [Hash] response from diffend API
|
|
47
|
+
def build_verdict(command, config, response)
|
|
35
48
|
case response['action']
|
|
36
49
|
when 'allow'
|
|
37
50
|
build_allow_message(command, response)
|
|
@@ -42,10 +55,18 @@ module Diffend
|
|
|
42
55
|
|
|
43
56
|
exit 1
|
|
44
57
|
else
|
|
45
|
-
|
|
58
|
+
Diffend::HandleErrors::Report.call(
|
|
59
|
+
config: config,
|
|
60
|
+
message: :unsupported_verdict,
|
|
61
|
+
payload: response,
|
|
62
|
+
report: true
|
|
63
|
+
)
|
|
46
64
|
end
|
|
47
65
|
end
|
|
48
66
|
|
|
67
|
+
# @param response [Hash] response from diffend API
|
|
68
|
+
#
|
|
69
|
+
# @return [String]
|
|
49
70
|
def build_error_message(response)
|
|
50
71
|
<<~MSG
|
|
51
72
|
\nDiffend returned an error for your request.\n
|
|
@@ -53,6 +74,10 @@ module Diffend
|
|
|
53
74
|
MSG
|
|
54
75
|
end
|
|
55
76
|
|
|
77
|
+
# @param command [String] either install or update
|
|
78
|
+
# @param response [Hash] response from diffend API
|
|
79
|
+
#
|
|
80
|
+
# @return [String]
|
|
56
81
|
def build_allow_message(command, response)
|
|
57
82
|
<<~MSG
|
|
58
83
|
\nDiffend reported an allow verdict for #{command} command for this project.\n
|
|
@@ -61,6 +86,10 @@ module Diffend
|
|
|
61
86
|
MSG
|
|
62
87
|
end
|
|
63
88
|
|
|
89
|
+
# @param command [String] either install or update
|
|
90
|
+
# @param response [Hash] response from diffend API
|
|
91
|
+
#
|
|
92
|
+
# @return [String]
|
|
64
93
|
def build_deny_message(command, response)
|
|
65
94
|
<<~MSG
|
|
66
95
|
\nDiffend reported a deny verdict for #{command} command for this project.\n
|
|
@@ -13,17 +13,22 @@ module Diffend
|
|
|
13
13
|
Bundler::Source::Gemspec,
|
|
14
14
|
Bundler::Source::Path
|
|
15
15
|
].freeze
|
|
16
|
-
|
|
16
|
+
# List of dependency types
|
|
17
|
+
DEPENDENCIES_TYPES = {
|
|
17
18
|
direct: 0,
|
|
18
|
-
|
|
19
|
+
dependency: 1
|
|
19
20
|
}.freeze
|
|
20
|
-
|
|
21
|
+
# List of sources types
|
|
22
|
+
SOURCES_TYPES = {
|
|
21
23
|
valid: 0,
|
|
22
24
|
multiple_primary: 1
|
|
23
25
|
}.freeze
|
|
24
|
-
|
|
26
|
+
# List of gem sources types
|
|
27
|
+
GEM_SOURCES_TYPES = {
|
|
25
28
|
local: 0,
|
|
26
|
-
|
|
29
|
+
gemfile_source: 1,
|
|
30
|
+
gemfile_git: 2,
|
|
31
|
+
gemfile_path: 3
|
|
27
32
|
}.freeze
|
|
28
33
|
|
|
29
34
|
class << self
|
|
@@ -49,43 +54,45 @@ module Diffend
|
|
|
49
54
|
def initialize(definition)
|
|
50
55
|
@definition = definition
|
|
51
56
|
@direct_dependencies = Hash[definition.dependencies.map { |val| [val.name, val] }]
|
|
52
|
-
@main_source = definition.send(:sources).rubygems_sources.last
|
|
53
57
|
# Support case without Gemfile.lock
|
|
54
58
|
@locked_specs = @definition.locked_gems ? @definition.locked_gems.specs : []
|
|
55
59
|
end
|
|
56
60
|
|
|
61
|
+
# Build install specification
|
|
62
|
+
#
|
|
63
|
+
# @return [Hash]
|
|
57
64
|
def build_install
|
|
58
65
|
hash = build_main
|
|
59
66
|
|
|
60
|
-
@definition.
|
|
67
|
+
@definition.specs.each do |spec|
|
|
61
68
|
next if skip?(spec.source)
|
|
62
69
|
|
|
63
70
|
locked_spec = @locked_specs.find { |s| s.name == spec.name }
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'type' => build_dependency_type(spec2.name),
|
|
71
|
-
'versions' => build_versions(spec2)
|
|
72
|
+
hash['dependencies'][spec.name] = {
|
|
73
|
+
'platform' => build_spec_platform(spec, locked_spec),
|
|
74
|
+
'source' => build_spec_source(spec),
|
|
75
|
+
'type' => build_dependency_type(spec.name),
|
|
76
|
+
'versions' => build_versions(spec, locked_spec)
|
|
72
77
|
}
|
|
73
78
|
end
|
|
74
79
|
|
|
75
80
|
hash
|
|
76
81
|
end
|
|
77
82
|
|
|
78
|
-
#
|
|
83
|
+
# Build update specification
|
|
84
|
+
#
|
|
85
|
+
# @return [Hash]
|
|
79
86
|
def build_update
|
|
80
87
|
hash = build_main
|
|
81
88
|
|
|
82
|
-
@definition.
|
|
89
|
+
@definition.specs.each do |spec|
|
|
83
90
|
next if skip?(spec.source)
|
|
84
91
|
|
|
85
92
|
locked_spec = @locked_specs.find { |s| s.name == spec.name }
|
|
86
93
|
|
|
87
94
|
hash['dependencies'][spec.name] = {
|
|
88
|
-
'platform' => build_spec_platform(spec),
|
|
95
|
+
'platform' => build_spec_platform(spec, locked_spec),
|
|
89
96
|
'source' => build_spec_source(spec),
|
|
90
97
|
'type' => build_dependency_type(spec.name),
|
|
91
98
|
'versions' => build_versions(spec, locked_spec)
|
|
@@ -97,6 +104,9 @@ module Diffend
|
|
|
97
104
|
|
|
98
105
|
private
|
|
99
106
|
|
|
107
|
+
# Build default specification
|
|
108
|
+
#
|
|
109
|
+
# @return [Hash]
|
|
100
110
|
def build_main
|
|
101
111
|
{
|
|
102
112
|
'dependencies' => {},
|
|
@@ -106,8 +116,18 @@ module Diffend
|
|
|
106
116
|
}
|
|
107
117
|
end
|
|
108
118
|
|
|
119
|
+
# Build gem versions
|
|
120
|
+
#
|
|
121
|
+
# @param spec [Bundler::StubSpecification, Bundler::LazySpecification, Gem::Specification]
|
|
122
|
+
# @param locked_spec [Bundler::LazySpecification, Gem::Specification, NilClass]
|
|
123
|
+
#
|
|
124
|
+
# @return [Array<String>]
|
|
109
125
|
def build_versions(spec, locked_spec = nil)
|
|
110
|
-
locked_spec
|
|
126
|
+
if locked_spec && locked_spec.version.to_s != spec.version.to_s
|
|
127
|
+
[locked_spec.version.to_s, spec.version.to_s]
|
|
128
|
+
else
|
|
129
|
+
[spec.version.to_s]
|
|
130
|
+
end
|
|
111
131
|
end
|
|
112
132
|
|
|
113
133
|
# @param specs [Array] specs that are direct dependencies
|
|
@@ -115,78 +135,162 @@ module Diffend
|
|
|
115
135
|
#
|
|
116
136
|
# @return [Boolean] dependency type
|
|
117
137
|
def build_dependency_type(name)
|
|
118
|
-
@direct_dependencies.key?(name)
|
|
138
|
+
if @direct_dependencies.key?(name)
|
|
139
|
+
DEPENDENCIES_TYPES[:direct]
|
|
140
|
+
else
|
|
141
|
+
DEPENDENCIES_TYPES[:dependency]
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Build gem platform
|
|
146
|
+
#
|
|
147
|
+
# @param spec [Bundler::StubSpecification, Bundler::LazySpecification, Gem::Specification]
|
|
148
|
+
# @param locked_spec [Bundler::LazySpecification, Gem::Specification, NilClass]
|
|
149
|
+
#
|
|
150
|
+
# @return [String]
|
|
151
|
+
def build_spec_platform(spec, locked_spec)
|
|
152
|
+
parse_platform(
|
|
153
|
+
spec.platform || locked_spec&.platform || spec.send(:generic_local_platform)
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Parse gem platform
|
|
158
|
+
#
|
|
159
|
+
# @param platform [String, Gem::Platform]
|
|
160
|
+
#
|
|
161
|
+
# @return [String]
|
|
162
|
+
def parse_platform(platform)
|
|
163
|
+
case platform
|
|
164
|
+
when String then platform
|
|
165
|
+
when Gem::Platform then platform.os
|
|
166
|
+
end
|
|
119
167
|
end
|
|
120
168
|
|
|
121
|
-
|
|
122
|
-
|
|
169
|
+
# Build gem source type
|
|
170
|
+
#
|
|
171
|
+
# @param source [Bundler::Source] gem source type
|
|
172
|
+
#
|
|
173
|
+
# @return [Integer] internal gem source type
|
|
174
|
+
def build_spec_gem_source_type(source)
|
|
175
|
+
case source
|
|
176
|
+
when Bundler::Source::Metadata
|
|
177
|
+
GEM_SOURCES_TYPES[:local]
|
|
178
|
+
when Bundler::Source::Rubygems, Bundler::Source::Rubygems::Remote
|
|
179
|
+
GEM_SOURCES_TYPES[:gemfile_source]
|
|
180
|
+
when Bundler::Source::Git
|
|
181
|
+
GEM_SOURCES_TYPES[:gemfile_git]
|
|
182
|
+
when Bundler::Source::Path
|
|
183
|
+
GEM_SOURCES_TYPES[:gemfile_path]
|
|
184
|
+
else
|
|
185
|
+
raise ArgumentError, "unknown source #{source.class}"
|
|
186
|
+
end
|
|
123
187
|
end
|
|
124
188
|
|
|
189
|
+
# Build gem source
|
|
190
|
+
#
|
|
191
|
+
# @param spec [Bundler::StubSpecification, Bundler::LazySpecification, Gem::Specification]
|
|
192
|
+
#
|
|
193
|
+
# @return [Hash]
|
|
125
194
|
def build_spec_source(spec)
|
|
126
|
-
|
|
127
|
-
dep_spec = @direct_dependencies[spec.name]
|
|
195
|
+
source = source_for_spec(spec)
|
|
128
196
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
197
|
+
{
|
|
198
|
+
'type' => build_spec_gem_source_type(source),
|
|
199
|
+
'value' => source_name_from_source(source)
|
|
200
|
+
}
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Figure out source for gem
|
|
204
|
+
#
|
|
205
|
+
# @param spec [Bundler::StubSpecification, Bundler::LazySpecification, Gem::Specification]
|
|
206
|
+
#
|
|
207
|
+
# @return [Bundler::Source] gem source type
|
|
208
|
+
def source_for_spec(spec)
|
|
209
|
+
return spec.remote if spec.remote
|
|
210
|
+
|
|
211
|
+
case spec.source
|
|
212
|
+
when Bundler::Source::Rubygems
|
|
213
|
+
spec
|
|
214
|
+
.source
|
|
215
|
+
.send(:remote_specs)
|
|
216
|
+
.search(Bundler::Dependency.new(spec.name, spec.version))
|
|
217
|
+
.last
|
|
218
|
+
.remote
|
|
219
|
+
when Bundler::Source::Metadata, Bundler::Source::Git, Bundler::Source::Path
|
|
220
|
+
spec.source
|
|
134
221
|
else
|
|
135
|
-
|
|
136
|
-
{ 'type' => GEM_SOURCES[:gemfile], 'url' => source_name_from_source(spec.source) }
|
|
137
|
-
else
|
|
138
|
-
{ 'type' => GEM_SOURCES[:local], 'url' => '' }
|
|
139
|
-
end
|
|
222
|
+
raise ArgumentError, "unknown source #{spec.source.class}"
|
|
140
223
|
end
|
|
141
224
|
end
|
|
142
225
|
|
|
226
|
+
# Build gem source name
|
|
227
|
+
#
|
|
228
|
+
# @param source [Bundler::Source] gem source type
|
|
229
|
+
#
|
|
230
|
+
# @return [String]
|
|
143
231
|
def source_name_from_source(source)
|
|
144
|
-
|
|
232
|
+
case source
|
|
233
|
+
when Bundler::Source::Metadata
|
|
234
|
+
''
|
|
235
|
+
when Bundler::Source::Rubygems::Remote
|
|
236
|
+
source_name(source.anonymized_uri)
|
|
237
|
+
when Bundler::Source::Git
|
|
238
|
+
source.instance_variable_get(:@safe_uri)
|
|
239
|
+
when Bundler::Source::Path
|
|
240
|
+
source.path
|
|
241
|
+
else
|
|
242
|
+
raise ArgumentError, "unknown source #{source.class}"
|
|
243
|
+
end
|
|
145
244
|
end
|
|
146
245
|
|
|
147
|
-
|
|
148
|
-
|
|
246
|
+
# @param uri [Bundler::URI]
|
|
247
|
+
#
|
|
248
|
+
# @return [String]
|
|
249
|
+
def source_name(uri)
|
|
250
|
+
uri.to_s[0...-1]
|
|
149
251
|
end
|
|
150
252
|
|
|
253
|
+
# Build sources used in the Gemfile
|
|
254
|
+
#
|
|
255
|
+
# @return [Array<Hash>]
|
|
151
256
|
def build_sources
|
|
152
257
|
sources = @definition.send(:sources).rubygems_sources
|
|
153
|
-
hash =
|
|
258
|
+
hash = {}
|
|
154
259
|
|
|
155
260
|
sources.each do |source|
|
|
156
|
-
type = source.remotes
|
|
261
|
+
type = build_source_type(source.remotes)
|
|
157
262
|
|
|
158
263
|
source.remotes.each do |src|
|
|
159
|
-
hash
|
|
264
|
+
hash[source_name(src)] = type
|
|
160
265
|
end
|
|
161
266
|
end
|
|
162
267
|
|
|
163
|
-
hash
|
|
268
|
+
hash.map { |name, type| { 'name' => name, 'type' => type } }
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Build gem source type
|
|
272
|
+
#
|
|
273
|
+
# @param remotes [Array<Bundler::URI>]
|
|
274
|
+
#
|
|
275
|
+
# @return [Integer] internal source type
|
|
276
|
+
def build_source_type(remotes)
|
|
277
|
+
remotes.count > 1 ? SOURCES_TYPES[:multiple_primary] : SOURCES_TYPES[:valid]
|
|
164
278
|
end
|
|
165
279
|
|
|
166
280
|
# Checks if we should skip a source
|
|
167
281
|
#
|
|
168
|
-
# @param source [Bundler::Source
|
|
282
|
+
# @param source [Bundler::Source] gem source type
|
|
169
283
|
#
|
|
170
284
|
# @return [Boolean] true if we should skip this source, false otherwise
|
|
171
285
|
def skip?(source)
|
|
172
|
-
return true if git?(source)
|
|
173
286
|
return true if me?(source)
|
|
174
287
|
|
|
175
288
|
false
|
|
176
289
|
end
|
|
177
290
|
|
|
178
|
-
# Checks if it's a git source
|
|
179
|
-
#
|
|
180
|
-
# @param source [Bundler::Source::Git, Bundler::Source::Rubygems]
|
|
181
|
-
#
|
|
182
|
-
# @return [Boolean] true if it's a git source, false otherwise
|
|
183
|
-
def git?(source)
|
|
184
|
-
source.instance_of?(Bundler::Source::Git)
|
|
185
|
-
end
|
|
186
|
-
|
|
187
291
|
# Checks if it's a self source, this happens for repositories that are a gem
|
|
188
292
|
#
|
|
189
|
-
# @param source [Bundler::Source
|
|
293
|
+
# @param source [Bundler::Source] gem source type
|
|
190
294
|
#
|
|
191
295
|
# @return [Boolean] true if it's a self source, false otherwise
|
|
192
296
|
def me?(source)
|