fir-cli 1.1.5 → 1.1.7

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.
@@ -4,73 +4,37 @@ module FIR
4
4
  module Build
5
5
 
6
6
  def build_ipa *args, options
7
- # initialize build options
7
+ # check build environment and make build cmd
8
+ check_osx
9
+
8
10
  if args.first.blank? || !File.exist?(args.first)
9
11
  @build_dir = Dir.pwd
10
12
  else
11
13
  @build_dir = File.absolute_path(args.shift.to_s) # pop the first param
12
14
  end
13
15
 
14
- @build_cmd = "xcodebuild build -sdk iphoneos"
15
- @build_tmp_dir = Dir.mktmpdir
16
- @custom_settings = parse_custom_settings(args) # convert ['a=1', 'b=2'] => { 'a' => '1', 'b' => '2' }
17
- @configuration = options[:configuration]
18
- @wrapper_name = File.basename(options[:name].to_s, '.*') + '.app' unless options[:name].blank?
19
- @target_name = options[:target]
20
- @scheme_name = options[:scheme]
21
- @output_path = options[:output].blank? ? "#{@build_dir}/build_ipa" : File.absolute_path(options[:output].to_s)
22
- @dsym_name = @wrapper_name + '.dSYM' unless @wrapper_name.blank?
23
-
24
- # check build environment and make build cmd
25
- check_osx
26
- if options.workspace?
27
- @workspace = check_and_find_workspace(@build_dir)
28
- check_scheme(@scheme_name)
29
- @build_cmd += " -workspace '#{@workspace}' -scheme '#{@scheme_name}'"
30
- else
31
- @project = check_and_find_project(@build_dir)
32
- @build_cmd += " -project '#{@project}'"
33
- end
34
-
35
- @build_cmd += " -configuration '#{@configuration}'" unless @configuration.blank?
36
- @build_cmd += " -target '#{@target_name}'" unless @target_name.blank?
16
+ @token = options[:token] || current_token
17
+ @changelog = options[:changelog].to_s
18
+ @short = options[:short].to_s
19
+ @proj = options[:proj].to_s
37
20
 
38
- # convert { "a" => "1", "b" => "2" } => "a='1' b='2'"
39
- @setting_str = @custom_settings.collect { |k, v| "#{k}='#{v}'" }.join(' ')
40
- @setting_str += " WRAPPER_NAME='#{@wrapper_name}'" unless @wrapper_name.blank?
41
- @setting_str += " TARGET_BUILD_DIR='#{@build_tmp_dir}'" unless @custom_settings['TARGET_BUILD_DIR']
42
- @setting_str += " CONFIGURATION_BUILD_DIR='#{@build_tmp_dir}'" unless @custom_settings['CONFIGURATION_BUILD_DIR']
43
- @setting_str += " DWARF_DSYM_FOLDER_PATH='#{@output_path}'" unless @custom_settings['DWARF_DSYM_FOLDER_PATH']
44
- @setting_str += " DWARF_DSYM_FILE_NAME='#{@dsym_name}'" unless @dsym_name.blank?
21
+ @build_tmp_dir = Dir.mktmpdir
22
+ @output_path = options[:output].blank? ? "#{@build_dir}/fir_build_ipa" : File.absolute_path(options[:output].to_s)
23
+ @ipa_build_cmd = initialize_ipa_build_cmd(args, options)
45
24
 
46
- @build_cmd += " #{@setting_str} 2>&1"
47
- puts @build_cmd if $DEBUG
25
+ puts @ipa_build_cmd if $DEBUG
48
26
 
49
27
  logger.info "Building......"
50
28
  logger_info_dividing_line
51
29
 
52
- logger.info `#{@build_cmd}`
53
-
54
- FileUtils.mkdir_p(@output_path) unless File.exist?(@output_path)
55
- Dir.chdir(@build_tmp_dir) do
56
- apps = Dir["*.app"]
57
- if apps.length == 0
58
- logger.error "Builded has no output app, Can not be packaged"
59
- exit 1
60
- end
30
+ logger.info `#{@ipa_build_cmd}`
61
31
 
62
- apps.each do |app|
63
- ipa_path = File.join(@output_path, "#{File.basename(app, '.app')}.ipa")
64
- zip_app2ipa(File.join(@build_tmp_dir, app), ipa_path)
65
- end
66
- end
32
+ output_ipa
67
33
 
68
- logger.info "Build Success"
34
+ publish_build_ipa if options.publish?
35
+ upload_build_mapping_file if options.mapping?
69
36
 
