fastlane-plugin-facelift 0.1.0

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.
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: []