Compare commits
61
Commits
d4a16eba70
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
897eb84042 | ||
|
|
61732adf9e | ||
|
|
e11b2fb58c | ||
|
|
3a411fe576 | ||
|
|
c0f125a23c | ||
|
|
47bfb28e26 | ||
|
|
5d91f56899 | ||
|
|
f05cdfba4f | ||
|
|
737ec11503 | ||
|
|
7155b7818c | ||
|
|
71299744da | ||
|
|
fea0c153d4 | ||
|
|
9e56b4127e
|
||
|
|
4cca8ef739 | ||
|
|
bc848e09ec | ||
|
|
53e512462b | ||
|
|
b307d8110a | ||
|
|
1ef8fc00e7 | ||
|
|
fef6c75738 | ||
|
|
4021345e0a | ||
|
|
289802d9bc | ||
|
|
385e7c05a9 | ||
|
|
f1141a78ad | ||
|
|
7cd4ac4238 | ||
|
|
ca2efae3c7 | ||
|
|
1062d3d9e2 | ||
|
|
25209ab76b | ||
|
|
522a0a0bf9 | ||
|
|
3c0d2b40cf | ||
|
|
d86c49d7ba | ||
|
|
6b7bc14451 | ||
|
|
a4f0010170 | ||
|
|
76723e2185 | ||
|
|
0207a311b5 | ||
|
|
37cbf76fc9 | ||
|
|
83216cbbbb | ||
|
|
68bf5a1502 | ||
|
|
e003011b9a | ||
|
|
b67a267ca5 | ||
|
|
1c6b04e511 | ||
|
|
6e7befa067 | ||
|
|
9e685cae0e | ||
|
|
04e06cf1a7 | ||
|
|
fa9de2881f | ||
|
|
296d7181ca | ||
|
|
a43966d6f8 | ||
|
|
dbc8c0fed6 | ||
|
|
24a09b44c4 | ||
|
|
502c7b06e4 | ||
|
|
74d3527b40 | ||
|
|
d88276a0ec | ||
|
|
ec414c9864 | ||
|
|
cc2cd8ea68 | ||
|
|
1759498e23 | ||
|
|
f66ca731f8 | ||
|
|
89732f1f3f | ||
|
|
942f3afbf9 | ||
|
|
87a5b0f809 | ||
|
|
0a004a595c | ||
|
|
155d048c18 | ||
|
|
ee58f96126 |
Vendored
BIN
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-android_cd`, add the dependencies into your `PluginFile` :
|
||||
This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-android_cd` , add the dependencies into your `PluginFile` :
|
||||
|
||||
```ruby
|
||||
gem "fastlane-plugin-android_cd", git: "https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-fastlane-plugins.git", branch: "main", glob: 'plugins/fastlane-plugin-android_cd/*.gemspec'
|
||||
@@ -22,7 +22,7 @@ To use these actions, you need to add some information to your Android folder so
|
||||
|
||||
In your android folder, place a compressed folder containing:
|
||||
|
||||
- The application's signing key (in the .jks format). If this is a new application, you can generate this key with the following command:
|
||||
* The application's signing key (in the .jks format). If this is a new application, you can generate this key with the following command:
|
||||
|
||||
```shel
|
||||
keytool -genkey -v -keystore key_store_name.keystore -alias key_alias_name -keyalg RSA -keysize 2048 -validity 10000
|
||||
@@ -30,29 +30,39 @@ keytool -genkey -v -keystore key_store_name.keystore -alias key_alias_name -keya
|
||||
|
||||
During the execution of the above command, you will be prompted to provide a password for your key. Make sure to remember this password, as you will need it later.
|
||||
|
||||
- The key.properties file containing sensitive data for using the signing key, such as the path of the key or the password. If this file does not exist yet, create it and fill in the following fields:
|
||||
* The key.properties file containing sensitive data for using the signing key, such as the path of the key or the password. If this file does not exist yet, create it and fill in the following fields:
|
||||
|
||||
- `storeFile`: the relative or absolute path to the key storage file that contains the private key used to sign the Android application.
|
||||
- `storePassword`: the password used to access the key storage file.
|
||||
- `keyAlias`: the alias of the key used to sign the application.
|
||||
- `keyPassword`: the password used to access the key.
|
||||
+ `storeFile`: the relative or absolute path to the key storage file that contains the private key used to sign the Android application.
|
||||
+ `storePassword`: the password used to access the key storage file.
|
||||
+ `keyAlias`: the alias of the key used to sign the application.
|
||||
+ `keyPassword`: the password used to access the key.
|
||||
|
||||
- The credentials in JSON format to allow the fastlane action to connect to the Google Play Store console and upload the new build. To retrieve them, follow these steps:
|
||||
* The credentials in JSON format to allow the fastlane action to connect to the Google Play Store console and upload the new build. To retrieve them, follow these steps:
|
||||
|
||||
- Go to your Google Play Store console.
|
||||
- In the Settings menu, select `API access`, then click `Create Service Account`
|
||||
- Navigate to the provided Google Developers Console link in the dialog
|
||||
- Click `Create Service Account` at the top of the Google Developers Console
|
||||
- Provide the required details, then click `Create`
|
||||
- Click `Select a role`, select `Service Accounts`, then click `Service Account User`
|
||||
- In the Service Accounts dashboard, navigate to the Actions column, tap the menu for the service account that you created, then click `Create Key`
|
||||
- Select JSON as the key type, then click `Save`
|
||||
- Back on the Google Play Console, click `Done` to close the dialog
|
||||
- Click on `Grant Access` for the newly added service account
|
||||
- Make sure that the role of this service account has the permission to upload builds
|
||||
- Click `Add User` to close the dialog
|
||||
+ Go to your Google Play Store console.
|
||||
+ In the Settings menu, select `API access`, then click `Create Service Account`
|
||||
+ Navigate to the provided Google Developers Console link in the dialog
|
||||
+ Click `Create Service Account` at the top of the Google Developers Console
|
||||
+ Provide the required details, then click `Create`
|
||||
+ Click `Select a role`, select `Service Accounts`, then click `Service Account User`
|
||||
+ In the Service Accounts dashboard, navigate to the Actions column, tap the menu for the service account that you created, then click `Create Key`
|
||||
+ Select JSON as the key type, then click `Save`
|
||||
+ Back on the Google Play Console, click `Done` to close the dialog
|
||||
+ Click on `Grant Access` for the newly added service account
|
||||
+ Make sure that the role of this service account has the permission to upload builds
|
||||
+ Click `Add User` to close the dialog
|
||||
|
||||
Once you have all three elements in your folder, it's time to encrypt them. Indeed, this folder contains elements that are too sensitive to be referenced on git. Compress the folder, place the compressed folder in the android folder of your Flutter project, and execute the following command:
|
||||
Once you have all three elements in your folder, it's time to encrypt them. Indeed, this folder contains elements that are too sensitive to be referenced on git.
|
||||
|
||||
Place them in a folder named `android_keys` and compress it using the following command:
|
||||
|
||||
```shell
|
||||
jar cfvM android_keys.zip android_keys
|
||||
```
|
||||
|
||||
> The plugin will use `jar xvf android/android_keys.zip && mv android/android_keys/* android/` to extract the secret files.
|
||||
|
||||
Then, encrypt the compressed folder using GPG.
|
||||
|
||||
```shell
|
||||
gpg --quiet --batch --yes --symmetric --passphrase="<android-key-passphrase>" --output android/android_keys.zip.gpg android/android_keys.zip
|
||||
@@ -74,7 +84,7 @@ build_and_deploy(beta_type: "internal")
|
||||
promote(from: "internal", destination: "beta")
|
||||
```
|
||||
|
||||
note that beta type can be `production`, `beta`, `alpha`, `internal`.
|
||||
note that beta type can be `production` , `beta` , `alpha` , `internal` .
|
||||
|
||||
## Run tests for this plugin
|
||||
|
||||
|
||||
+41
-26
@@ -7,42 +7,51 @@ module Fastlane
|
||||
def self.run(params)
|
||||
# Check parameters
|
||||
unless Helper::AndroidCdHelper.is_set(params[:beta_type])
|
||||
UI.error("Parameters beta_type cannot be null")
|
||||
puts("Error on beta type parameter")
|
||||
raise ArgumentError, "❌ Parameters beta_type cannot be null"
|
||||
end
|
||||
|
||||
UI.message("⌛️ Building and deploying to Google Play Store in #{params[:beta_type]}..")
|
||||
|
||||
# Decrypt the keys archive and Extract the keys archive
|
||||
Helper::AndroidCdHelper.decrypt_android_keys('.')
|
||||
|
||||
# Clean the project before building
|
||||
Fastlane::Actions.gradle(task: "clean")
|
||||
|
||||
# Set the build number based on the number of commits
|
||||
build_number = Fastlane::Actions.number_of_commits
|
||||
UI.message("👉🏼 Credentials decrypted")
|
||||
|
||||
# Build the Android App Bundle
|
||||
Fastlane::Actions.gradle(
|
||||
task: "bundle",
|
||||
build_type: "Release",
|
||||
print_command: true,
|
||||
properties: {
|
||||
"android.injected.version.code" => build_number
|
||||
}
|
||||
other_action.flutter_build(
|
||||
build: 'appbundle',
|
||||
build_args: params[:build_args]
|
||||
)
|
||||
|
||||
UI.message("👉🏼 App built")
|
||||
|
||||
# Upload the Android App Bundle to the Play Store
|
||||
Fastlane::Actions.upload_to_play_store(
|
||||
track: params[:beta_type],
|
||||
json_key: './service_account_key.json',
|
||||
aab: '../build/app/outputs/bundle/release/app-release.aab',
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_images: true,
|
||||
skip_upload_screenshots: true,
|
||||
release_status: "draft",
|
||||
version_code: build_number
|
||||
)
|
||||
begin
|
||||
other_action.upload_to_play_store(
|
||||
track: params[:beta_type],
|
||||
json_key: './service_account_key.json',
|
||||
aab: '../build/app/outputs/bundle/release/app-release.aab',
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_images: true,
|
||||
skip_upload_screenshots: true,
|
||||
release_status: "completed"
|
||||
)
|
||||
rescue StandardError => e
|
||||
UI.message("👉🏼 Unable to upload as completed; try to upload it in draft status")
|
||||
other_action.upload_to_play_store(
|
||||
track: params[:beta_type],
|
||||
json_key: './service_account_key.json',
|
||||
aab: '../build/app/outputs/bundle/release/app-release.aab',
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_images: true,
|
||||
skip_upload_screenshots: true,
|
||||
release_status: "draft"
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
# Delete artifacts files
|
||||
artifacts = ['android_keys.zip', 'key.jks', 'key.properties', 'service_account_key.json']
|
||||
Helper::AndroidCdHelper.delete_artifacts(artifacts)
|
||||
|
||||
UI.success('🍺 Successfully build & deploy appbundle to Google Play Store')
|
||||
end
|
||||
@@ -67,7 +76,13 @@ module Fastlane
|
||||
FastlaneCore::ConfigItem.new(key: :beta_type,
|
||||
env_name: "ANDROID_CD_TEST_TYPE",
|
||||
optional: false,
|
||||
description: "Type of test (production, beta, alpha, internal)")
|
||||
description: "Type of test (production, beta, alpha, internal)"),
|
||||
FastlaneCore::ConfigItem.new(
|
||||
key: :build_args,
|
||||
description: 'An array of extra arguments for "flutter build"',
|
||||
optional: true,
|
||||
type: Array
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
+9
-6
@@ -7,14 +7,12 @@ module Fastlane
|
||||
def self.run(params)
|
||||
# Check from parameters
|
||||
unless Helper::AndroidCdHelper.is_set(params[:from])
|
||||
UI.error("❌ Parameters from cannot be null")
|
||||
puts("Error on beta type parameter")
|
||||
raise ArgumentError, "❌ Parameters from cannot be null"
|
||||
end
|
||||
|
||||
# Check destination parameters
|
||||
unless Helper::AndroidCdHelper.is_set(params[:destination])
|
||||
UI.error("❌ Parameters destination cannot be null")
|
||||
puts("Error on beta type parameter")
|
||||
raise ArgumentError, "❌ Parameters destination cannot be null"
|
||||
end
|
||||
|
||||
UI.message("⌛️ Promoting to Google Play Store from #{params[:from]} to #{params[:destination]}..")
|
||||
@@ -23,8 +21,9 @@ module Fastlane
|
||||
Helper::AndroidCdHelper.decrypt_android_keys('.')
|
||||
|
||||
# Upload the Android App Bundle to the Play Store
|
||||
Fastlane::Actions.upload_to_play_store(
|
||||
track: params[:beta_type],
|
||||
other_action.upload_to_play_store(
|
||||
track: params[:from],
|
||||
track_promote_to: params[:destination],
|
||||
json_key: './service_account_key.json',
|
||||
skip_upload_apk: true,
|
||||
skip_upload_aab: true,
|
||||
@@ -34,6 +33,10 @@ module Fastlane
|
||||
skip_upload_screenshots: true
|
||||
)
|
||||
|
||||
# Delete artifacts files
|
||||
artifacts = ['android_keys.zip', 'key.jks', 'key.properties', 'service_account_key.json']
|
||||
Helper::AndroidCdHelper.delete_artifacts(artifacts)
|
||||
|
||||
UI.success('🍺 Successfully promote appbundle to Google Play Store')
|
||||
end
|
||||
|
||||
|
||||
+9
-3
@@ -13,11 +13,10 @@ module Fastlane
|
||||
|
||||
# Check if the command executed successfully
|
||||
if $?.success?
|
||||
|
||||
# Move the extracted files to the current directory
|
||||
`jar xvf #{android_directory} && mv #{android_directory}/android_keys/* #{android_directory}`
|
||||
`jar xvf #{android_directory}/android_keys.zip && mv #{android_directory}/android_keys/* #{android_directory}`
|
||||
else
|
||||
puts("Erreur lors de la décompression du fichier GPG")
|
||||
UI.user_error!("❌ Erreur lors de la décompression du fichier GPG")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,6 +26,13 @@ module Fastlane
|
||||
str_variable = variable.strip if variable.class.to_s == "String"
|
||||
variable && !(str_variable.nil? || str_variable.empty?)
|
||||
end
|
||||
|
||||
# Delete built files
|
||||
def self.delete_artifacts(artifacts)
|
||||
artifacts.each do |file|
|
||||
File.delete(file) if File.exist?(file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gem-
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.5
|
||||
- name: Install dependencies
|
||||
run: bundle check || bundle install --jobs=4 --retry=3 --path vendor/bundle
|
||||
- name: Run tests
|
||||
run: bundle exec rake
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results
|
||||
@@ -0,0 +1,12 @@
|
||||
*.gem
|
||||
Gemfile.lock
|
||||
|
||||
## Documentation cache and generated files:
|
||||
/.yardoc/
|
||||
/_yardoc/
|
||||
/doc/
|
||||
/rdoc/
|
||||
fastlane/README.md
|
||||
fastlane/report.xml
|
||||
coverage
|
||||
test-results
|
||||
@@ -0,0 +1,5 @@
|
||||
--require spec_helper
|
||||
--color
|
||||
--format d
|
||||
--format RspecJunitFormatter
|
||||
--out test-results/rspec/rspec.xml
|
||||
@@ -0,0 +1,188 @@
|
||||
---
|
||||
require:
|
||||
- rubocop/require_tools
|
||||
- rubocop-performance
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.6
|
||||
NewCops: enable
|
||||
Include:
|
||||
- "**/*.rb"
|
||||
- "**/*file"
|
||||
- "**/*.gemspec"
|
||||
- "*/lib/assets/*Template"
|
||||
- "*/lib/assets/*TemplateAndroid"
|
||||
Exclude:
|
||||
- "**/lib/assets/custom_action_template.rb"
|
||||
- "./vendor/**/*"
|
||||
- "**/lib/assets/DefaultFastfileTemplate"
|
||||
- "**/lib/assets/MatchfileTemplate"
|
||||
- "**/spec/fixtures/broken_files/broken_file.rb"
|
||||
- "**/*.provisionprofile"
|
||||
Lint/ErbNewArguments:
|
||||
Enabled: false
|
||||
Style/SlicingWithRange:
|
||||
Enabled: false
|
||||
Style/MultipleComparison:
|
||||
Enabled: false
|
||||
Style/PercentLiteralDelimiters:
|
||||
Enabled: false
|
||||
Style/ClassCheck:
|
||||
EnforcedStyle: kind_of?
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: false
|
||||
Style/SafeNavigation:
|
||||
Enabled: false
|
||||
Performance/RegexpMatch:
|
||||
Enabled: false
|
||||
Performance/StringReplacement:
|
||||
Enabled: false
|
||||
Style/NumericPredicate:
|
||||
Enabled: false
|
||||
Metrics/BlockLength:
|
||||
Enabled: false
|
||||
Metrics/ModuleLength:
|
||||
Enabled: false
|
||||
Naming/VariableNumber:
|
||||
Enabled: false
|
||||
Style/MissingRespondToMissing:
|
||||
Enabled: false
|
||||
Style/MultilineBlockChain:
|
||||
Enabled: false
|
||||
Style/NumericLiteralPrefix:
|
||||
Enabled: false
|
||||
Style/TernaryParentheses:
|
||||
Enabled: false
|
||||
Style/EmptyMethod:
|
||||
Enabled: false
|
||||
Lint/UselessAssignment:
|
||||
Exclude:
|
||||
- "**/spec/**/*"
|
||||
Require/MissingRequireStatement:
|
||||
Exclude:
|
||||
- "**/spec/**/*.rb"
|
||||
- "**/spec_helper.rb"
|
||||
- spaceship/lib/spaceship/babosa_fix.rb
|
||||
- fastlane_core/lib/fastlane_core/ui/disable_colors.rb
|
||||
- "**/Fastfile"
|
||||
- "**/*.gemspec"
|
||||
- rakelib/**/*
|
||||
- "**/*.rake"
|
||||
- "**/Rakefile"
|
||||
- fastlane/**/*
|
||||
- supply/**/*
|
||||
Layout/FirstHashElementIndentation:
|
||||
Enabled: false
|
||||
Layout/HashAlignment:
|
||||
Enabled: false
|
||||
Layout/DotPosition:
|
||||
Enabled: false
|
||||
Style/DoubleNegation:
|
||||
Enabled: false
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
Layout/HeredocIndentation:
|
||||
Enabled: false
|
||||
Style/MixinGrouping:
|
||||
Exclude:
|
||||
- "**/spec/**/*"
|
||||
Lint/SuppressedException:
|
||||
Enabled: false
|
||||
Lint/UnusedBlockArgument:
|
||||
Enabled: false
|
||||
Lint/AmbiguousBlockAssociation:
|
||||
Enabled: false
|
||||
Style/GlobalVars:
|
||||
Enabled: false
|
||||
Style/ClassAndModuleChildren:
|
||||
Enabled: false
|
||||
Style/SpecialGlobalVars:
|
||||
Enabled: false
|
||||
Metrics/AbcSize:
|
||||
Enabled: false
|
||||
Metrics/MethodLength:
|
||||
Enabled: false
|
||||
Metrics/CyclomaticComplexity:
|
||||
Enabled: false
|
||||
Style/WordArray:
|
||||
MinSize: 19
|
||||
Style/SignalException:
|
||||
Enabled: false
|
||||
Style/RedundantReturn:
|
||||
Enabled: false
|
||||
Style/IfUnlessModifier:
|
||||
Enabled: false
|
||||
Style/AndOr:
|
||||
Enabled: true
|
||||
EnforcedStyle: conditionals
|
||||
Metrics/ClassLength:
|
||||
Max: 320
|
||||
Layout/LineLength:
|
||||
Max: 370
|
||||
Metrics/ParameterLists:
|
||||
Max: 17
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
||||
Style/StringLiterals:
|
||||
Enabled: false
|
||||
Style/ConditionalAssignment:
|
||||
Enabled: false
|
||||
Style/RedundantSelf:
|
||||
Enabled: false
|
||||
Lint/UnusedMethodArgument:
|
||||
Enabled: false
|
||||
Lint/ParenthesesAsGroupedExpression:
|
||||
Exclude:
|
||||
- "**/spec/**/*"
|
||||
Naming/PredicateName:
|
||||
Enabled: false
|
||||
Style/PerlBackrefs:
|
||||
Enabled: false
|
||||
Layout/SpaceAroundOperators:
|
||||
Exclude:
|
||||
- "**/spec/actions_specs/xcodebuild_spec.rb"
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- "**/Dangerfile"
|
||||
- "**/Brewfile"
|
||||
- "**/Gemfile"
|
||||
- "**/Podfile"
|
||||
- "**/Rakefile"
|
||||
- "**/Fastfile"
|
||||
- "**/Deliverfile"
|
||||
- "**/Snapfile"
|
||||
- "**/Pluginfile"
|
||||
- "**/*.gemspec"
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
Style/MutableConstant:
|
||||
Enabled: false
|
||||
Style/ZeroLengthPredicate:
|
||||
Enabled: false
|
||||
Style/IfInsideElse:
|
||||
Enabled: false
|
||||
Style/CollectionMethods:
|
||||
Enabled: false
|
||||
Style/MethodCallWithArgsParentheses:
|
||||
Enabled: true
|
||||
IgnoredMethods:
|
||||
- require
|
||||
- require_relative
|
||||
- fastlane_require
|
||||
- gem
|
||||
- program
|
||||
- command
|
||||
- raise
|
||||
- attr_accessor
|
||||
- attr_reader
|
||||
- desc
|
||||
- lane
|
||||
- private_lane
|
||||
- platform
|
||||
- to
|
||||
- not_to
|
||||
- describe
|
||||
- it
|
||||
- be
|
||||
- context
|
||||
- before
|
||||
- after
|
||||
@@ -0,0 +1,4 @@
|
||||
# os: osx # enable this if you need macOS support
|
||||
language: ruby
|
||||
rvm:
|
||||
- 2.2.4
|
||||
@@ -0,0 +1,9 @@
|
||||
source('https://rubygems.org')
|
||||
|
||||
gemspec
|
||||
|
||||
gem 'json'
|
||||
gem "cocoapods"
|
||||
|
||||
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
|
||||
eval_gemfile(plugins_path) if File.exist?(plugins_path)
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 Malo Léon <malo.leon@wyatt-studio.fr>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,52 @@
|
||||
# ios_cd plugin
|
||||
|
||||
[](https://rubygems.org/gems/fastlane-plugin-ios_cd)
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-ios_cd`, add it to your project by running:
|
||||
|
||||
```bash
|
||||
fastlane add_plugin ios_cd
|
||||
```
|
||||
|
||||
## About ios_cd
|
||||
|
||||
Testflight and AppStore deployment plugin for Fastlane, simplifying the build and deployment porcess to internal, external, and production channels, and promoting builds for testing.
|
||||
|
||||
**Note to author:** Add a more detailed description about this plugin here. If your plugin contains multiple actions, make sure to mention them here.
|
||||
|
||||
## Example
|
||||
|
||||
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`.
|
||||
|
||||
**Note to author:** Please set up a sample project to make it easy for users to explore what your plugin does. Provide everything that is necessary to try out the plugin in this project (including a sample Xcode/Android project if necessary)
|
||||
|
||||
## Run tests for this plugin
|
||||
|
||||
To run both the tests, and code style validation, run
|
||||
|
||||
```
|
||||
rake
|
||||
```
|
||||
|
||||
To automatically fix many of the styling issues, use
|
||||
```
|
||||
rubocop -a
|
||||
```
|
||||
|
||||
## Issues and Feedback
|
||||
|
||||
For any other issues and feedback about this plugin, please submit it to this repository.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
|
||||
|
||||
## Using _fastlane_ Plugins
|
||||
|
||||
For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
|
||||
|
||||
## About _fastlane_
|
||||
|
||||
_fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
|
||||
@@ -0,0 +1,9 @@
|
||||
require 'bundler/gem_tasks'
|
||||
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new
|
||||
|
||||
require 'rubocop/rake_task'
|
||||
RuboCop::RakeTask.new(:rubocop)
|
||||
|
||||
task(default: [:spec, :rubocop])
|
||||
@@ -0,0 +1,36 @@
|
||||
lib = File.expand_path("lib", __dir__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require 'fastlane/plugin/ios_cd/version'
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = 'fastlane-plugin-ios_cd'
|
||||
spec.version = Fastlane::IosCd::VERSION
|
||||
spec.author = 'Malo Léon'
|
||||
spec.email = 'malo.leon@wyatt-studio.fr'
|
||||
|
||||
spec.summary = 'Testflight and AppStore deployment plugin for Fastlane, simplifying the build and deployment porcess to internal, external, and production channels, and promoting builds for testing.'
|
||||
# spec.homepage = "https://github.com/<GITHUB_USERNAME>/fastlane-plugin-ios_cd"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.files = Dir["lib/**/*"] + %w(README.md LICENSE)
|
||||
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
||||
spec.require_paths = ['lib']
|
||||
|
||||
spec.required_ruby_version = '>= 2.6'
|
||||
|
||||
# Don't add a dependency to fastlane or fastlane_re
|
||||
# since this would cause a circular dependency
|
||||
|
||||
# spec.add_dependency 'your-dependency', '~> 1.0.0'
|
||||
|
||||
spec.add_development_dependency('bundler')
|
||||
spec.add_development_dependency('fastlane', '>= 2.212.2')
|
||||
spec.add_development_dependency('pry')
|
||||
spec.add_development_dependency('rake')
|
||||
spec.add_development_dependency('rspec')
|
||||
spec.add_development_dependency('rspec_junit_formatter')
|
||||
spec.add_development_dependency('rubocop', '1.12.1')
|
||||
spec.add_development_dependency('rubocop-performance')
|
||||
spec.add_development_dependency('rubocop-require_tools')
|
||||
spec.add_development_dependency('simplecov')
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
lane :test do
|
||||
build_and_deploy(beta_type: "internal")
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
# Autogenerated by fastlane
|
||||
@@ -0,0 +1,16 @@
|
||||
require 'fastlane/plugin/ios_cd/version'
|
||||
|
||||
module Fastlane
|
||||
module IosCd
|
||||
# Return all .rb files inside the "actions" and "helper" directory
|
||||
def self.all_classes
|
||||
Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# By default we want to import all available actions and helpers
|
||||
# A plugin can contain any number of actions and plugins
|
||||
Fastlane::IosCd.all_classes.each do |current|
|
||||
require current
|
||||
end
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
require 'fastlane/action'
|
||||
require_relative '../helper/ios_cd_helper'
|
||||
|
||||
module Fastlane
|
||||
module Actions
|
||||
class BuildAndDeployAction < Action
|
||||
def self.run(params)
|
||||
# Check parameters
|
||||
unless Helper::IosCdHelper.is_set(params[:beta_type])
|
||||
raise ArgumentError, "❌ Parameters beta_type cannot be null"
|
||||
end
|
||||
|
||||
# Check if match password exist
|
||||
match_password = ENV['MATCH_PASSWORD']
|
||||
|
||||
if match_password.nil? || match_password.empty?
|
||||
raise ArgumentError, "❌ match password must be provided"
|
||||
end
|
||||
|
||||
UI.message("⌛️ Building and deploying to Store in #{params[:beta_type]}..")
|
||||
|
||||
# Decrypt the keys archive and Extract the keys archive
|
||||
Helper::IosCdHelper.decrypt_ios_keys('.')
|
||||
UI.message("🍺 Credentials decrypted.")
|
||||
|
||||
# Retrieve credentials
|
||||
creds = Helper::IosCdHelper.parse_ios_credentials('.')
|
||||
UI.message("🍺 Credentials parsed.")
|
||||
|
||||
# Delete decrypted artifacts
|
||||
artifacts = ['ios_keys.zip', 'ios_credentials.json']
|
||||
Helper::IosCdHelper.delete_artifacts(artifacts)
|
||||
|
||||
# Check credentials
|
||||
required_fields = ['developer_app_id', 'username', 'developer_app_identifier', 'app_identifier_extensions', 'apple_issuer_id', 'apple_key_id', 'team_id', 'team_name', 'apple_key_content', 'git_url', 'git_basic_authorization', 'provisioning_profiles', 'temp_keychain_user', 'temp_keychain_password']
|
||||
Helper::IosCdHelper.check_required_fields(required_fields, creds.keys)
|
||||
|
||||
# Delete keychain if existing
|
||||
if File.exist?(File.expand_path("~/Library/Keychains/#{creds['temp_keychain_user']}-db"))
|
||||
other_action.delete_keychain(
|
||||
name: creds['temp_keychain_user'].to_s
|
||||
)
|
||||
end
|
||||
|
||||
# Create keychain to store certifs
|
||||
other_action.create_keychain(
|
||||
name: creds['temp_keychain_user'].to_s,
|
||||
password: creds['temp_keychain_password'].to_s,
|
||||
unlock: false,
|
||||
timeout: 0
|
||||
)
|
||||
UI.message("🍺 New keychain created")
|
||||
|
||||
# Obtain App Store Connect API key
|
||||
api_key = other_action.app_store_connect_api_key(
|
||||
key_id: creds['apple_key_id'].to_s,
|
||||
issuer_id: creds['apple_issuer_id'].to_s,
|
||||
key_content: creds['apple_key_content'].to_s,
|
||||
duration: 500,
|
||||
in_house: false
|
||||
)
|
||||
UI.message("🍺 API Key formated")
|
||||
|
||||
# Set up code signing using match
|
||||
# Configures and runs `match` which manages code signing certificates and provisioning profiles for the project.
|
||||
# The function takes the app's bundle identifier, an authorization token for the project's Git repository, and the name and password for a temporary keychain used to store the signing certificate.
|
||||
# It uses the App Store Connect API key to access the App Store and increment the build number.
|
||||
# It then runs `gym` to build and sign the app using the selected provisioning profile, and finally, uses `pilot` to upload the app to TestFlight for beta testing.
|
||||
other_action.match(
|
||||
api_key: api_key,
|
||||
type: 'appstore',
|
||||
app_identifier: creds['app_identifier_extensions'],
|
||||
git_basic_authorization: Base64.strict_encode64(creds['git_basic_authorization']),
|
||||
keychain_name: creds['temp_keychain_user'].to_s,
|
||||
keychain_password: creds['temp_keychain_password'].to_s,
|
||||
username: creds['username'].to_s,
|
||||
team_id: creds['team_id'].to_s,
|
||||
team_name: creds['team_name'].to_s,
|
||||
git_url: creds['git_url'].to_s,
|
||||
storage_mode: "git"
|
||||
)
|
||||
UI.message("🍺 Signing environment ready")
|
||||
|
||||
archive_path = "../build/ios/archive/Runner.xcarchive"
|
||||
|
||||
if File.exist?(archive_path)
|
||||
UI.message("🍺 Archive built")
|
||||
else
|
||||
raise ArgumentError, "❌ Cannot build XCode archive"
|
||||
end
|
||||
|
||||
# Build and export app using Gym
|
||||
# Packages an iOS app or framework for distribution to the App Store, TestFlight, or Enterprise distribution.
|
||||
other_action.gym(
|
||||
configuration: "Release",
|
||||
workspace: "Runner.xcworkspace",
|
||||
archive_path: archive_path,
|
||||
skip_build_archive: true,
|
||||
export_method: "app-store",
|
||||
export_team_id: creds['team_id'].to_s,
|
||||
export_options: {
|
||||
provisioningProfiles: creds['provisioning_profiles'],
|
||||
signingCertificate: "Apple Distribution: Jagger & Lewis (8547YCHLVR)"
|
||||
}
|
||||
)
|
||||
|
||||
# Upload build to App Store Connect using Pilot
|
||||
other_action.pilot(
|
||||
api_key: api_key,
|
||||
apple_id: creds['developer_app_id'].to_s,
|
||||
app_identifier: creds['developer_app_identifier'].to_s,
|
||||
skip_waiting_for_build_processing: true,
|
||||
skip_submission: true,
|
||||
distribute_external: false,
|
||||
notify_external_testers: false,
|
||||
ipa: "./Runner.ipa",
|
||||
)
|
||||
|
||||
# Delete keychain if existing
|
||||
if File.exist?(File.expand_path("~/Library/Keychains/#{name}-db"))
|
||||
other_action.delete_keychain(
|
||||
name: creds['temp_keychain_user']
|
||||
)
|
||||
end
|
||||
|
||||
# Delete build artifacts
|
||||
artifacts = ['Runner.app.dSYM.zip', 'Runner.ipa']
|
||||
Helper::IosCdHelper.delete_artifacts(artifacts)
|
||||
end
|
||||
|
||||
def self.description
|
||||
"Testflight and AppStore deployment plugin for Fastlane, simplifying the build and deployment porcess to internal, external, and production channels, and promoting builds for testing."
|
||||
end
|
||||
|
||||
def self.authors
|
||||
["SAS Wyatt Studio"]
|
||||
end
|
||||
|
||||
def self.return_value
|
||||
end
|
||||
|
||||
def self.details
|
||||
"The Fastlane Testflight / Appstore deloyment action streamlines the build and deployment to internal, external and production channels. Allow you to promote builds on testflight beta tests."
|
||||
end
|
||||
|
||||
def self.available_options
|
||||
[
|
||||
FastlaneCore::ConfigItem.new(key: :beta_type,
|
||||
env_name: "IOS_CD_TEST_TYPE",
|
||||
optional: false,
|
||||
description: "Type of test (internal, external, production)"),
|
||||
]
|
||||
end
|
||||
|
||||
def self.is_supported?(platform)
|
||||
[:ios].include?(platform)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,71 @@
|
||||
require 'fastlane/action'
|
||||
require_relative '../helper/ios_cd_helper'
|
||||
|
||||
module Fastlane
|
||||
module Actions
|
||||
class PromoteAction < Action
|
||||
def self.run(params)
|
||||
|
||||
UI.message("⌛️ Promoting to Store in external test..")
|
||||
|
||||
# Decrypt the keys archive and Extract the keys archive
|
||||
Helper::IosCdHelper.decrypt_ios_keys('.')
|
||||
UI.message("👉🏼 Credentials decrypted.")
|
||||
|
||||
# Retrieve credentials
|
||||
creds = Helper::IosCdHelper.parseIosCredentials('.')
|
||||
UI.message("👉🏼 Credentials parsed.")
|
||||
|
||||
# Obtain App Store Connect API key
|
||||
api_key = other_action.app_store_connect_api_key(
|
||||
key_id: creds['apple_key_id'].to_s,
|
||||
issuer_id: creds['apple_issuer_id'].to_s,
|
||||
key_content: creds['apple_key_content'].to_s,
|
||||
duration: 1200,
|
||||
in_house: false
|
||||
)
|
||||
UI.message("👉🏼 API Key formated")
|
||||
|
||||
# Upload build to App Store Connect using Pilot
|
||||
other_action.pilot(
|
||||
api_key: api_key,
|
||||
apple_id: creds['developer_app_id'].to_s,
|
||||
app_identifier: creds['developer_app_identifier'].to_s,
|
||||
skip_waiting_for_build_processing: false,
|
||||
distribute_only: true,
|
||||
skip_submission: true,
|
||||
distribute_external: true,
|
||||
notify_external_testers: true,
|
||||
groups: [
|
||||
"Bêta externe",
|
||||
],
|
||||
app_platform:'ios',
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
def self.description
|
||||
"Testflight and AppStore deployment plugin for Fastlane, simplifying the build and deployment porcess to internal, external, and production channels, and promoting builds for testing."
|
||||
end
|
||||
|
||||
def self.authors
|
||||
["SAS Wyatt Studio"]
|
||||
end
|
||||
|
||||
def self.return_value
|
||||
end
|
||||
|
||||
def self.details
|
||||
"The Fastlane Testflight / Appstore deloyment action streamlines the build and deployment to internal, external and production channels. Allow you to promote builds on testflight beta tests."
|
||||
end
|
||||
|
||||
def self.available_options
|
||||
[]
|
||||
end
|
||||
|
||||
def self.is_supported?(platform)
|
||||
[:ios].include?(platform)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,72 @@
|
||||
require 'fastlane/action'
|
||||
require 'json'
|
||||
require 'fastlane_core/ui/ui'
|
||||
|
||||
module Fastlane
|
||||
module Helper
|
||||
class IosCdHelper
|
||||
# Check if a parameter is set or not
|
||||
def self.is_set(variable)
|
||||
str_variable = variable
|
||||
str_variable = variable.strip if variable.class.to_s == "String"
|
||||
variable && !(str_variable.nil? || str_variable.empty?)
|
||||
end
|
||||
|
||||
# Decrypts ios credentials
|
||||
def self.decrypt_ios_keys(ios_directory)
|
||||
# Define the GPG command with options
|
||||
gpg_command = "gpg --quiet --batch --yes --decrypt --passphrase=#{ENV['IOS_KEYS_SECRET_PASSPHRASE']} \
|
||||
--output #{ios_directory}/ios_keys.zip #{ios_directory}/ios_keys.zip.gpg"
|
||||
|
||||
# Execute the GPG command using system
|
||||
system(gpg_command)
|
||||
|
||||
# Check if the command executed successfully
|
||||
if $?.success?
|
||||
# Move the extracted files to the current directory
|
||||
`jar xvf #{ios_directory}/ios_keys.zip && mv #{ios_directory}/ios_keys/* #{ios_directory}`
|
||||
else
|
||||
UI.user_error!("❌ Erreur lors de la décompression du fichier GPG")
|
||||
end
|
||||
end
|
||||
|
||||
# Parse credential file
|
||||
def self.parse_ios_credentials(ios_directory)
|
||||
if File.exist?("#{ios_directory}/ios_credentials.json")
|
||||
# Read file and decrypt it
|
||||
file = File.read("#{ios_directory}/ios_credentials.json")
|
||||
JSON.parse(file)
|
||||
|
||||
else
|
||||
UI.user_error!("❌ Ios credentials doesn't exist")
|
||||
puts("json file doesn't exist")
|
||||
end
|
||||
end
|
||||
|
||||
# Check json fields
|
||||
def self.check_required_fields(required_fields, json)
|
||||
missing_fields = required_fields - json
|
||||
|
||||
unless missing_fields.empty?
|
||||
raise ArgumentError, "❌ missing keys in credential json file : #{missing_fields}"
|
||||
end
|
||||
end
|
||||
|
||||
# Check if set of environment variables are defined
|
||||
def self.check_environment_variables(variables)
|
||||
variables.each do |variable|
|
||||
unless ENV.key?(variable)
|
||||
raise "❌ The environment variable '#{variable}' is not defined."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Delete built files
|
||||
def self.delete_artifacts(artifacts)
|
||||
artifacts.each do |file|
|
||||
File.delete(file) if File.exist?(file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
module Fastlane
|
||||
module IosCd
|
||||
VERSION = "0.1.0"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
describe Fastlane::Actions::BuildAndDeployAction do
|
||||
describe '#run' do
|
||||
it 'prints a message' do
|
||||
expect(Fastlane::UI).to receive(:message).with("The ios_cd plugin is working!")
|
||||
|
||||
Fastlane::Actions::BuildAndDeployAction.run(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
||||
|
||||
require 'simplecov'
|
||||
|
||||
# SimpleCov.minimum_coverage 95
|
||||
SimpleCov.start
|
||||
|
||||
# This module is only used to check the environment is currently a testing env
|
||||
module SpecHelper
|
||||
end
|
||||
|
||||
require 'fastlane' # to import the Action super class
|
||||
require 'fastlane/plugin/ios_cd' # import the actual plugin
|
||||
|
||||
Fastlane.load_actions # load other actions (in case your plugin calls other actions or shared values)
|
||||
Reference in New Issue
Block a user