apprepo 0.0.2 → 0.0.4

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.
@@ -1,39 +1,35 @@
1
1
  module AppRepo
2
+ class Manifest
3
+ #
4
+ # Translated internal key names from Fastlane to AppRepo
5
+ #
2
6
 
3
- class Manifest
7
+ attr_accessor :appcode # AppRepo Internal Code
8
+ attr_accessor :filename # IPA file name
9
+ attr_accessor :bundle_identifier # app_identifier
10
+ attr_accessor :bundle_version # app_version
11
+ attr_accessor :title # app_name
12
+ attr_accessor :subtitle # app_description
13
+ attr_accessor :notify # will send push notification / slack
4
14
 
5
- #
6
- # Translated internal key names from Fastlane to AppRepo
7
- #
15
+ def initialize(appcode)
16
+ self.appcode = appcode
17
+ UI.message('Initializing "AppRepo:Manifest requies at least APPCODE :"' + self.appcode)
18
+ end
8
19
 
9
- attr_accessor :appcode # AppRepo Internal Code
10
- attr_accessor :filename # IPA file name
11
- attr_accessor :bundle_identifier # app_identifier
12
- attr_accessor :bundle_version # app_version
13
- attr_accessor :title # app_name
14
- attr_accessor :subtitle # app_description
15
- attr_accessor :notify # will send push notification / slack
16
-
17
- def initialize (appcode)
18
- self.appcode = appcode
19
- puts 'Initializing "AppRepo:Manifest requies at least APPCODE :"'+self.appcode
20
- end
21
-
22
- # Provide JSON serialized data
23
- def getJSON
20
+ # Provide JSON serialized data
21
+ def getJSON
24
22
  structure = {
25
- appcode: self.appcode,
26
- filename: self.filename,
27
- bundle_identifier: self.bundle_identifier,
28
- bundle_version: self.bundle_version,
29
- title: self.title,
30
- subtitle: self.subtitle,
31
- notify: self.notify
23
+ appcode: appcode,
24
+ filename: filename,
25
+ bundle_identifier: bundle_identifier,
26
+ bundle_version: bundle_version,
27
+ title: title,
28
+ subtitle: subtitle,
29
+ notify: notify
32
30
  }
33
-
31
+
34
32
  fputs structure
33
+ end
35
34
  end
36
-
37
- end
38
35
  end
