61 Commits
Author SHA1 Message Date
malo 897eb84042 refactor: use manually signing xcarchive 2023-11-07 16:37:19 +01:00
malo 61732adf9e feat: add export team id in gym 2023-11-07 13:37:44 +01:00
malo e11b2fb58c feat: remove build responsabilty from iOS script 2023-10-31 09:32:14 +01:00
malo 3a411fe576 refactor: rollback pod install step 2023-10-26 19:25:08 +02:00
malo c0f125a23c fix: pilot ipa path 2023-10-26 17:56:19 +02:00
malo 47bfb28e26 refactor: remove error handler in flutter build call 2023-10-26 14:35:31 +02:00
malo 5d91f56899 feat: build archive with flutter instead of gym and use only gym to build ipa (close #19) 2023-10-26 14:32:10 +02:00
Malo Léon f05cdfba4f feat: remove incremented feature from iOS script (#33) 2023-10-25 16:54:54 +02:00
malo 737ec11503 fix: fix keychain name when trying to delete 2023-08-14 13:39:21 +02:00
malo 7155b7818c fix: update promote action to make supply working on promote 2023-08-10 14:14:26 +00:00
malo 71299744da feat: action try to upload as completed before draft 2023-08-10 13:05:32 +02:00
malo fea0c153d4 feat: pass args on android cd 2023-08-08 18:28:03 +02:00
hugo 9e56b4127e docs: add jar documentation to zip file in a format fastlane can understand 2023-08-08 17:51:26 +02:00
malo 4cca8ef739 fix: rollback to draft status 2023-07-27 10:55:32 +02:00
malo bc848e09ec feat: move aab release_status from draft to completed 2023-07-12 08:41:49 +02:00
malo 53e512462b feat: remove comments on android pipeline to upload build 2023-07-11 15:59:16 +02:00
malo b307d8110a remove all actions linked to gradle 2023-07-11 15:52:27 +02:00
malo 1ef8fc00e7 wip: temporary remove upload to tetflight action 2023-07-11 15:45:34 +02:00
malo fef6c75738 fix: use flutter_build action instead of gradle action (#25) 2023-07-11 15:29:19 +02:00
malo 4021345e0a refactor: move delete artifacts fn in helper for android lane 2023-07-05 16:46:45 +02:00
malo 289802d9bc docs: add some comments 2023-07-05 16:40:45 +02:00
malo 385e7c05a9 refactor: move some fn in helper class 2023-07-05 16:38:57 +02:00
malo f1141a78ad fix: remove duplacte git_url field 2023-07-05 16:17:24 +02:00
malo 7cd4ac4238 fix: spec dir folder for gradle 2023-06-22 14:24:33 +02:00
malo ca2efae3c7 fix: syntax error 2023-06-22 14:14:22 +02:00
malo 1062d3d9e2 fix: remove projectDir field 2023-06-22 14:12:56 +02:00
malo 25209ab76b fix(android): unzip bug (close #20) 2023-06-22 14:03:04 +02:00
malo 522a0a0bf9 fix: update api_key duration 2023-06-22 10:59:57 +02:00
malo 3c0d2b40cf feat: add rase instead of user error in android script 2023-06-09 17:56:02 +02:00
malo d86c49d7ba feat: script remove artifacts on android cd (close #15) 2023-06-09 17:53:29 +02:00
malo 6b7bc14451 feat: remove artifacts (#15), check for match password (close #13), and check json key (close #11) 2023-06-09 17:53:29 +02:00
malo a4f0010170 refactor: user_error fn 2023-06-09 17:53:29 +02:00
malo 76723e2185 refactor: remove class call with other_action 2023-06-09 17:53:29 +02:00
malo 0207a311b5 style: remove unecesary puts lines 2023-06-09 17:53:29 +02:00
malo 37cbf76fc9 style: update error message with more consistency 2023-06-09 17:53:28 +02:00
malo 83216cbbbb style: Improved error message consistency 2023-05-15 18:27:20 +02:00
malo 68bf5a1502 fix: crendential typo in ios helper 2023-05-15 18:19:02 +02:00
malo e003011b9a feat: Improved pipeline efficiency and adapted to new credentials template 2023-05-15 17:00:23 +02:00
malo b67a267ca5 fix: rollback native actions calls in helper 2023-05-02 09:41:34 +02:00
malo 1c6b04e511 refactor: use other_action keyword to call native fastane actions 2023-05-02 09:30:21 +02:00
malo 6e7befa067 fix: remove call keyword 2023-05-01 23:14:07 +02:00
malo 9e685cae0e refactor: rollback in actions called in helper 2023-05-01 22:48:51 +02:00
malo 04e06cf1a7 refactor: specify class for actions 2023-05-01 22:37:50 +02:00
malo fa9de2881f refactor: try to use method actions instead of classes 2023-05-01 22:31:03 +02:00
malo 296d7181ca fix: change git_url label 2023-05-01 21:38:45 +02:00
malo a43966d6f8 feat: add logs and parameters to pilot 2023-05-01 21:34:17 +02:00
malo dbc8c0fed6 feat: add logs and parameters to match 2023-05-01 21:21:11 +02:00
malo 24a09b44c4 feat: add logs and parameters to match 2023-05-01 21:16:35 +02:00
malo 502c7b06e4 fix: add integrate args to cocoapods action args (#2) 2023-04-27 18:24:03 +02:00
malo 74d3527b40 fix: add integrate args to cocoapods action args (#2) 2023-04-27 18:20:00 +02:00
malo d88276a0ec fix: add clean args to cocoapods action args (#2) 2023-04-27 18:17:58 +02:00
malo ec414c9864 fix: add necessary args (#2) 2023-04-27 18:05:53 +02:00
malo cc2cd8ea68 fix: update syntax (#2) 2023-04-27 16:52:22 +02:00
malo 1759498e23 refactor: update params typo (#2) 2023-04-27 14:49:24 +02:00
malo f66ca731f8 refactor: upadte native fastlane calls (#1 #2) 2023-04-27 14:23:12 +02:00
malo 89732f1f3f fix: add ios_directory (#2) 2023-04-26 17:20:22 +02:00
malo 942f3afbf9 fix: fix export helper methode (#2) 2023-04-26 17:16:03 +02:00
malo 87a5b0f809 fix: fix unexpected end-of-input error (#2) 2023-04-26 17:04:24 +02:00
malo 0a004a595c refactor: rename action (#2) 2023-04-26 17:00:56 +02:00
malo 155d048c18 feat: implement deployement pipeline (#2) 2023-04-26 16:54:43 +02:00
malo ee58f96126 feat: init ios plugin (#2) 2023-04-26 13:46:02 +02:00
24 changed files with 808 additions and 57 deletions
BIN
View File
Binary file not shown.
+32 -22
View File
@@ -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
@@ -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
@@ -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
@@ -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
+12
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
--require spec_helper
--color
--format d
--format RspecJunitFormatter
--out test-results/rspec/rspec.xml
+188
View File
@@ -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
+9
View File
@@ -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)
+21
View File
@@ -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.
+52
View File
@@ -0,0 +1,52 @@
# ios_cd plugin
[![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](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).
+9
View File
@@ -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
@@ -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)