70
- if options.publish?
71
- ipa_path = Dir["#{@output_path}/*.ipa"].first
72
- publish(ipa_path, short: options[:short], changelog: options[:changelog], token: options[:token])
73
- end
37
+ logger_info_blank_line
74
38
  end
75
39
 
76
40
  def build_apk *args, options
@@ -78,12 +42,89 @@ module FIR
78
42
 
79
43
  private
80
44
 
81
- def parse_custom_settings args
45
+ def initialize_ipa_build_cmd args, options
46
+ ipa_build_cmd = "xcodebuild build -sdk iphoneos"
47
+
48
+ @configuration = options[:configuration]
49
+ @wrapper_name = File.basename(options[:name].to_s, '.*') + '.app' unless options[:name].blank?
50
+ @target_name = options[:target]
51
+ @scheme_name = options[:scheme]
52
+ @dsym_name = @wrapper_name + '.dSYM' unless @wrapper_name.blank?
53
+
54
+ if options.workspace?
55
+ workspace = check_and_find_ios_workspace(@build_dir)
56
+ check_ios_scheme(@scheme_name)
57
+ ipa_build_cmd += " -workspace '#{workspace}' -scheme '#{@scheme_name}'"
58
+ else
59
+ project = check_and_find_ios_project(@build_dir)
60
+ ipa_build_cmd += " -project '#{project}'"
61
+ end
62
+
63
+ ipa_build_cmd += " -configuration '#{@configuration}'" unless @configuration.blank?
64
+ ipa_build_cmd += " -target '#{@target_name}'" unless @target_name.blank?
65
+ ipa_build_cmd += " #{ipa_custom_settings(args)} 2>&1"
66
+
67
+ ipa_build_cmd
68
+ end
69
+
70
+ def ipa_custom_settings args
71
+ custom_settings = parse_ipa_custom_settings(args)
72
+
73
+ # convert { "a" => "1", "b" => "2" } => "a='1' b='2'"
74
+ setting_str = custom_settings.collect { |k, v| "#{k}='#{v}'" }.join(' ')
75
+ setting_str += " WRAPPER_NAME='#{@wrapper_name}'" unless @wrapper_name.blank?
76
+ setting_str += " TARGET_BUILD_DIR='#{@build_tmp_dir}'" unless custom_settings['TARGET_BUILD_DIR']
77
+ setting_str += " CONFIGURATION_BUILD_DIR='#{@build_tmp_dir}'" unless custom_settings['CONFIGURATION_BUILD_DIR']
78
+ setting_str += " DWARF_DSYM_FOLDER_PATH='#{@output_path}'" unless custom_settings['DWARF_DSYM_FOLDER_PATH']
79
+ setting_str += " DWARF_DSYM_FILE_NAME='#{@dsym_name}'" unless @dsym_name.blank?
80
+ setting_str
81
+ end
82
+
83
+ def output_ipa
84
+ FileUtils.mkdir_p(@output_path) unless File.exist?(@output_path)
85
+ Dir.chdir(@build_tmp_dir) do
86
+ apps = Dir["*.app"]
87
+ if apps.length == 0
88
+ logger.error "Builded has no output app, Can not be packaged"
89
+ exit 1
90
+ end
91
+
92
+ apps.each do |app|
93
+ ipa_path = File.join(@output_path, "#{File.basename(app, '.app')}.ipa")
94
+ zip_app2ipa(File.join(@build_tmp_dir, app), ipa_path)
95
+ end
96
+ end
97
+
98
+ logger.info "Build Success"
99
+ end
100
+
101
+ def publish_build_ipa
102
+ logger_info_blank_line
103
+ publish Dir["#{@output_path}/*.ipa"].first, short: @short,
104
+ changelog: @changelog,
105
+ token: @token
106
+ end
107
+
108
+ def upload_build_mapping_file
109
+ logger_info_blank_line
110
+
111
+ @app_info = ipa_info(Dir["#{@output_path}/*.ipa"].first)
112
+ @mapping_file = Dir["#{@output_path}/*.dSYM/Contents/Resources/DWARF/*"].first
113
+
114
+ mapping @mapping_file, proj: @proj,
115
+ build: @app_info[:build],
116
+ version: @app_info[:version],
117
+ token: @token
118
+ end
119
+
120
+ # convert ['a=1', 'b=2'] => { 'a' => '1', 'b' => '2' }
121
+ def parse_ipa_custom_settings args
82
122
  hash = {}
83
123
  args.each do |setting|
84
124
  k, v = setting.split('=', 2).map(&:strip)
85
125
  hash[k] = v
86
126
  end
