wss_agent 0.0.17 → 0.0.21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/lib/config/default.yml +2 -1
- data/lib/data/ca-certificates.crt +4049 -0
- data/lib/wss_agent.rb +7 -8
- data/lib/wss_agent/cli.rb +11 -8
- data/lib/wss_agent/client.rb +37 -8
- data/lib/wss_agent/configure.rb +12 -10
- data/lib/wss_agent/gem_sha1.rb +3 -6
- data/lib/wss_agent/project.rb +15 -19
- data/lib/wss_agent/response.rb +3 -3
- data/lib/wss_agent/response_inventory.rb +7 -6
- data/lib/wss_agent/response_policies.rb +7 -6
- data/lib/wss_agent/specifications.rb +41 -15
- data/lib/wss_agent/version.rb +2 -2
- data/spec/fixtures/wss_agent.yml +1 -2
- data/spec/wss_agent/client_spec.rb +4 -4
- data/spec/wss_agent/configure_spec.rb +26 -26
- data/wss_agent.gemspec +19 -19
- metadata +26 -55
data/lib/wss_agent.rb
CHANGED
@@ -2,7 +2,7 @@ require 'thor'
|
|
2
2
|
require 'net/http'
|
3
3
|
require 'awesome_print'
|
4
4
|
require 'yaml'
|
5
|
-
require '
|
5
|
+
require 'multi_json'
|
6
6
|
require 'faraday'
|
7
7
|
require 'faraday_middleware'
|
8
8
|
require 'yell'
|
@@ -17,9 +17,8 @@ require 'wss_agent/client'
|
|
17
17
|
require 'wss_agent/gem_sha1'
|
18
18
|
require 'wss_agent/project'
|
19
19
|
|
20
|
-
|
21
20
|
module WssAgent
|
22
|
-
|
21
|
+
DEFAULT_CA_BUNDLE_PATH = File.dirname(__FILE__) + '/data/ca-certificates.crt'
|
23
22
|
|
24
23
|
class WssAgentError < StandardError
|
25
24
|
def self.status_code(code)
|
@@ -27,11 +26,11 @@ module WssAgent
|
|
27
26
|
end
|
28
27
|
end
|
29
28
|
|
30
|
-
class NotFoundConfigFile < WssAgentError; status_code(8)
|
31
|
-
class InvalidConfigFile < WssAgentError; status_code(9)
|
32
|
-
class TokenNotFound < WssAgentError; status_code(10)
|
33
|
-
class ApiUrlNotFound < WssAgentError; status_code(11)
|
34
|
-
class ApiUrlInvalid < WssAgentError; status_code(12)
|
29
|
+
class NotFoundConfigFile < WssAgentError; status_code(8); end
|
30
|
+
class InvalidConfigFile < WssAgentError; status_code(9); end
|
31
|
+
class TokenNotFound < WssAgentError; status_code(10); end
|
32
|
+
class ApiUrlNotFound < WssAgentError; status_code(11); end
|
33
|
+
class ApiUrlInvalid < WssAgentError; status_code(12); end
|
35
34
|
|
36
35
|
def self.logger
|
37
36
|
@logger ||= Yell.new STDOUT, level: [:info]
|
data/lib/wss_agent/cli.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
module WssAgent
|
2
2
|
class CLI < Thor
|
3
|
-
desc
|
3
|
+
desc 'config', 'create config file'
|
4
4
|
def config
|
5
5
|
File.open(File.join(Dir.pwd, Configure::CURRENT_CONFIG_FILE), 'w') do |f|
|
6
6
|
f << File.read(Configure.custom_default_path)
|
7
7
|
end
|
8
8
|
ap 'Created the config file: wss_agent.yml'
|
9
9
|
end
|
10
|
+
map init: :config
|
10
11
|
|
11
12
|
desc 'list', 'display list dependencies'
|
12
|
-
|
13
|
-
|
14
|
-
method_option :verbose, :
|
13
|
+
method_option :all, type: :boolean
|
14
|
+
method_option :excludes, type: :string
|
15
|
+
method_option :verbose, aliases: '-v', desc: 'Be verbose'
|
15
16
|
def list
|
16
17
|
WssAgent.enable_debug! if options['verbose']
|
17
18
|
results = Specifications.list(options)
|
@@ -24,9 +25,10 @@ module WssAgent
|
|
24
25
|
end
|
25
26
|
|
26
27
|
desc 'update', 'update open source inventory'
|
27
|
-
|
28
|
-
|
29
|
-
method_option :verbose, :
|
28
|
+
method_option :all, type: :boolean
|
29
|
+
method_option :excludes, type: :string
|
30
|
+
method_option :verbose, aliases: '-v', desc: 'Be verbose'
|
31
|
+
method_option :force, type: :boolean, aliases: '-f', desc: 'Force Check All Dependencies'
|
30
32
|
def update
|
31
33
|
WssAgent.enable_debug! if options['verbose']
|
32
34
|
Specifications.update(options)
|
@@ -35,7 +37,8 @@ module WssAgent
|
|
35
37
|
end
|
36
38
|
|
37
39
|
desc 'check_policies', 'checking dependencies that they conforms with company policy.'
|
38
|
-
method_option :verbose, :
|
40
|
+
method_option :verbose, aliases: '-v', desc: 'Be verbose'
|
41
|
+
method_option :force, type: :boolean, aliases: '-f', desc: 'Force Check All Dependencies'
|
39
42
|
def check_policies
|
40
43
|
WssAgent.enable_debug! if options['verbose']
|
41
44
|
Specifications.check_policies(options)
|
data/lib/wss_agent/client.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
module WssAgent
|
2
2
|
class Client
|
3
|
-
|
4
3
|
attr_accessor :connection
|
5
|
-
|
6
|
-
|
4
|
+
POLICY_TYPES = {
|
5
|
+
basic: 'CHECK_POLICIES',
|
6
|
+
compliance: 'CHECK_POLICY_COMPLIANCE'
|
7
|
+
}.freeze
|
8
|
+
|
9
|
+
UPDATE_TYPE = 'UPDATE'.freeze
|
7
10
|
REQUEST_TIMEOUT = 120
|
8
11
|
|
9
12
|
def initialize
|
10
|
-
@connection ||= Faraday.new(
|
13
|
+
@connection ||= Faraday.new(connection_options) do |h|
|
11
14
|
h.port = Configure.port
|
12
15
|
h.headers[:content_type] = 'application/x-www-form-urlencoded'
|
13
16
|
h.request :url_encoded
|
@@ -25,7 +28,7 @@ module WssAgent
|
|
25
28
|
if Configure['project_token']
|
26
29
|
diff_data['projectToken'] = Configure['project_token']
|
27
30
|
end
|
28
|
-
|
31
|
+
MultiJson.dump([diff_data])
|
29
32
|
end
|
30
33
|
|
31
34
|
def payload(gem_list, options = {})
|
@@ -41,11 +44,18 @@ module WssAgent
|
|
41
44
|
end
|
42
45
|
|
43
46
|
def update(gem_list)
|
44
|
-
ResponseInventory.new(request(gem_list,
|
47
|
+
ResponseInventory.new(request(gem_list, type: UPDATE_TYPE))
|
45
48
|
end
|
46
49
|
|
47
|
-
def check_policies(gem_list)
|
48
|
-
|
50
|
+
def check_policies(gem_list, options = {})
|
51
|
+
request_options =
|
52
|
+
if WssAgent::Configure['force_check_all_dependencies'] || options['force']
|
53
|
+
{ type: POLICY_TYPES[:compliance], forceCheckAllDependencies: true }
|
54
|
+
else
|
55
|
+
{ type: POLICY_TYPES[:basic], forceCheckAllDependencies: false }
|
56
|
+
end
|
57
|
+
|
58
|
+
ResponsePolicies.new(request(gem_list, request_options))
|
49
59
|
end
|
50
60
|
|
51
61
|
def request(gem_list, options = {})
|
@@ -55,5 +65,24 @@ module WssAgent
|
|
55
65
|
rescue Faraday::Error::ClientError => ex
|
56
66
|
ex
|
57
67
|
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def connection_options
|
72
|
+
@connection_options ||
|
73
|
+
begin
|
74
|
+
|
75
|
+
@connection_options = {
|
76
|
+
url: Configure.url,
|
77
|
+
request: { timeout: REQUEST_TIMEOUT }
|
78
|
+
}
|
79
|
+
if Configure.ssl?
|
80
|
+
@connection_options[:ssl] = {
|
81
|
+
ca_file: WssAgent::DEFAULT_CA_BUNDLE_PATH
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
@connection_options
|
86
|
+
end
|
58
87
|
end
|
59
88
|
end
|
data/lib/wss_agent/configure.rb
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
module WssAgent
|
2
2
|
class Configure
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
API_PATH = '/agent'
|
3
|
+
DEFAULT_CONFIG_FILE = 'default.yml'.freeze
|
4
|
+
CUSTOM_DEFAULT_CONFIG_FILE = 'custom_default.yml'.freeze
|
5
|
+
CURRENT_CONFIG_FILE = 'wss_agent.yml'.freeze
|
6
|
+
API_PATH = '/agent'.freeze
|
8
7
|
|
9
8
|
extend SingleForwardable
|
10
9
|
def_delegator :current, :[]
|
11
10
|
|
12
11
|
class << self
|
13
|
-
|
14
12
|
def default_path
|
15
13
|
File.join(File.expand_path('../..', __FILE__), 'config', DEFAULT_CONFIG_FILE)
|
16
14
|
end
|
@@ -38,8 +36,8 @@ module WssAgent
|
|
38
36
|
|
39
37
|
@current_config = YAML.load(File.read(current_path))
|
40
38
|
|
41
|
-
|
42
|
-
return raise InvalidConfigFile,
|
39
|
+
unless !!@current_config
|
40
|
+
return raise InvalidConfigFile, 'Problem reading wss_agent.yml, please check the file is a valid YAML'
|
43
41
|
end
|
44
42
|
|
45
43
|
default.merge(@current_config)
|
@@ -53,7 +51,7 @@ module WssAgent
|
|
53
51
|
URI(@url)
|
54
52
|
|
55
53
|
rescue URI::Error
|
56
|
-
raise ApiUrlInvalid,
|
54
|
+
raise ApiUrlInvalid, 'Api url is invalid. Could you please check url in wss_agent.yml'
|
57
55
|
end
|
58
56
|
|
59
57
|
def port
|
@@ -65,10 +63,14 @@ module WssAgent
|
|
65
63
|
[@uri.scheme, @uri.host].join('://')
|
66
64
|
end
|
67
65
|
|
66
|
+
def ssl?
|
67
|
+
uri.scheme == 'https'
|
68
|
+
end
|
69
|
+
|
68
70
|
def api_path
|
69
71
|
@uri = uri
|
70
72
|
@url_path = @uri.path
|
71
|
-
@url_path ==
|
73
|
+
@url_path == '' ? API_PATH : @url_path
|
72
74
|
end
|
73
75
|
|
74
76
|
def token
|
data/lib/wss_agent/gem_sha1.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'digest'
|
2
2
|
|
3
3
|
module WssAgent
|
4
4
|
class GemSha1
|
@@ -19,7 +19,7 @@ module WssAgent
|
|
19
19
|
h.adapter :excon
|
20
20
|
end
|
21
21
|
response = conn.get("/api/v1/versions/#{spec.name}.json")
|
22
|
-
versions =
|
22
|
+
versions = MultiJson.load(response.body)
|
23
23
|
unless versions.detect { |j| j['number'] == spec.version }
|
24
24
|
spec.version = versions.first['number']
|
25
25
|
end
|
@@ -61,9 +61,7 @@ module WssAgent
|
|
61
61
|
|
62
62
|
when '302' # redirect
|
63
63
|
response = Net::HTTP.get_response(URI(response['location']))
|
64
|
-
if response.code == '200'
|
65
|
-
return Digest::SHA1.hexdigest(response.body)
|
66
|
-
end
|
64
|
+
return Digest::SHA1.hexdigest(response.body) if response.code == '200'
|
67
65
|
else # gem isn't found
|
68
66
|
''
|
69
67
|
end
|
@@ -71,6 +69,5 @@ module WssAgent
|
|
71
69
|
rescue Timeout::Error
|
72
70
|
retry_request ? nil : remote_file(true)
|
73
71
|
end
|
74
|
-
|
75
72
|
end
|
76
73
|
end
|
data/lib/wss_agent/project.rb
CHANGED
@@ -2,43 +2,39 @@ module WssAgent
|
|
2
2
|
class Project
|
3
3
|
|
4
4
|
def project_name
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
when is_rails_app?
|
9
|
-
rails_app_name
|
10
|
-
else
|
11
|
-
folder_name
|
12
|
-
end
|
5
|
+
return gem.name if gem?
|
6
|
+
return rails_app_name if rails?
|
7
|
+
folder_name
|
13
8
|
end
|
14
9
|
|
15
10
|
def project_version
|
16
|
-
|
17
|
-
gem.version.to_s
|
18
|
-
else
|
19
|
-
''
|
20
|
-
end
|
11
|
+
gem? ? gem.version.to_s : ''
|
21
12
|
end
|
22
13
|
|
23
14
|
def folder_name
|
24
15
|
Bundler.root.split.last.to_s
|
25
16
|
end
|
26
17
|
|
27
|
-
def
|
18
|
+
def gem?
|
28
19
|
!Dir.glob(Bundler.root.join('*.gemspec')).last.nil?
|
29
20
|
end
|
30
21
|
|
31
22
|
def gem
|
32
|
-
@gem ||= Gem::Specification.load(
|
23
|
+
@gem ||= Gem::Specification.load(
|
24
|
+
Dir.glob(Bundler.root.join('*.gemspec')).last
|
25
|
+
)
|
33
26
|
end
|
34
27
|
|
35
|
-
def
|
36
|
-
|
28
|
+
def rails?
|
29
|
+
File.exist?(rails_app_path)
|
37
30
|
end
|
38
31
|
|
39
32
|
def rails_app_name
|
40
|
-
|
41
|
-
|
33
|
+
File.read(rails_app_path).match(/module (\w*)/)[1]
|
34
|
+
end
|
35
|
+
|
36
|
+
def rails_app_path
|
37
|
+
Bundler.root.join('config', 'application.rb')
|
42
38
|
end
|
43
39
|
end
|
44
40
|
end
|
data/lib/wss_agent/response.rb
CHANGED
@@ -23,7 +23,7 @@ module WssAgent
|
|
23
23
|
def parse_response
|
24
24
|
if response.success?
|
25
25
|
begin
|
26
|
-
@response_data =
|
26
|
+
@response_data = MultiJson.load(response.body)
|
27
27
|
@status = @response_data['status'].to_i
|
28
28
|
@message = @response_data['message']
|
29
29
|
rescue
|
@@ -49,9 +49,9 @@ module WssAgent
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def data
|
52
|
-
@data ||=
|
52
|
+
@data ||= MultiJson.load(response_data['data'])
|
53
53
|
rescue
|
54
|
-
response_data && response_data.key?('data') ?
|
54
|
+
response_data && response_data.key?('data') ? response_data['data'] : nil
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
@@ -6,19 +6,20 @@ module WssAgent
|
|
6
6
|
@message = "White Source update results: \n"
|
7
7
|
@message << " White Source organization: #{data['organization']} \n"
|
8
8
|
|
9
|
-
|
9
|
+
if data['createdProjects'].empty?
|
10
|
+
@message << " No new projects found \n"
|
11
|
+
else
|
10
12
|
@message << " #{data['createdProjects'].size} newly created projects: "
|
11
13
|
@message << data['createdProjects'].join(' ')
|
12
|
-
else
|
13
|
-
@message << " No new projects found \n"
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
if data['updatedProjects'].empty?
|
17
|
+
@message << "\n No projects were updated \n"
|
18
|
+
else
|
17
19
|
@message << " #{data['updatedProjects'].size} existing projects were updated: "
|
18
20
|
@message << data['updatedProjects'].join(' ')
|
19
|
-
else
|
20
|
-
@message << "\n No projects were updated \n"
|
21
21
|
end
|
22
|
+
|
22
23
|
@message
|
23
24
|
else
|
24
25
|
super
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module WssAgent
|
2
2
|
class ResponsePolicies < Response
|
3
|
-
REJECT_ACTION = 'Reject'
|
3
|
+
REJECT_ACTION = 'Reject'.freeze
|
4
4
|
|
5
5
|
def parse_response
|
6
6
|
if response.success?
|
7
7
|
begin
|
8
|
-
@response_data =
|
8
|
+
@response_data = MultiJson.load(response.body)
|
9
9
|
@status = @response_data['status'].to_i
|
10
10
|
@message = @response_data['message']
|
11
11
|
check_new_projects
|
@@ -20,7 +20,6 @@ module WssAgent
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
23
|
def message
|
25
24
|
if success?
|
26
25
|
if policy_violations?
|
@@ -33,7 +32,7 @@ module WssAgent
|
|
33
32
|
}.join("\n")
|
34
33
|
@message.join("\n")
|
35
34
|
else
|
36
|
-
|
35
|
+
'All dependencies conform with open source policies'
|
37
36
|
end
|
38
37
|
end
|
39
38
|
end
|
@@ -64,8 +63,10 @@ module WssAgent
|
|
64
63
|
def check(resource)
|
65
64
|
if resource.key?('resource') && resource.key?('policy') &&
|
66
65
|
(resource['policy']['actionType'] == REJECT_ACTION)
|
67
|
-
add_resource(
|
68
|
-
|
66
|
+
add_resource(
|
67
|
+
'resource' => resource['resource'],
|
68
|
+
'policy' => resource['policy']
|
69
|
+
)
|
69
70
|
end
|
70
71
|
|
71
72
|
if resource.key?('children') && resource['children'].is_a?(Array)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'digest'
|
2
2
|
|
3
3
|
module WssAgent
|
4
4
|
class Specifications
|
@@ -10,7 +10,11 @@ module WssAgent
|
|
10
10
|
# @option options [Boolean] 'all' if true then get all dependencies (include development dependencies)
|
11
11
|
# @option options [String] 'excludes' list gem name which need to exclude from end list
|
12
12
|
def specs(options = {})
|
13
|
-
list_gems = Bundler::Definition.build(
|
13
|
+
list_gems = Bundler::Definition.build(
|
14
|
+
Bundler.default_gemfile,
|
15
|
+
Bundler.default_lockfile,
|
16
|
+
false
|
17
|
+
).specs.to_a
|
14
18
|
if options['all']
|
15
19
|
# get all gems
|
16
20
|
list = {}
|
@@ -31,26 +35,38 @@ module WssAgent
|
|
31
35
|
new(specs(options)).call
|
32
36
|
end
|
33
37
|
|
38
|
+
def check_policy?(options = {})
|
39
|
+
options['force'] ||
|
40
|
+
WssAgent::Configure['check_policies'] ||
|
41
|
+
WssAgent::Configure['force_check_all_dependencies']
|
42
|
+
end
|
43
|
+
private :check_policy?
|
44
|
+
|
34
45
|
# Send gem list to server
|
35
46
|
#
|
36
47
|
# @param (see Specifications#specs)
|
37
48
|
def update(options = {})
|
38
49
|
wss_client = WssAgent::Client.new
|
39
|
-
|
40
|
-
|
50
|
+
|
51
|
+
if check_policy?(options)
|
52
|
+
policy_results = wss_client.check_policies(
|
53
|
+
WssAgent::Specifications.list(options),
|
54
|
+
options
|
55
|
+
)
|
41
56
|
if policy_results.success? && policy_results.policy_violations?
|
42
57
|
puts policy_results.message
|
43
58
|
return false
|
44
59
|
end
|
45
60
|
end
|
46
61
|
|
62
|
+
|
47
63
|
result = wss_client.update(WssAgent::Specifications.list(options))
|
48
64
|
if result.success?
|
49
65
|
WssAgent.logger.debug result.data
|
50
66
|
puts result.message
|
51
67
|
else
|
52
68
|
WssAgent.logger.debug "synchronization errors occur: status: #{result.status}, message: #{result.message}, data: #{result.data}"
|
53
|
-
ap "error: #{result.status}/#{result.data}", color: {string: :red }
|
69
|
+
ap "error: #{result.status}/#{result.data}", color: { string: :red }
|
54
70
|
end
|
55
71
|
|
56
72
|
result.success?
|
@@ -61,13 +77,16 @@ module WssAgent
|
|
61
77
|
# @param (see Specifications#specs)
|
62
78
|
def check_policies(options = {})
|
63
79
|
wss_client = WssAgent::Client.new
|
64
|
-
result = wss_client.check_policies(
|
80
|
+
result = wss_client.check_policies(
|
81
|
+
WssAgent::Specifications.list(options),
|
82
|
+
options
|
83
|
+
)
|
65
84
|
if result.success?
|
66
85
|
WssAgent.logger.debug result.data
|
67
86
|
puts result.message
|
68
87
|
else
|
69
88
|
WssAgent.logger.debug "check policies errors occur: #{result.status}, message: #{result.message}, data: #{result.data}"
|
70
|
-
ap "error: #{result.status}/#{result.data}", color: {string: :red }
|
89
|
+
ap "error: #{result.status}/#{result.data}", color: { string: :red }
|
71
90
|
end
|
72
91
|
end
|
73
92
|
|
@@ -84,6 +103,7 @@ module WssAgent
|
|
84
103
|
if options['excludes'] && options['excludes'].to_s.split(',').include?(gd.name)
|
85
104
|
next
|
86
105
|
end
|
106
|
+
|
87
107
|
gs = gd.matching_specs.first
|
88
108
|
if gs
|
89
109
|
unless list[gs.name]
|
@@ -94,8 +114,10 @@ module WssAgent
|
|
94
114
|
end
|
95
115
|
else
|
96
116
|
unless list[gd.name]
|
97
|
-
list[gd.name] = Gem::Specification.new(
|
98
|
-
|
117
|
+
list[gd.name] = Gem::Specification.new(
|
118
|
+
gd.name,
|
119
|
+
gd.requirements_list.last.scan(/[\d\.\w]+/).first
|
120
|
+
)
|
99
121
|
rm_dep = remote_dependencies(gd.name, gd.requirements_list.last)
|
100
122
|
unless rm_dep.empty?
|
101
123
|
list = gem_dependencies(list, rm_dep, options)
|
@@ -113,16 +135,20 @@ module WssAgent
|
|
113
135
|
# @params version [String] version gem
|
114
136
|
#
|
115
137
|
# @return [Array<Gem::Dependency>] list gem dependencies
|
116
|
-
def remote_dependencies(gem_name,
|
138
|
+
def remote_dependencies(gem_name, _version)
|
117
139
|
conn = Faraday.new(url: 'https://rubygems.org') do |h|
|
118
140
|
h.headers[:content_type] = 'application/x-www-form-urlencoded'
|
119
141
|
h.request :url_encoded
|
120
142
|
h.adapter :excon
|
121
143
|
end
|
122
144
|
response = conn.get("/api/v1/gems/#{gem_name}.json")
|
123
|
-
dep_list =
|
124
|
-
dep_list['dependencies'].values.flatten.
|
125
|
-
|
145
|
+
dep_list = MultiJson.load(response.body)
|
146
|
+
dep_list['dependencies'].values.flatten.map do |j|
|
147
|
+
Gem::Dependency.new(
|
148
|
+
j['name'],
|
149
|
+
Gem::Requirement.new(j['requirements'].split(','))
|
150
|
+
)
|
151
|
+
end
|
126
152
|
end
|
127
153
|
end # end class << self
|
128
154
|
|
@@ -140,8 +166,8 @@ module WssAgent
|
|
140
166
|
def gem_item(spec)
|
141
167
|
{
|
142
168
|
'groupId' => spec.name,
|
143
|
-
|
144
|
-
|
169
|
+
'artifactId' => spec.file_name,
|
170
|
+
'version' => spec.version.to_s,
|
145
171
|
'sha1' => GemSha1.new(spec).sha1,
|
146
172
|
'optional' => false,
|
147
173
|
'children' => [],
|