39
-
@@ -0,0 +1,98 @@
1
+ require 'fastlane_core'
2
+
3
+ module AppRepo
4
+ class Options
5
+ def self.available_options
6
+ [
7
+ FastlaneCore::ConfigItem.new(key: :ipa,
8
+ short_option: '-i',
9
+ optional: true,
10
+ env_name: 'APPREPO_IPA_PATH',
11
+ description: 'Path to your ipa file',
12
+ default_value: Dir['*.ipa'].first,
13
+ verify_block: proc do |value|
14
+ UI.user_error!("Could not find ipa file at path '#{value}'") unless File.exist?(value)
15
+ UI.user_error!("'#{value}' doesn't seem to be an ipa file") unless value.end_with?('.ipa')
16
+ end,
17
+ conflicting_options: [:pkg],
18
+ conflict_block: proc do |value|
19
+ UI.user_error!("You can't use 'ipa' and '#{value.key}' options in one run.")
20
+ end),
21
+ FastlaneCore::ConfigItem.new(key: :app_identifier,
22
+ short_option: '-b',
23
+ optional: false,
24
+ env_name: 'APPREPO_APP_ID',
25
+ description: 'Your bundle identifier',
26
+ default_value: ""),
27
+ FastlaneCore::ConfigItem.new(key: :app_code,
28
+ short_option: '-c',
29
+ optional: true,
30
+ env_name: 'APPREPO_APPCODE',
31
+ description: 'APPCODE value for apprepo'),
32
+ FastlaneCore::ConfigItem.new(key: :repo_url,
33
+ short_option: '-r',
34
+ optional: false,
35
+ env_name: 'APPREPO_URL',
36
+ description: 'URL of your Apprepo server'),
37
+ FastlaneCore::ConfigItem.new(key: :repo_user,
38
+ short_option: '-u',
39
+ optional: false,
40
+ env_name: 'APPREPO_USER',
41
+ description: 'USER of your Apprepo server'),
42
+ FastlaneCore::ConfigItem.new(key: :repo_key,
43
+ short_option: '-k',
44
+ optional: false,
45
+ env_name: 'APPREPO_KEY',
46
+ description: 'RSA key for your Apprepo server'),
47
+ FastlaneCore::ConfigItem.new(key: :repo_description,
48
+ short_option: '-d',
49
+ optional: true,
50
+ env_name: 'APPREPO_DESCRIPTION',
51
+ description: 'Long description for your Apprepo server'),
52
+ FastlaneCore::ConfigItem.new(key: :metadata_path,
53
+ short_option: '-m',
54
+ description: 'Path to the folder containing the metadata files',
55
+ optional: true),
56
+ FastlaneCore::ConfigItem.new(key: :meta_title,
57
+ short_option: '-a',
58
+ description: 'Name of the app',
59
+ optional: true),
60
+ FastlaneCore::ConfigItem.new(key: :skip_binary_upload,
61
+ description: 'Skip uploading an ipa or pkg to iTunes Connect',
62
+ is_string: false,
63
+ default_value: false),
64
+ FastlaneCore::ConfigItem.new(key: :app_version,
65
+ short_option: '-z',
66
+ description: 'The version that should be edited or created',
67
+ optional: true),
68
+ FastlaneCore::ConfigItem.new(key: :skip_metadata,
69
+ description: "Don't upload the metadata (e.g. title, description), this will still upload screenshots",
70
+ is_string: false,
71
+ default_value: false),
72
+ FastlaneCore::ConfigItem.new(key: :notify,
73
+ description: "Notify AppRepo users on update",
74
+ is_string: false,
75
+ default_value: false),
76
+ FastlaneCore::ConfigItem.new(key: :build_number,
77
+ short_option: '-n',
78
+ description: 'If set the given build number (already uploaded to iTC) will be used instead of the current built one',
79
+ optional: true,
80
+ conflicting_options: [:ipa, :pkg],
81
+ conflict_block: proc do |value|
82
+ UI.user_error!("You can't use 'build_number' and '#{value.key}' options in one run.")
83
+ end),
84
+
85
+ # App Metadata
86
+ # Non Localised
87
+ FastlaneCore::ConfigItem.new(key: :app_icon,
88
+ description: 'Metadata: The path to the app icon',
89
+ optional: true,
90
+ short_option: '-l',
91
+ verify_block: proc do |value|
92
+ UI.user_error!("Could not find png file at path '#{value}'") unless File.exist?(value)
93
+ UI.user_error!("'#{value}' doesn't seem to be a png file") unless value.end_with?('.png')
94
+ end)
95
+ ]
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,77 @@
1
+ module AppRepo
2
+ class Runner
3
+ attr_accessor :options
4
+
5
+ def initialize(options)
6
+ UI.message('[AppRepo:Runner] Initializing...')
7
+ self.options = options
8
+ #login
9
+ AppRepo::DetectValues.new.run!(self.options)
10
+ #FastlaneCore::PrintTable.print_values(config: options, hide_keys: [:app], mask_keys: ['app_review_information.demo_password'], title: "deliver #{AppRepo::VERSION} Summary")
11
+ end
12
+
13
+ def login
14
+ #UI.message("Login to AppRepo (#{options[:username]})")
15
+ #AppRepo::Server.login(options[:username])
16
+ #AppRepo::Server.select_app
17
+ #UI.message('Login successful')
18
+ end
19
+
20
+ def run
21
+ verify_version if options[:app_version].to_s.length > 0
22
+ upload_metadata
23
+
24
+ has_binary = (options[:ipa] || options[:pkg])
25
+ if !options[:skip_binary_upload] && !options[:build_number] && has_binary
26
+ upload_binary
27
+ end
28
+
29
+ UI.success('Finished the upload to AppRepo')
30
+
31
+ notify if options[:notify]
32
+ end
33
+
34
+ # Make sure the version on AppRepo matches the one in the ipa
35
+ # If not, the new version will automatically be created
36
+ def verify_version
37
+ app_version = options[:app_version]
38
+ UI.message("TODO: Make sure the latest version on AppRepo matches '#{app_version}' from the ipa file...")
39
+
40
+ #changed = options[:app].ensure_version!(app_version)
41
+ #if changed
42
+ # UI.success("Successfully set the version to '#{app_version}'")
43
+ #else
44
+ # UI.success("'#{app_version}' is the latest version on iTunes Connect")
45
+ #end
46
+ end
47
+
48
+ # Upload all metadata, screenshots, pricing information, etc. to iTunes Connect
49
+ def upload_metadata
50
+ #
51
+ end
52
+
53
+ # Upload the binary to iTunes Connect
54
+ def upload_binary
55
+ UI.message('Uploading binary to iTunes Connect')
56
+ if options[:ipa]
57
+ #package_path = FastlaneCore::IpaUploadPackageBuilder.new.generate(
58
+ # app_id: options[:app].apple_id,
59
+ # ipa_path: options[:ipa],
60
+ # package_path: '/tmp'
61
+ puts "TODO: Build package path without IpaUploadPackageBuilder"
62
+
63
+ end
64
+
65
+ #transporter = FastlaneCore::ItunesTransporter.new(options[:username])
66
+ #result = transporter.upload(options[:app].apple_id, package_path)
67
+ #UI.user_error!('Could not upload binary to iTunes Connect. Check out the error above') unless result
68
+ end
69
+
70
+ def notify
71
+ # should be in metadata
72
+ end
73
+
74
+ private
75
+
76
+ end
77
+ end
@@ -0,0 +1,49 @@
1
+ module AppRepo
2
+ class Setup
3
+
4
+ def setup_apprepo(file_path, data, _apprepo_path, _options)
5
+ UI.message('[AppRepo:Setup] Setting up...')
6
+ File.write(file_path, data)
7
+
8
+ # TODO: implement later
9
+ # download_metadata(apprepo_path, options)
10
+
11
+ UI.success("NOT! created new Repofile at path '#{file_path}'")
12
+ end
13
+
14
+ # This method takes care of creating a new 'apprepo' folder, containg the app metadata
15
+ # and screenshots folders
16
+ def generate_apprepo_file(apprepo_path, options)
17
+ #
18
+ #v = options[:app].latest_version
19
+ #generate_metadata_files(v, File.join(apprepo_path, 'metadata'))
20
+
21
+ # Generate the final Repofile here
22
+ gem_path = Helper.gem_path('apprepo')
23
+ apprepo = File.read("#{gem_path}/../assets/RepofileDefault")
24
+ apprepo.gsub!("[[APP_IDENTIFIER]]", options[:app].bundle_id)
25
+ apprepo.gsub!("[[APPREPO_IPA_PATH]]", options[:app].file_path)
26
+ apprepo.gsub!("[[APP_VERSION]]", options[:app].version)
27
+ apprepo.gsub!("[[APP_NAME]]", options[:app].name)
28
+ UI.user_error("TODO: ADJUST Repofile'")
29
+ # deliver => apprepo??
30
+ end
31
+
32
+ def download_metadata(apprepo_path, _options)
33
+ path = File.join(apprepo_path, 'metadata')
34
+ FileUtils.mkdir_p(path)
35
+ UI.success("TODO: DOWNLOAD METADATA'")
36
+ # AppRepo::DownloadManifest.run(options, path)
37
+ end
38
+
39
+ def run(options)
40
+ UI.message('[AppRepo:Setup] Running...')
41
+ containing = (File.directory?('fastlane') ? 'fastlane' : '.')
42
+ file_path = File.join(containing, 'Repofile')
43
+ data = generate_apprepo_file(containing, options)
44
+ setup_apprepo(file_path, data, containing, options)
45
+ end
46
+ end
47
+ end
48
+
49
+ # @setup = new AppRepo::Setup
@@ -0,0 +1,22 @@
1
+ module AppRepo
2
+ class UploadAssets
3
+ def upload(options)
4
+ app = options[:app]
5
+
6
+ v = app.edit_version
7
+ UI.user_error!("Could not find a version to edit for app '#{app.name}'") unless v
8
+
9
+ if options[:app_icon]
10
+ UI.message('Uploading app icon...')
11
+ v.upload_large_icon!(options[:app_icon])
12
+ end
13
+
14
+ if options[:apple_watch_app_icon]
15
+ UI.message('Uploading apple watchapp icon...')
16
+ v.upload_watch_icon!(options[:apple_watch_app_icon])
17
+ end
18
+
19
+ v.save!
20
+ end
21
+ end
22
+ end
@@ -1,14 +1,13 @@
1
1
  module AppRepo