127
+
87
128
  hash
88
129
  end
89
130
 
@@ -94,13 +135,13 @@ module FIR
94
135
  end
95
136
  end
96
137
 
97
- def check_and_find_project path
138
+ def check_and_find_ios_project path
98
139
  unless File.exist?(path)
99
140
  logger.error "The first param BUILD_DIR must be a xcodeproj directory"
100
141
  exit 1
101
142
  end
102
143
 
103
- if is_project?(path)
144
+ if is_ios_project?(path)
104
145
  project = path
105
146
  else
106
147
  project = Dir["#{path}/*.xcodeproj"].first
@@ -113,13 +154,13 @@ module FIR
113
154
  project
114
155
  end
115
156
 
116
- def check_and_find_workspace path
157
+ def check_and_find_ios_workspace path
117
158
  unless File.exist?(path)
118
159
  logger.error "The first param BUILD_DIR must be a xcworkspace directory"
119
160
  exit 1
120
161
  end
121
162
 
122
- if is_workspace?(path)
163
+ if is_ios_workspace?(path)
123
164
  workspace = path
124
165
  else
125
166
  workspace = Dir["#{path}/*.xcworkspace"].first
@@ -132,18 +173,18 @@ module FIR
132
173
  workspace
133
174
  end
134
175
 
135
- def check_scheme scheme_name
176
+ def check_ios_scheme scheme_name
136
177
  if scheme_name.blank?
137
178
  logger.error "Must provide a scheme by `-S` option when build a workspace"
138
179
  exit 1
139
180
  end
140
181
  end
141
182
 
142
- def is_project? path
183
+ def is_ios_project? path
143
184
  File.extname(path) == '.xcodeproj'
144
185
  end
145
186
 
146
- def is_workspace? path
187
+ def is_ios_workspace? path
147
188
  File.extname(path) == '.xcworkspace'
148
189
  end
149
190
 
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Config
5
+ CONFIG_PATH = "#{ENV['HOME']}/.fir-cli"
6
+ API_YML_PATH = File.expand_path("../../", __FILE__) + '/api.yml'
7
+ APP_FILE_TYPE = %w(.ipa .apk).freeze
8
+
9
+ def fir_api
10
+ @fir_api ||= YAML.load_file(API_YML_PATH).deep_symbolize_keys[:fir]
11
+ end
12
+
13
+ def bughd_api
14
+ @bughd_api ||= YAML.load_file(API_YML_PATH).deep_symbolize_keys[:bughd]
15
+ end
16
+
17
+ def config
18
+ @config ||= YAML.load_file(CONFIG_PATH).deep_symbolize_keys if File.exist?(CONFIG_PATH)
19
+ end
20
+
21
+ def reload_config
22
+ @config = YAML.load_file(CONFIG_PATH).deep_symbolize_keys
23
+ end
24
+
25
+ def write_config hash
26
+ File.open(CONFIG_PATH, 'w+') { |f| f << YAML.dump(hash) }
27
+ end
28
+
29
+ def current_token
30
+ @token ||= config[:token] if config
31
+ end
32
+
33
+ alias_method :☠, :exit
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Http
5
+
6
+ DEFAULT_TIMEOUT = 300
7
+
8
+ def get url, params = {}
9
+ begin
10
+ res = ::RestClient::Request.execute(
11
+ method: :get,
12
+ url: url,
13
+ timeout: DEFAULT_TIMEOUT,
14
+ headers: default_headers.merge(params: params)
15
+ )
16
+ rescue => e
17
+ logger.error e.message.to_s + " - " + e.response.to_s
18
+ exit 1
19
+ end
20
+
21
+ JSON.parse(res.body.force_encoding("UTF-8"), symbolize_names: true)
22
+ end
23
+
24
+ %w(post patch put).each do |method|
25
+ define_method method do |url, query|
26
+ begin
27
+ res = ::RestClient::Request.execute(
28
+ method: method.to_sym,
29
+ url: url,
30
+ payload: query,
31
+ timeout: DEFAULT_TIMEOUT,
32
+ headers: default_headers
33
+ )
34
+ rescue => e
35
+ logger.error e.message.to_s + " - " + e.response.to_s
36
+ exit 1
37
+ end
38
+
39
+ JSON.parse(res.body.force_encoding("UTF-8"), symbolize_names: true)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def default_headers
46
+ { content_type: :json, source: 'fir-cli', version: FIR::VERSION }
47
+ end
48
+ end
49
+ end
data/lib/fir/util/info.rb CHANGED
@@ -7,6 +7,7 @@ module FIR
7
7
  file_path = File.absolute_path(args.first.to_s)
