krausefx-shenzhen 0.14.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +68 -0
- data/LICENSE +19 -0
- data/README.md +198 -0
- data/Rakefile +9 -0
- data/lib/shenzhen.rb +4 -0
- data/lib/shenzhen/agvtool.rb +17 -0
- data/lib/shenzhen/commands.rb +16 -0
- data/lib/shenzhen/commands/build.rb +213 -0
- data/lib/shenzhen/commands/distribute.rb +30 -0
- data/lib/shenzhen/commands/info.rb +94 -0
- data/lib/shenzhen/plistbuddy.rb +9 -0
- data/lib/shenzhen/plugins/crashlytics.rb +82 -0
- data/lib/shenzhen/plugins/deploygate.rb +97 -0
- data/lib/shenzhen/plugins/fir.rb +145 -0
- data/lib/shenzhen/plugins/ftp.rb +181 -0
- data/lib/shenzhen/plugins/hockeyapp.rb +119 -0
- data/lib/shenzhen/plugins/itunesconnect.rb +142 -0
- data/lib/shenzhen/plugins/pgyer.rb +135 -0
- data/lib/shenzhen/plugins/rivierabuild.rb +81 -0
- data/lib/shenzhen/plugins/s3.rb +139 -0
- data/lib/shenzhen/plugins/testfairy.rb +99 -0
- data/lib/shenzhen/version.rb +3 -0
- data/lib/shenzhen/xcodebuild.rb +99 -0
- data/shenzhen.gemspec +37 -0
- metadata +264 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'openssl'
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday_middleware'
|
5
|
+
|
6
|
+
module Shenzhen::Plugins
|
7
|
+
module RivieraBuild
|
8
|
+
class Client
|
9
|
+
HOSTNAME = 'apps.rivierabuild.com'
|
10
|
+
|
11
|
+
def initialize(api_token)
|
12
|
+
@api_token = api_token
|
13
|
+
@connection = Faraday.new(:url => "https://#{HOSTNAME}", :request => { :timeout => 120 }) do |builder|
|
14
|
+
builder.request :multipart
|
15
|
+
builder.request :url_encoded
|
16
|
+
builder.response :json, :content_type => /\bjson$/
|
17
|
+
builder.use FaradayMiddleware::FollowRedirects
|
18
|
+
builder.adapter :net_http
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def upload_build(ipa, options)
|
23
|
+
options[:file] = Faraday::UploadIO.new(ipa, 'application/octet-stream') if ipa and File.exist?(ipa)
|
24
|
+
|
25
|
+
@connection.post do |req|
|
26
|
+
req.url("/api/upload")
|
27
|
+
req.body = options
|
28
|
+
end.on_complete do |env|
|
29
|
+
yield env[:status], env[:body] if block_given?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
command :'distribute:rivierabuild' do |c|
|
37
|
+
c.syntax = "ipa distribute:rivierabuild [options]"
|
38
|
+
c.summary = "Distribute an .ipa file over RivieraBuild"
|
39
|
+
c.description = ""
|
40
|
+
c.option '-f', '--file FILE', ".ipa file for the build"
|
41
|
+
c.option '-k', '--key KEY', "API KEY. Available at https://apps.rivierabuild.com/settings"
|
42
|
+
c.option '-a', '--availability AVAILABILITY', "For how long the build will be available? More info: http://api.rivierabuild.com"
|
43
|
+
c.option '-p', '--passcode PASSCODE', "Optional passcode required to install the build on a device"
|
44
|
+
c.option '-n', '--note NOTE', "Release notes for the build, Markdown"
|
45
|
+
c.option '--commit-sha SHA', "The Git commit SHA for this build"
|
46
|
+
c.option '--app-id', "Riviera Build Application ID"
|
47
|
+
|
48
|
+
c.action do |args, options|
|
49
|
+
determine_file! unless @file = options.file
|
50
|
+
say_warning "Missing or unspecified .ipa file" unless @file and File.exist?(@file)
|
51
|
+
|
52
|
+
determine_rivierabuild_api_token! unless @api_token = options.key || ENV['RIVIERA_API_KEY']
|
53
|
+
say_error "Missing API Token" and abort unless @api_token
|
54
|
+
|
55
|
+
determine_availability! unless @availability = options.availability
|
56
|
+
say_error "Missing availability" and abort unless @availability
|
57
|
+
|
58
|
+
parameters = {}
|
59
|
+
parameters[:api_key] = @api_token
|
60
|
+
parameters[:availability] = @availability
|
61
|
+
parameters[:passcode] = options.passcode if options.passcode
|
62
|
+
parameters[:app_id] = options.app_id if options.app_id
|
63
|
+
parameters[:note] = options.note if options.note
|
64
|
+
parameters[:commit_sha] = options.commit_sha if options.commit_sha
|
65
|
+
|
66
|
+
client = Shenzhen::Plugins::RivieraBuild::Client.new(@api_token)
|
67
|
+
response = client.upload_build(@file, parameters)
|
68
|
+
case response.status
|
69
|
+
when 200...300
|
70
|
+
say_ok "Build successfully uploaded to RivieraBuild: #{response.body['file_url']}"
|
71
|
+
else
|
72
|
+
say_error "Error uploading to RivieraBuild: #{response.body}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def determine_rivierabuild_api_token!
|
79
|
+
@api_token ||= ask "API Key:"
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module Shenzhen::Plugins
|
4
|
+
module S3
|
5
|
+
class Client
|
6
|
+
def initialize(access_key_id, secret_access_key, region)
|
7
|
+
@s3 = AWS::S3.new(:access_key_id => access_key_id,
|
8
|
+
:secret_access_key => secret_access_key,
|
9
|
+
:region => region)
|
10
|
+
end
|
11
|
+
|
12
|
+
def upload_build(ipa, options)
|
13
|
+
path = expand_path_with_substitutions_from_ipa_plist(ipa, options[:path]) if options[:path]
|
14
|
+
|
15
|
+
@s3.buckets.create(options[:bucket]) if options[:create]
|
16
|
+
|
17
|
+
bucket = @s3.buckets[options[:bucket]]
|
18
|
+
|
19
|
+
uploaded_urls = []
|
20
|
+
|
21
|
+
files = []
|
22
|
+
files << ipa
|
23
|
+
files << options[:dsym] if options[:dsym]
|
24
|
+
files.each do |file|
|
25
|
+
basename = File.basename(file)
|
26
|
+
key = path ? File.join(path, basename) : basename
|
27
|
+
File.open(file) do |descriptor|
|
28
|
+
obj = bucket.objects.create(key, descriptor, :acl => options[:acl])
|
29
|
+
uploaded_urls << obj.public_url.to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
uploaded_urls
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def expand_path_with_substitutions_from_ipa_plist(ipa, path)
|
39
|
+
substitutions = path.scan(/\{CFBundle[^}]+\}/)
|
40
|
+
return path if substitutions.empty?
|
41
|
+
|
42
|
+
Dir.mktmpdir do |dir|
|
43
|
+
system "unzip -q #{ipa} -d #{dir} 2> /dev/null"
|
44
|
+
|
45
|
+
plist = Dir["#{dir}/**/*.app/Info.plist"].last
|
46
|
+
|
47
|
+
substitutions.uniq.each do |substitution|
|
48
|
+
key = substitution[1...-1]
|
49
|
+
value = Shenzhen::PlistBuddy.print(plist, key)
|
50
|
+
|
51
|
+
path.gsub!(Regexp.new(substitution), value) if value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
return path
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
command :'distribute:s3' do |c|
|
62
|
+
c.syntax = "ipa distribute:s3 [options]"
|
63
|
+
c.summary = "Distribute an .ipa file over Amazon S3"
|
64
|
+
c.description = ""
|
65
|
+
|
66
|
+
c.example '', '$ ipa distribute:s3 -f ./file.ipa -a accesskeyid --bucket bucket-name'
|
67
|
+
|
68
|
+
c.option '-f', '--file FILE', ".ipa file for the build"
|
69
|
+
c.option '-d', '--dsym FILE', "zipped .dsym package for the build"
|
70
|
+
c.option '-a', '--access-key-id ACCESS_KEY_ID', "AWS Access Key ID"
|
71
|
+
c.option '-s', '--secret-access-key SECRET_ACCESS_KEY', "AWS Secret Access Key"
|
72
|
+
c.option '-b', '--bucket BUCKET', "S3 bucket"
|
73
|
+
c.option '--[no-]create', "Create bucket if it doesn't already exist"
|
74
|
+
c.option '-r', '--region REGION', "Optional AWS region (for bucket creation)"
|
75
|
+
c.option '--acl ACL', "Uploaded object permissions e.g public_read (default), private, public_read_write, authenticated_read"
|
76
|
+
c.option '--source-dir SOURCE', "Optional source directory e.g. ./build"
|
77
|
+
c.option '-P', '--path PATH', "S3 'path'. Values from Info.plist will be substituded for keys wrapped in {} \n\t\t eg. \"/path/to/folder/{CFBundleVersion}/\" could be evaluated as \"/path/to/folder/1.0.0/\""
|
78
|
+
|
79
|
+
c.action do |args, options|
|
80
|
+
Dir.chdir(options.source_dir) if options.source_dir
|
81
|
+
|
82
|
+
determine_file! unless @file = options.file
|
83
|
+
say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
|
84
|
+
|
85
|
+
determine_dsym! unless @dsym = options.dsym
|
86
|
+
say_error "Specified dSYM.zip file doesn't exist" if @dsym and !File.exist?(@dsym)
|
87
|
+
|
88
|
+
determine_access_key_id! unless @access_key_id = options.access_key_id
|
89
|
+
say_error "Missing AWS Access Key ID" and abort unless @access_key_id
|
90
|
+
|
91
|
+
determine_secret_access_key! unless @secret_access_key = options.secret_access_key
|
92
|
+
say_error "Missing AWS Secret Access Key" and abort unless @secret_access_key
|
93
|
+
|
94
|
+
determine_bucket! unless @bucket = options.bucket
|
95
|
+
say_error "Missing bucket" and abort unless @bucket
|
96
|
+
|
97
|
+
determine_region! unless @region = options.region
|
98
|
+
|
99
|
+
determine_acl! unless @acl = options.acl
|
100
|
+
say_error "Missing ACL" and abort unless @acl
|
101
|
+
|
102
|
+
@path = options.path
|
103
|
+
|
104
|
+
client = Shenzhen::Plugins::S3::Client.new(@access_key_id, @secret_access_key, @region)
|
105
|
+
|
106
|
+
begin
|
107
|
+
urls = client.upload_build @file, {:bucket => @bucket, :create => !!options.create, :acl => @acl, :dsym => @dsym, :path => @path}
|
108
|
+
urls.each { |url| say_ok url}
|
109
|
+
say_ok "Build successfully uploaded to S3"
|
110
|
+
rescue => exception
|
111
|
+
say_error "Error while uploading to S3: #{exception}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def determine_access_key_id!
|
118
|
+
@access_key_id ||= ENV['AWS_ACCESS_KEY_ID']
|
119
|
+
@access_key_id ||= ask "Access Key ID:"
|
120
|
+
end
|
121
|
+
|
122
|
+
def determine_secret_access_key!
|
123
|
+
@secret_access_key ||= ENV['AWS_SECRET_ACCESS_KEY']
|
124
|
+
@secret_access_key ||= ask "Secret Access Key:"
|
125
|
+
end
|
126
|
+
|
127
|
+
def determine_bucket!
|
128
|
+
@bucket ||= ENV['S3_BUCKET']
|
129
|
+
@bucket ||= ask "S3 Bucket:"
|
130
|
+
end
|
131
|
+
|
132
|
+
def determine_region!
|
133
|
+
@region ||= ENV['AWS_REGION']
|
134
|
+
end
|
135
|
+
|
136
|
+
def determine_acl!
|
137
|
+
@acl ||= "public_read"
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'openssl'
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday_middleware'
|
5
|
+
|
6
|
+
module Shenzhen::Plugins
|
7
|
+
module TestFairy
|
8
|
+
class Client
|
9
|
+
HOSTNAME = 'app.testfairy.com'
|
10
|
+
|
11
|
+
def initialize(api_key)
|
12
|
+
@api_key = api_key
|
13
|
+
@connection = Faraday.new(:url => "https://#{HOSTNAME}") do |builder|
|
14
|
+
builder.request :multipart
|
15
|
+
builder.request :url_encoded
|
16
|
+
builder.response :json, :content_type => /\bjson$/
|
17
|
+
builder.use FaradayMiddleware::FollowRedirects
|
18
|
+
builder.adapter :net_http
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def upload_build(ipa, options)
|
23
|
+
options[:file] = Faraday::UploadIO.new(ipa, 'application/octet-stream') if ipa and File.exist?(ipa)
|
24
|
+
|
25
|
+
if symbols_file = options.delete(:symbols_file)
|
26
|
+
options[:symbols_file] = Faraday::UploadIO.new(symbols_file, 'application/octet-stream')
|
27
|
+
end
|
28
|
+
|
29
|
+
@connection.post do |req|
|
30
|
+
req.url("/api/upload/")
|
31
|
+
req.body = options
|
32
|
+
end.on_complete do |env|
|
33
|
+
yield env[:status], env[:body] if block_given?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
command :'distribute:testfairy' do |c|
|
41
|
+
c.syntax = "ipa distribute:testfairy [options]"
|
42
|
+
c.summary = "Distribute an .ipa file over TestFairy"
|
43
|
+
c.description = ""
|
44
|
+
c.option '-f', '--file FILE', ".ipa file for the build"
|
45
|
+
c.option '-d', '--dsym FILE', "zipped .dsym package for the build"
|
46
|
+
c.option '-a', '--key KEY', "API Key. Available at https://app.testfairy.com/settings for details."
|
47
|
+
c.option '-c', '--comment COMMENT', "Comment for the build"
|
48
|
+
c.option '--tester-groups GROUPS', 'Comma-separated list of tester groups to be notified on the new build. Or "all" to notify all testers.'
|
49
|
+
c.option '--metrics METRICS', "Comma-separated list of metrics to record"
|
50
|
+
c.option '--max-duration DURATION', 'Maximum session recording length, eg 20m or 1h. Default is "10m". Maximum 24h.'
|
51
|
+
c.option '--video ACTIVE', 'Video recording settings "on", "off" or "wifi" for recording video only when wifi is available. Default is "on".'
|
52
|
+
c.option '--video-quality QUALITY', 'Video quality settings, "high", "medium" or "low". Default is "high".'
|
53
|
+
c.option '--video-rate RATE', 'Video rate recording in frames per second, default is "1.0".'
|
54
|
+
c.option '--icon-watermark ADD', 'Add a small watermark to app icon. Default is "off".'
|
55
|
+
|
56
|
+
c.action do |args, options|
|
57
|
+
determine_file! unless @file = options.file
|
58
|
+
say_warning "Missing or unspecified .ipa file" unless @file and File.exist?(@file)
|
59
|
+
|
60
|
+
determine_dsym! unless @dsym = options.dsym
|
61
|
+
say_warning "Specified dSYM.zip file doesn't exist" if @dsym and !File.exist?(@dsym)
|
62
|
+
|
63
|
+
determine_testfairy_api_key! unless @api_key = options.key || ENV['TESTFAIRY_API_KEY']
|
64
|
+
say_error "Missing API Key" and abort unless @api_key
|
65
|
+
|
66
|
+
determine_notes! unless @comment = options.comment
|
67
|
+
say_error "Missing release comment" and abort unless @comment
|
68
|
+
|
69
|
+
parameters = {}
|
70
|
+
# Required
|
71
|
+
parameters[:api_key] = @api_key
|
72
|
+
# Optional
|
73
|
+
parameters[:comment] = @comment
|
74
|
+
parameters[:symbols_file] = @dsym if @dsym
|
75
|
+
parameters[:testers_groups] = options.testers_groups if options.testers_groups
|
76
|
+
parameters[:'max-duration'] = options.max_duration if options.max_duration
|
77
|
+
parameters[:video] = options.video if options.video
|
78
|
+
parameters[:'video-quality'] = options.video_quality if options.video_quality
|
79
|
+
parameters[:'video-rate'] = options.video_rate if options.video_rate
|
80
|
+
parameters[:'icon-watermark'] = options.icon_watermark if options.icon_watermark
|
81
|
+
parameters[:metrics] = options.metrics if options.metrics
|
82
|
+
|
83
|
+
|
84
|
+
client = Shenzhen::Plugins::TestFairy::Client.new(@api_key)
|
85
|
+
response = client.upload_build(@file, parameters)
|
86
|
+
case response.status
|
87
|
+
when 200...300
|
88
|
+
say_ok "Build successfully uploaded to TestFairy"
|
89
|
+
else
|
90
|
+
say_error "Error uploading to TestFairy: #{response.body}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def determine_testfairy_api_key!
|
97
|
+
@api_key ||= ask "API Key:"
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Shenzhen::XcodeBuild
|
4
|
+
class Info < OpenStruct; end
|
5
|
+
class Settings < OpenStruct
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(hash = {})
|
9
|
+
super
|
10
|
+
self.targets = hash.keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def members
|
14
|
+
self.targets
|
15
|
+
end
|
16
|
+
|
17
|
+
def each
|
18
|
+
members.each do |target|
|
19
|
+
yield target, send(target)
|
20
|
+
end
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Error < StandardError; end
|
27
|
+
class NilOutputError < Error; end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def info(*args)
|
31
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
32
|
+
output = `xcrun xcodebuild -list #{(args + args_from_options(options)).join(" ")} 2>&1`
|
33
|
+
|
34
|
+
raise Error.new $1 if /^xcodebuild\: error\: (.+)$/ === output
|
35
|
+
|
36
|
+
return nil unless /\S/ === output
|
37
|
+
|
38
|
+
lines = output.split(/\n/)
|
39
|
+
info, group = {}, nil
|
40
|
+
|
41
|
+
info[:project] = lines.shift.match(/\"(.+)\"\:/)[1] rescue nil
|
42
|
+
|
43
|
+
lines.each do |line|
|
44
|
+
if /\:$/ === line
|
45
|
+
group = line.strip[0...-1].downcase.gsub(/\s+/, '_')
|
46
|
+
info[group] = []
|
47
|
+
next
|
48
|
+
end
|
49
|
+
|
50
|
+
unless group.nil? or /\.$/ === line
|
51
|
+
info[group] << line.strip
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
info.each do |group, values|
|
56
|
+
next unless Array === values
|
57
|
+
values.delete("") and values.uniq!
|
58
|
+
end
|
59
|
+
|
60
|
+
Info.new(info)
|
61
|
+
end
|
62
|
+
|
63
|
+
def settings(*args)
|
64
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
65
|
+
output = `xcrun xcodebuild #{(args + args_from_options(options)).join(" ")} -showBuildSettings 2> /dev/null`
|
66
|
+
|
67
|
+
return nil unless /\S/ === output
|
68
|
+
|
69
|
+
raise Error.new $1 if /^xcodebuild\: error\: (.+)$/ === output
|
70
|
+
|
71
|
+
lines = output.split(/\n/)
|
72
|
+
|
73
|
+
settings, target = {}, nil
|
74
|
+
lines.each do |line|
|
75
|
+
case line
|
76
|
+
when /Build settings for action build and target \"?([^":]+)/
|
77
|
+
target = $1
|
78
|
+
settings[target] = {}
|
79
|
+
else
|
80
|
+
key, value = line.split(/\=/).collect(&:strip)
|
81
|
+
settings[target][key] = value if target
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Settings.new(settings)
|
86
|
+
end
|
87
|
+
|
88
|
+
def version
|
89
|
+
output = `xcrun xcodebuild -version`
|
90
|
+
output.scan(/([\d+\.?]+)/).flatten.first rescue nil
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def args_from_options(options = {})
|
96
|
+
options.reject{|key, value| value.nil?}.collect{|key, value| "-#{key} '#{value}'"}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/shenzhen.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require "shenzhen/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "krausefx-shenzhen"
|
8
|
+
s.authors = ["Mattt Thompson"]
|
9
|
+
s.email = "m@mattt.me"
|
10
|
+
s.license = "MIT"
|
11
|
+
s.homepage = "http://nomad-cli.com"
|
12
|
+
s.version = Shenzhen::VERSION
|
13
|
+
s.platform = Gem::Platform::RUBY
|
14
|
+
s.summary = "Shenzhen"
|
15
|
+
s.description = "CLI for Building & Distributing iOS Apps (.ipa Files)"
|
16
|
+
|
17
|
+
s.add_dependency "commander", "~> 4.3"
|
18
|
+
s.add_dependency "highline", ">= 1.7.2"
|
19
|
+
s.add_dependency "terminal-table", "~> 1.4.5"
|
20
|
+
s.add_dependency "json", "~> 1.8"
|
21
|
+
s.add_dependency "faraday", "~> 0.8.9"
|
22
|
+
s.add_dependency "faraday_middleware", "~> 0.9"
|
23
|
+
s.add_dependency "dotenv", ">= 0.7"
|
24
|
+
s.add_dependency "aws-sdk", "~> 1.0"
|
25
|
+
s.add_dependency "net-sftp", "~> 2.1.2"
|
26
|
+
s.add_dependency "plist", "~> 3.1.0"
|
27
|
+
s.add_dependency "rubyzip", "~> 1.1"
|
28
|
+
s.add_dependency "security", "~> 0.1.3"
|
29
|
+
|
30
|
+
s.add_development_dependency "rspec"
|
31
|
+
s.add_development_dependency "rake"
|
32
|
+
|
33
|
+
s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
|
34
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
35
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
end
|