2
+ class UploadDescriptor
2
3
 
3
- class UploadDescriptor
4
+ attr_accessor :appcode # required
5
+ attr_accessor :ipa # can be inferred anyway (glob or metadata)
6
+ attr_accessor :metadata # optional, allows re-uploading same binary without metadata change
4
7
 
5
- attr_accessor :appcode
6
-
7
- def initialize (appcode)
8
- self.appcode = appcode
9
- puts 'Initializing "AppRepo:UploadDescriptor with appcode "'+self.appcode
8
+ def initialize(appcode)
9
+ self.appcode = appcode
10
+ UI.message('Initializing "AppRepo:UploadDescriptor with appcode "' + self.appcode + '"')
11
+ end
10
12
  end
11
13
  end
12
-
13
- end
14
-
@@ -0,0 +1,192 @@
1
+ module AppRepo
2
+ # upload description, rating, etc.
3
+ class UploadMetadata
4
+ # All the localised values attached to the version
5
+ LOCALISED_VERSION_VALUES = [:description, :keywords, :release_notes, :support_url, :marketing_url].freeze
6
+
7
+ # Everything attached to the version but not being localised
8
+ NON_LOCALISED_VERSION_VALUES = [:copyright].freeze
9
+
10
+ # Localised app details values
11
+ LOCALISED_APP_VALUES = [:name, :privacy_url].freeze
12
+
13
+ # Non localized app details values
14
+ NON_LOCALISED_APP_VALUES = [:primary_category, :secondary_category,
15
+ :primary_first_sub_category, :primary_second_sub_category,
16
+ :secondary_first_sub_category, :secondary_second_sub_category
17
+ ].freeze
18
+
19
+ # Make sure to call `load_from_filesystem` before calling upload
20
+ def upload(options)
21
+ return if options[:skip_metadata]
22
+ verify_available_languages!(options)
23
+
24
+ app = options[:app]
25
+
26
+ details = app.details
27
+ v = app.edit_version
28
+
29
+ (LOCALISED_VERSION_VALUES + LOCALISED_APP_VALUES).each do |key|
30
+ current = options[key]
31
+ next unless current
32
+
33
+ unless current.is_a?(Hash)
34
+ UI.error("Error with provided '#{key}'. Must be a hash, the key being the language.")
35
+ next
36
+ end
37
+
38
+ current.each do |language, value|
39
+ next unless value.to_s.length > 0
40
+ strip_value = value.to_s.strip
41
+ v.send(key)[language] = strip_value if LOCALISED_VERSION_VALUES.include?(key)
42
+ details.send(key)[language] = strip_value if LOCALISED_APP_VALUES.include?(key)
43
+ end
44
+ end
45
+
46
+ (NON_LOCALISED_VERSION_VALUES + NON_LOCALISED_APP_VALUES).each do |key|
47
+ current = options[key].to_s.strip
48
+ next unless current.to_s.length > 0
49
+ v.send("#{key}=", current) if NON_LOCALISED_VERSION_VALUES.include?(key)
50
+ details.send("#{key}=", current) if NON_LOCALISED_APP_VALUES.include?(key)
51
+ end
52
+
53
+ v.release_on_approval = options[:automatic_release]
54
+
55
+ set_review_information(v, options)
56
+ set_app_rating(v, options)
57
+
58
+ UI.message('Uploading metadata to iTunes Connect')
59
+ v.save!
60
+ details.save!
61
+ UI.success('Successfully uploaded initial set of metadata to iTunes Connect')
62
+ end
63
+
64
+ # If the user is using the 'default' language, then assign values where they are needed
65
+ def assign_defaults(options)
66
+ # Build a complete list of the required languages
67
+ enabled_languages = []
68
+
69
+ # Get all languages used in existing settings
70
+ (LOCALISED_VERSION_VALUES + LOCALISED_APP_VALUES).each do |key|
71
+ current = options[key]
72
+ next unless current && current.is_a?(Hash)
73
+ current.each do |language, _value|
74
+ enabled_languages << language unless enabled_languages.include?(language)
75
+ end
76
+ end
77
+
78
+ # Check folder list (an empty folder signifies a language is required)
79
+ Dir.glob(File.join(options[:metadata_path], '*')).each do |lng_folder|
80
+ next unless File.directory?(lng_folder) # We don't want to read txt as they are non localised
81
+
82
+ language = File.basename(lng_folder)
83
+ enabled_languages << language unless enabled_languages.include?(language)
84
+ end
85
+
86
+ return unless enabled_languages.include?('default')
87
+ UI.message('Detected languages: ' + enabled_languages.to_s)
88
+
89
+ (LOCALISED_VERSION_VALUES + LOCALISED_APP_VALUES).each do |key|
90
+ current = options[key]
91
+ next unless current && current.is_a?(Hash)
92
+
93
+ default = current['default']
94
+ next if default.nil?
95
+
96
+ enabled_languages.each do |language|
97
+ value = current[language]
98
+ next unless value.nil?
99
+
100
+ current[language] = default
101
+ end
102
+ current.delete('default')
103
+ end
104
+ end
105
+
106
+ # Makes sure all languages we need are actually created
107
+ def verify_available_languages!(options)
108
+ return if options[:skip_metadata]
109
+
110
+ # Collect all languages we need
111
+ # We only care about languages from user provided values
112
+ # as the other languages are on iTC already anyway
113
+ v = options[:app].edit_version
114
+ UI.user_error!("Could not find a version to edit for app '#{options[:app].name}', the app metadata is read-only currently") unless v
115
+
116
+ enabled_languages = []
117
+ LOCALISED_VERSION_VALUES.each do |key|
118
+ current = options[key]
119
+ next unless current && current.is_a?(Hash)
120
+ current.each do |language, _value|
121
+ enabled_languages << language unless enabled_languages.include?(language)
122
+ end
123
+ end
124
+
125
+ if enabled_languages.count > 0
126
+ v.create_languages(enabled_languages)
127
+ lng_text = 'language'
128
+ lng_text += 's' if enabled_languages.count != 1
129
+ UI.message("Activating #{lng_text} #{enabled_languages.join(', ')}...")
130
+ v.save!
131
+ end
132
+ true
133
+ end
134
+
135
+ # Loads the metadata files and stores them into the options object
136
+ def load_from_filesystem(options)
137
+ return if options[:skip_metadata]
138
+
139
+ # Load localised data
140
+ Loader.language_folders(options[:metadata_path]).each do |lng_folder|
141
+ language = File.basename(lng_folder)
142
+ (LOCALISED_VERSION_VALUES + LOCALISED_APP_VALUES).each do |key|
143
+ path = File.join(lng_folder, "#{key}.txt")
144
+ next unless File.exist?(path)
145
+
146
+ UI.message("Loading '#{path}'...")
147
+ options[key] ||= {}
148
+ options[key][language] ||= File.read(path)
149
+ end
150
+ end
151
+
152
+ # Load non localised data
153
+ (NON_LOCALISED_VERSION_VALUES + NON_LOCALISED_APP_VALUES).each do |key|
154
+ path = File.join(options[:metadata_path], "#{key}.txt")
155
+ next unless File.exist?(path)
156
+
157
+ UI.message("Loading '#{path}'...")
158
+ options[key] ||= File.read(path)
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ def set_review_information(v, options)
165
+ return unless options[:app_review_information]
166
+ info = options[:app_review_information]
167
+ UI.user_error!('`app_review_information` must be a hash') unless info.is_a?(Hash)
168
+
169
+ v.review_first_name = info[:first_name] if info[:first_name]
170
+ v.review_last_name = info[:last_name] if info[:last_name]
171
+ v.review_phone_number = info[:phone_number] if info[:phone_number]
172
+ v.review_email = info[:email_address] if info[:email_address]
173
+ v.review_demo_user = info[:demo_user] if info[:demo_user]
174
+ v.review_demo_password = info[:demo_password] if info[:demo_password]
175
+ v.review_user_needed = (v.review_demo_user.to_s + v.review_demo_password.to_s).length > 0
176
+ v.review_notes = info[:notes] if info[:notes]
177
+ end
178
+
179
+ def set_app_rating(v, options)
180
+ return unless options[:app_rating_config_path]
181
+
182
+ require 'json'
183
+ begin
184
+ json = JSON.parse(File.read(options[:app_rating_config_path]))
185
+ rescue => ex
186
+ UI.error(ex.to_s)
187
+ UI.user_error!("Error parsing JSON file at path '#{options[:app_rating_config_path]}'")
188
+ end
189
+ v.update_rating(json)
190
+ end
191
+ end
192
+ end