8
8
  is_all = !options[:all].blank?
9
9
 
10
+ check_file_exist file_path
10
11
  check_supported_file file_path
11
12
 
12
13
  file_type = File.extname(file_path).delete('.')
@@ -16,10 +17,12 @@ module FIR
16
17
 
17
18
  app_info = send("#{file_type}_info", file_path, is_all)
18
19
  app_info.each { |k, v| logger.info "#{k}: #{v}" }
20
+
21
+ logger_info_blank_line
19
22
  end
20
23
 
21
- def ipa_info ipa_path, is_all
22
- ipa = Parser::IPA.new(ipa_path)
24
+ def ipa_info ipa_path, is_all = false
25
+ ipa = FIR::Parser::Ipa.new(ipa_path)
23
26
  app = ipa.app
24
27
 
25
28
  info = {
@@ -52,7 +55,7 @@ module FIR
52
55
  info
53
56
  end
54
57
 
55
- def apk_info apk_path, is_all
58
+ def apk_info apk_path, is_all = false
56
59
  apk = Android::Apk.new(apk_path)
57
60
  info = {
58
61
  type: 'android',
@@ -12,6 +12,7 @@ module FIR
12
12
  write_config(email: user_info.fetch(:email, ''), token: token)
13
13
  reload_config
14
14
  logger.info "Login succeed, current user's email: #{config[:email]}"
15
+ logger_info_blank_line
15
16
  end
16
17
  end
17
18
  end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Mapping
5
+
6
+ def mapping *args, options
7
+ @file_path = File.absolute_path(args.first.to_s)
8
+ @token = options[:token] || current_token
9
+ @proj = options[:proj].to_s
10
+ @version = options[:version].to_s
11
+ @build = options[:build].to_s
12
+
13
+ check_file_exist @file_path
14
+ check_token_cannot_be_blank @token
15
+ check_project_id_cannot_be_blank
16
+
17
+ logger.info "Creating bughd project's version......."
18
+ logger_info_dividing_line
19
+
20
+ @full_version = find_or_create_bughd_full_version
21
+
22
+ logger.info "Uploading mapping file......."
23
+ logger_info_dividing_line
24
+
25
+ upload_mapping_file
26
+
27
+ logger.info "Uploaded succeed: #{bughd_api[:domain]}/project/#{@proj}/settings"
28
+ logger_info_blank_line
29
+ end
30
+
31
+ def find_or_create_bughd_full_version
32
+ url = bughd_api[:project_url] + "/#{@proj}/full_versions"
33
+ post url, version: @version, build: @build, uuid: uuid
34
+ end
35
+
36
+ def upload_mapping_file
37
+ if File.is_dsym?(@file_path)
38
+ tmp_file_path = "#{Dir.tmpdir}/#{File.basename(@file_path)}-fircli.dSYM"
39
+ FileUtils.cp(@file_path, tmp_file_path)
40
+ elsif File.is_txt?(@file_path)
41
+ tmp_file_path = "#{Dir.tmpdir}/#{File.basename(@file_path)}-fircli.txt"
42
+ FileUtils.cp(@file_path, tmp_file_path)
43
+ else
44
+ tmp_file_path = @file_path
45
+ end
46
+
47
+ url = bughd_api[:full_version_url] + "/#{@full_version[:id]}"
48
+ patch url, file: File.new(tmp_file_path, 'rb'), project_id: @proj, uuid: uuid
49
+ end
50
+
51
+ private
52
+
53
+ def check_project_id_cannot_be_blank
54
+ if @proj.blank?
55
+ logger.error "Project id can't be blank"
56
+ exit 1
57
+ end
58
+ end
59
+
60
+ def uuid
61
+ @uuid ||= fetch_user_uuid(@token)
62
+ end
63
+
64
+ end
65
+ end
data/lib/fir/util/me.rb CHANGED
@@ -6,13 +6,14 @@ module FIR
6
6
  def me
7
7
  check_logined
8
8
 
9
- if user_info = fetch_user_info(current_token)
10
- email = user_info.fetch(:email, '')
11
- name = user_info.fetch(:name, '')
9
+ user_info = fetch_user_info(current_token)
12
10
 
13
- logger.info "Login succeed, current user's email: #{email}"
14
- logger.info "Login succeed, current user's name: #{name}"
15
- end
11
+ email = user_info.fetch(:email, '')
12
+ name = user_info.fetch(:name, '')
13
+
14
+ logger.info "Login succeed, current user's email: #{email}"
15
+ logger.info "Login succeed, current user's name: #{name}"
16
+ logger_info_blank_line
16
17
  end
17
18
  end
18
19
  end
@@ -0,0 +1,157 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Parser
5
+
6
+ class Ipa
7
+
8
+ def initialize(path)
9
+ @path = path
10
+ end
11
+
12
+ def app
13
+ @app ||= App.new(app_path)
14
+ end
15
+
16
+ def app_path
17
+ @app_path ||= Dir.glob(File.join(contents, 'Payload', '*.app')).first
18
+ end
19
+
20
+ def cleanup
21
+ return unless @contents
22
+ FileUtils.rm_rf(@contents)
23
+ @contents = nil
24
+ end
25
+
26
+ def metadata
27
+ return unless has_metadata?
28
+ @metadata ||= CFPropertyList.native_types(CFPropertyList::List.new(file: metadata_path).value)
29
+ end
30
+
31
+ def has_metadata?
32
+ File.file? metadata_path
33
+ end
34
+
35
+ def metadata_path
36
+ @metadata_path ||= File.join(@contents, 'iTunesMetadata.plist')
37
+ end
38
+
39
+ def release_type
40
+ has_metadata? ? 'store' : 'adhoc'
41
+ end
42
+
43
+ def contents
44
+ return if @contents
45
+ @contents = "fir-cli_tmp/ipa_files-#{Time.now.to_i}"
46
+
47
+ Zip::File.open(@path) do |zip_file|
48
+ zip_file.each do |f|
49
+ f_path = File.join(@contents, f.name)
50
+ FileUtils.mkdir_p(File.dirname(f_path))
51
+ zip_file.extract(f, f_path) unless File.exist?(f_path)
52
+ end
53
+ end
54
+
55
+ @contents
56
+ end
57
+
58
+ class App
59
+
60
+ def initialize(path)
61
+ @path = path
62
+ end
63
+
64
+ def info
65
+ @info ||= CFPropertyList.native_types(
66
+ CFPropertyList::List.new(file: File.join(@path, 'Info.plist')).value)
67
+ end
68
+
69
+ def name
70
+ info['CFBundleName']
71
+ end
72
+
73
+ def identifier
74
+ info['CFBundleIdentifier']
75
+ end
76
+
77
+ def display_name
78
+ info['CFBundleDisplayName']
79
+ end
80
+
81
+ def version
82
+ info['CFBundleVersion']
83
+ end
84
+
85
+ def short_version
86
+ info['CFBundleShortVersionString']
87
+ end
88
+
89
+ def icons
90
+ @icons ||= begin
91
+ icons = []
92
+ info['CFBundleIcons']['CFBundlePrimaryIcon']['CFBundleIconFiles'].each do |name|
93
+ icons << get_image(name)
94
+ icons << get_image("#{name}@2x")
95
+ end
96
+ icons.delete_if { |i| !i }
97
+ rescue NoMethodError
98
+ []
99
+ end
100
+ end
101
+
102
+ def mobileprovision
103
+ return unless has_mobileprovision?
104
+ return @mobileprovision if @mobileprovision
105
+
106
+ cmd = "security cms -D -i \"#{mobileprovision_path}\""
107
+ begin
108
+ @mobileprovision = CFPropertyList.native_types(CFPropertyList::List.new(data: `#{cmd}`).value)
109
+ rescue CFFormatError
110
+ @mobileprovision = {}
111
+ end
112
+ end
113
+
114
+ def has_mobileprovision?
115
+ File.file? mobileprovision_path
116
+ end
117
+
118
+ def mobileprovision_path
119
+ @mobileprovision_path ||= File.join(@path, 'embedded.mobileprovision')
120
+ end
121
+
122
+ def hide_developer_certificates
123
+ mobileprovision.delete('DeveloperCertificates') if has_mobileprovision?
124
+ end
125
+
126
+ def devices
127
+ mobileprovision['ProvisionedDevices'] if has_mobileprovision?
128
+ end
129
+
130
+ def distribution_name
131
+ "#{mobileprovision['Name']} - #{mobileprovision['TeamName']}" if has_mobileprovision?
132
+ end
133
+
134
+ def release_type
135
+ if has_mobileprovision?
136
+ if devices
137
+ 'adhoc'
138
+ else
139
+ 'inhouse'
140
+ end
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def get_image name
147
+ path = File.join(@path, "#{name}.png")
148
+ return nil unless File.exist?(path)
149
+ path
150
+ end
151
+ end
152
+ end
153
+
154
+ class Apk
155
+ end
156
+ end
157
+ end