fastlane-plugin-facelift 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5d8f24a65c6678e724bdfcdb47995b28558b63a8
4
+ data.tar.gz: 6b0bbb3b208b753e9b58cab4ddf3f474db3af6ab
5
+ SHA512:
6
+ metadata.gz: f59f28ba0251f899695bfc019f26b633bc67c24fe3c77eba26162364d61619b792cd85c73f282ab11d413fe8639609367fc32eb926630b36fec14287487c63f0
7
+ data.tar.gz: cab9dd45d3207d78a1ef0a91a7ae9a709e895964645aa6e42db096cf1b24d86cdc6e6e97546574e5adfd5862593523143ec90c5b0c04acdfc1b06827d9912970
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Richard Szalay <richard.szalay@mullenloweprofero.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # facelift plugin
2
+
3
+ [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-facelift)
4
+
5
+ ## Getting Started
6
+
7
+ This project is a [fastlane](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-facelift`, add it to your project by running:
8
+
9
+ ```bash
10
+ fastlane add_plugin facelift
11
+ ```
12
+
13
+ ## About facelift
14
+
15
+ Applies changes to plists and app icons inside a compiled IPA, combined with sigh's `resign` it makes it easy to release an IPA with different configurations 🎭
16
+
17
+ ## Example
18
+
19
+ Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plugin. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
20
+
21
+ Here's some usage scenarios:
22
+
23
+ ```ruby
24
+ # Modify Info.plist
25
+ facelift(
26
+ ipa: "example/Example.ipa",
27
+
28
+ iconset: "example/Blue.appiconset",
29
+
30
+ # Set a hash of plist values
31
+ plist_values: {
32
+ ":CustomApplicationKey" => "Replaced!"
33
+ },
34
+
35
+ # Run a list of PlistBuddy commands
36
+ plist_commands: [
37
+ "Delete :DebugApplicationKey"
38
+ ]
39
+ )
40
+
41
+ # Modify a different plist
42
+ facelift(
43
+ ipa: "example/Example.ipa",
44
+ plist_file: "GoogleService-Info.plist",
45
+
46
+ plist_values: {
47
+ ":TRACKING_ID" => "UA-22222222-22"
48
+ }
49
+ )
50
+ ```
51
+
52
+ ## Run tests for this plugin
53
+
54
+ To run both the tests, and code style validation, run
55
+
56
+ ```
57
+ rake
58
+ ```
59
+
60
+ To automatically fix many of the styling issues, use
61
+ ```
62
+ rubocop -a
63
+ ```
64
+
65
+ ## Issues and Feedback
66
+
67
+ For any other issues and feedback about this plugin, please submit it to this repository.
68
+
69
+ ## Troubleshooting
70
+
71
+ If you have trouble using plugins, check out the [Plugins Troubleshooting](https://github.com/fastlane/fastlane/blob/master/fastlane/docs/PluginsTroubleshooting.md) doc in the main `fastlane` repo.
72
+
73
+ ## Using `fastlane` Plugins
74
+
75
+ For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Plugins.md).
76
+
77
+ ## About `fastlane`
78
+
79
+ `fastlane` is the easiest way to automate building and releasing your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
@@ -0,0 +1,16 @@
1
+ require 'fastlane/plugin/facelift/version'
2
+
3
+ module Fastlane
4
+ module Facelift
5
+ # Return all .rb files inside the "actions" and "helper" directory
6
+ def self.all_classes
7
+ Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
8
+ end
9
+ end
10
+ end
11
+
12
+ # By default we want to import all available actions and helpers
13
+ # A plugin can contain any number of actions and plugins
14
+ Fastlane::Facelift.all_classes.each do |current|
15
+ require current
16
+ end
@@ -0,0 +1,106 @@
1
+ module Fastlane
2
+ module Actions
3
+ class FaceliftAction < Action
4
+ def self.run(params)
5
+ params[:ipa] = File.expand_path params[:ipa]
6
+ raise "IPA #{params[:ipa]} does not exist" unless File.exist? params[:ipa]
7
+
8
+ params[:app_name] = (File.basename params[:ipa], ".*") + ".app" unless params[:app_name]
9
+
10
+ create_temp_dir = params[:temp_dir].nil?
11
+ params[:temp_dir] = Dir.mktmpdir if create_temp_dir
12
+ UI.verbose("Working in temp dir: #{params[:temp_dir]}")
13
+
14
+ archive = Helper::IPAArchive.new params[:ipa], params[:app_name], params[:temp_dir]
15
+
16
+ raise "IPA does not contain Payload/#{params[:app_name]}. Rename the .ipa to match the .app, or provide an app_name option value" unless archive.contains
17
+
18
+ params[:plist_file] = "Info.plist" unless params[:plist_file]
19
+
20
+ Helper::PlistPatcher.patch(
21
+ archive,
22
+ params[:plist_file],
23
+ params[:plist_values],
24
+ params[:plist_commands]
25
+ ) if params[:plist_values] or params[:plist_commands]
26
+
27
+ Helper::IconPatcher.patch(
28
+ archive,
29
+ params[:iconset],
30
+ !params[:skip_delete_icons]
31
+ ) if params[:iconset]
32
+
33
+ if create_temp_dir
34
+ UI.verbose("Removing temp dir")
35
+ `rm -rf #{params[:temp_dir]}`
36
+ end
37
+ end
38
+
39
+ def self.description
40
+ "Reconfigures .plists and icons inside a compiled IPA"
41
+ end
42
+
43
+ def self.authors
44
+ ["Richard Szalay"]
45
+ end
46
+
47
+ def self.available_options
48
+ [
49
+ FastlaneCore::ConfigItem.new(key: :ipa,
50
+ env_name: "FACELIFT_IPA",
51
+ description: "Path of the IPA file being modified",
52
+ optional: false,
53
+ type: String),
54
+
55
+ FastlaneCore::ConfigItem.new(key: :iconset,
56
+ description: "Path to iconset to swap into the IPA (ignores :plist option)",
57
+ optional: true,
58
+ type: String),
59
+
60
+ FastlaneCore::ConfigItem.new(key: :plist_file,
61
+ env_name: "FACELIFT_PLIST_FILE",
62
+ description: "The name of the plist file to modify, relative to the .app bundle`",
63
+ optional: true,
64
+ default_value: "Info.plist",
65
+ type: String),
66
+
67
+ FastlaneCore::ConfigItem.new(key: :plist_values,
68
+ description: "Hash of plist values to set to the plist file",
69
+ optional: true,
70
+ type: Hash),
71
+
72
+ FastlaneCore::ConfigItem.new(key: :plist_commands,
73
+ description: "Array of PlistBuddy commands to invoke",
74
+ optional: true,
75
+ type: Array),
76
+
77
+ # TODO: :force flag for ignoring command errors and auto-adding plist_values if non-existant
78
+
79
+ # Very optional
80
+ FastlaneCore::ConfigItem.new(key: :app_name,
81
+ env_name: "FACELIFT_APP_NAME",
82
+ description: "The name of the .app file (including extension), if not the same as the IPA",
83
+ optional: true,
84
+ type: String),
85
+
86
+ FastlaneCore::ConfigItem.new(key: :temp_dir,
87
+ env_name: "FACELIFT_TEMP_DIR",
88
+ description: "The temporary directory to work from. One will be created if not supplied",
89
+ optional: true,
90
+ type: String),
91
+
92
+ FastlaneCore::ConfigItem.new(key: :skip_delete_icons,
93
+ env_name: "FACELIFT_SKIP_DELETE_ICONS",
94
+ description: "When true, the old icon files will not be deleted from the IPA",
95
+ optional: true,
96
+ default_value: false,
97
+ type: [TrueClass, FalseClass])
98
+ ]
99
+ end
100
+
101
+ def self.is_supported?(platform)
102
+ [:ios].include?(platform)
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,12 @@
1
+ module Fastlane
2
+ module Helper
3
+ class FaceliftHelper
4
+ # class methods that you define here become available in your action
5
+ # as `Helper::FaceliftHelper.your_method`
6
+ #
7
+ def self.show_message
8
+ UI.message("Hello from the facelift plugin helper!")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,91 @@
1
+ module Fastlane
2
+ module Helper
3
+ class IconPatcher
4
+ def self.patch(archive, iconset_path, delete_old_iconset)
5
+ plist_path = "Info.plist"
6
+ archive.extract(plist_path)
7
+
8
+ UI.message("Patching icons from: #{iconset_path}")
9
+
10
+ plist_buddy = PlistBuddy.new archive.local_path(plist_path)
11
+
12
+ self.delete_icons(archive, plist_buddy, delete_old_iconset)
13
+
14
+ icon_list = get_icons_from_iconset(iconset_path)
15
+ icon_list.group_by { |i| i[:idiom] }.each do |idiom, icons|
16
+ idiom_suffix = idiom == "iphone" ? "" : "~#{idiom}"
17
+ icons_plist_key = ":CFBundleIcons#{idiom_suffix}:CFBundlePrimaryIcon:CFBundleIconFiles"
18
+
19
+ plist_buddy.exec("Add #{icons_plist_key} array")
20
+
21
+ icons.each do |i|
22
+ relative_path = (i[:target]).to_s
23
+ local_path = archive.local_path(relative_path)
24
+ `cp #{i[:source]} #{local_path}`
25
+ archive.replace(relative_path)
26
+ end
27
+
28
+ icons.map { |i| i[:name] }.uniq.each_with_index do |key, index|
29
+ plist_buddy.exec("Add #{icons_plist_key}:#{index} string #{key}")
30
+ end
31
+ end
32
+
33
+ archive.replace(plist_path)
34
+ end
35
+
36
+ def self.get_icons_from_iconset(icon_set_path)
37
+ icon_set = File.basename(icon_set_path, ".*")
38
+
39
+ icon_set_manifest_file = File.expand_path "#{icon_set_path}/Contents.json"
40
+ raise ".iconset manifest #{icon_set_manifest_file} does not exist" unless File.exist? icon_set_manifest_file
41
+
42
+ icon_set_manifest = JSON.parse(File.read(icon_set_manifest_file))
43
+
44
+ return icon_set_manifest["images"].map do |entry|
45
+ scale_suffix = entry['scale'] == '1x' ? '' : "@" + entry['scale']
46
+ idiom_suffix = entry['idiom'] == "iphone" ? '' : "~" + entry['idiom']
47
+ file_extension = File.extname(entry['filename'])
48
+
49
+ {
50
+ source: "#{icon_set_path}/#{entry['filename']}",
51
+ name: "#{icon_set}#{entry['size']}",
52
+ idiom: entry['idiom'],
53
+ target: "#{icon_set}#{entry['size']}#{scale_suffix}#{idiom_suffix}#{file_extension}"
54
+ }
55
+ end
56
+ end
57
+
58
+ def self.delete_icons(archive, plist_buddy, delete_old_iconset)
59
+ existing_icon_set_keys = plist_buddy.parse_dict_keys(plist_buddy.exec("Print"))
60
+ .map { |k| k.match(/^CFBundleIcons(~.+)?$/) }
61
+ .select { |m| m }
62
+
63
+ existing_icon_set_keys.each do |match|
64
+ key = match[0]
65
+ idiom_suffix = match[1]
66
+
67
+ icon_list_key = ":#{key}:CFBundlePrimaryIcon:CFBundleIconFiles"
68
+
69
+ begin
70
+ icon_files_value = plist_buddy.exec "Print #{icon_list_key}"
71
+ rescue
72
+ next
73
+ end
74
+
75
+ existing_icons = plist_buddy.parse_scalar_array(icon_files_value)
76
+
77
+ if existing_icons.size && delete_old_iconset
78
+ icons_to_delete = existing_icons.map { |name| "#{name}#{idiom_suffix}*" }
79
+
80
+ icons_to_delete.each do |icon_to_delete|
81
+ archive.delete icon_to_delete
82
+ end
83
+
84
+ end
85
+
86
+ plist_buddy.exec "Delete #{icon_list_key}"
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,55 @@
1
+ module Fastlane
2
+ module Helper
3
+ class IPAArchive
4
+ def initialize(ipa_file, app_name, temp_dir)
5
+ @ipa_file = ipa_file
6
+ @app_path = "Payload/#{app_name}"
7
+ @temp_dir = temp_dir
8
+ end
9
+
10
+ # Returns the full path to the given file within the temp dir
11
+ def local_path(path)
12
+ "#{@temp_dir}/#{@app_path}/#{path}"
13
+ end
14
+
15
+ # Extract files to the temp dir
16
+ def extract(path)
17
+ UI.verbose("Extracting #{@app_path}/#{path}")
18
+
19
+ Dir.chdir(@temp_dir) do
20
+ result = `unzip -o -q #{@ipa_file} #{@app_path}/#{path}`
21
+
22
+ if $?.exitstatus.nonzero?
23
+ UI.important result
24
+ raise "extract operation failed with exit code #{$?.exitstatus}"
25
+ end
26
+ end
27
+ end
28
+
29
+ # Restore extracted files from the temp dir
30
+ def replace(path)
31
+ UI.verbose("Replacing #{@app_path}/#{path}")
32
+ Dir.chdir(@temp_dir) do
33
+ `zip -q #{@ipa_file} #{@app_path}/#{path}`
34
+ end
35
+ end
36
+
37
+ # Delete path inside the ipa
38
+ def delete(path)
39
+ UI.verbose("Deleting #{@app_path}/#{path}")
40
+ Dir.chdir(@temp_dir) do
41
+ `zip -dq #{@ipa_file} #{@app_path}/#{path}`
42
+ end
43
+ end
44
+
45
+ def contains(path = nil)
46
+ `zipinfo -1 #{@ipa_file} #{@app_path}/#{path}`
47
+ $?.exitstatus.zero?
48
+ end
49
+
50
+ def clean
51
+ `rm -rf #{temp_dir}/*`
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ module Fastlane
2
+ module Helper
3
+ class PlistBuddy
4
+ def initialize(plist_file)
5
+ @plist_file = plist_file
6
+ end
7
+
8
+ def exec(command)
9
+ UI.verbose("/usr/libexec/PlistBuddy -c \"#{command}\" \"#{@plist_file}\"")
10
+ result = `/usr/libexec/PlistBuddy -c "#{command}" "#{@plist_file}"`
11
+
12
+ if $?.exitstatus.nonzero?
13
+ UI.important "PlistBuddy command failed: #{result}"
14
+ raise "PlistBuddy command failed failed with exit code #{$?.exitstatus} - #{result}"
15
+ end
16
+
17
+ return result
18
+ end
19
+
20
+ def parse_scalar_array(result)
21
+ # This should probably use -x and parse the xml using Nokogiri
22
+
23
+ return [] unless result =~ /\S/
24
+
25
+ result_lines = result.lines.map(&:chop)
26
+
27
+ raise "value is not an array" unless result_lines.first == "Array {"
28
+
29
+ array_values = result_lines.drop(1).take(result_lines.size - 2)
30
+
31
+ return array_values.map { |line| line[4..line.size] }
32
+ end
33
+
34
+ def parse_dict_keys(entry)
35
+ # This should probably use -x and parse the xml using Nokogiri
36
+
37
+ result_lines = entry.lines.map(&:chop)
38
+
39
+ raise "value is not an dict" unless result_lines.first == "Dict {"
40
+
41
+ keys = result_lines
42
+ .map { |l| l.match(/^\s{4}([^\s}]+)/) }
43
+ .select { |l| l }
44
+ .map { |l| l[1] }
45
+
46
+ return keys
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ module Fastlane
2
+ module Helper
3
+ class PlistPatcher
4
+ def self.patch(archive, plist_path, values, commands)
5
+ archive.extract(plist_path)
6
+
7
+ UI.message("Patching Plist: #{plist_path}")
8
+
9
+ plist_buddy = PlistBuddy.new archive.local_path(plist_path)
10
+
11
+ values.each do |key, value|
12
+ plist_buddy.exec "Set #{key} #{value}"
13
+ end if values
14
+
15
+ commands.each do |command|
16
+ plist_buddy.exec command
17
+ end if commands
18
+
19
+ archive.replace(plist_path)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ module Fastlane
2
+ module Facelift
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-facelift
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Richard Szalay
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: fastlane
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: 1.99.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: 1.99.0
97
+ description:
98
+ email: richard@richardszalay.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - LICENSE
104
+ - README.md
105
+ - lib/fastlane/plugin/facelift.rb
106
+ - lib/fastlane/plugin/facelift/actions/facelift_action.rb
107
+ - lib/fastlane/plugin/facelift/helper/facelift_helper.rb
108
+ - lib/fastlane/plugin/facelift/helper/icon_patcher.rb
109
+ - lib/fastlane/plugin/facelift/helper/ipa_archive.rb
110
+ - lib/fastlane/plugin/facelift/helper/plist_buddy.rb
111
+ - lib/fastlane/plugin/facelift/helper/plist_patcher.rb
112
+ - lib/fastlane/plugin/facelift/version.rb
113
+ homepage: https://github.com/richardszalay/fastlane-plugin-facelift
114
+ licenses:
115
+ - MIT
116
+ metadata: {}
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 2.4.8
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: Applies changes to plists and app icons inside a compiled IPA
137
+ test_files: []