fastlane-plugin-google_play_track_updater 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
+ SHA256:
3
+ metadata.gz: 11eadb84667aef723c783a8dcffa26780b405708ff266657ddacc2a8d7f91d2f
4
+ data.tar.gz: f680a23a1d846440ca71559f04d20636e0faeb95729471b700deaa879c16f3cd
5
+ SHA512:
6
+ metadata.gz: '08cc54c3f46d2a9748e21a7de9eb83bfd024f5ad176b00f7144a38bdec1b9787982bc30b71d818dbe7a850ea14367c83abe7f2be46e6258854d12e6a54d200d8'
7
+ data.tar.gz: defce589cb05520229a89060ca58073c2e7e59ec9ef1abaec5a332b9854b324f96969bdd22042d633ebd7a9c5b18451f051fbc2fea8adaec6473163d3c9505cf
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Takuma Homma <nagomimatcha@gmail.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,177 @@
1
+ # google_play_track_updater plugin
2
+
3
+ [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-google_play_track_updater)
4
+
5
+ ## Getting Started
6
+
7
+ This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-google_play_track_updater`, add it to your project by running:
8
+
9
+ ```bash
10
+ # to Gemfile
11
+ bundle add 'fastlane-plugin-google_play_track_updater'
12
+ ```
13
+
14
+ ```bash
15
+ # to Pluginfile
16
+ fastlane add_plugin google_play_track_updater
17
+ ```
18
+
19
+ ## About google_play_track_updater
20
+
21
+ A fastlane plugin for managing Google Play release tracks. This plugin provides three actions to control release statuses and rollout fractions:
22
+
23
+ - **`halt_google_play_release`** - Halts an active staged rollout or completed release
24
+ - **`resume_google_play_release`** - Resumes a halted release
25
+ - **`update_google_play_release_rollout`** - Updates the rollout percentage for a staged rollout
26
+
27
+ ## Motivation
28
+
29
+ While [supply](https://docs.fastlane.tools/actions/supply/) can also change release statuses, it requires many configuration parameters which can lead to unintended side effects. This plugin provides dedicated actions for each specific operation, making release management more explicit and manageable.
30
+
31
+ By using focused actions like `halt_google_play_release`, `resume_google_play_release`, and `update_google_play_release_rollout`, you can:
32
+
33
+ - Clearly express your intent in your Fastfile
34
+ - Reduce the risk of accidentally modifying other release properties
35
+ - Simplify your CI/CD pipelines with purpose-built commands
36
+
37
+ ## Actions
38
+
39
+ ### halt_google_play_release
40
+
41
+ Halts an active staged rollout or completed release for a specific version on a Google Play track.
42
+
43
+ ```ruby
44
+ halt_google_play_release(
45
+ package_name: "com.example.app",
46
+ track: "production",
47
+ version_name: "1.0.0",
48
+ json_file_path: "path/to/service-account.json"
49
+ )
50
+ ```
51
+
52
+ **Parameters:**
53
+
54
+ | Key | Description | Required | Type |
55
+ |-----|-------------|----------|------|
56
+ | `package_name` | The package name of the application (e.g., 'com.example.app') | Yes | String |
57
+ | `track` | The track of the application (production, beta, alpha, internal) | Yes | String |
58
+ | `version_name` | The version name to halt (e.g., '1.0.0') | Yes | String |
59
+ | `json_file_path` | Path to a file containing service account or external account JSON | No* | String |
60
+ | `json_key_data` | Service account or external account JSON data as a string | No* | String |
61
+
62
+ \* Either `json_file_path` or `json_key_data` must be provided
63
+
64
+ ### resume_google_play_release
65
+
66
+ Resumes a halted staged rollout for a specific version on a Google Play track. The status will change from 'halted' to either 'completed' (if no user_fraction is set) or 'inProgress' (if user_fraction is set for staged rollout).
67
+
68
+ ```ruby
69
+ resume_google_play_release(
70
+ package_name: "com.example.app",
71
+ track: "production",
72
+ version_name: "1.0.0",
73
+ json_file_path: "path/to/service-account.json"
74
+ )
75
+ ```
76
+
77
+ **Parameters:**
78
+
79
+ | Key | Description | Required | Type |
80
+ |-----|-------------|----------|------|
81
+ | `package_name` | The package name of the application (e.g., 'com.example.app') | Yes | String |
82
+ | `track` | The track of the application (production, beta, alpha, internal) | Yes | String |
83
+ | `version_name` | The version name to resume (e.g., '1.0.0') | Yes | String |
84
+ | `json_file_path` | Path to a file containing service account or external account JSON | No* | String |
85
+ | `json_key_data` | Service account or external account JSON data as a string | No* | String |
86
+
87
+ \* Either `json_file_path` or `json_key_data` must be provided
88
+
89
+ ### update_google_play_release_rollout
90
+
91
+ Updates the rollout percentage for a staged rollout on a Google Play track. Only updates releases with 'inProgress' status.
92
+
93
+ ```ruby
94
+ update_google_play_release_rollout(
95
+ package_name: "com.example.app",
96
+ track: "production",
97
+ version_name: "1.0.0",
98
+ user_fraction: 0.5, # 50% rollout
99
+ json_file_path: "path/to/service-account.json"
100
+ )
101
+ ```
102
+
103
+ **Parameters:**
104
+
105
+ | Key | Description | Required | Type |
106
+ |-----|-------------|----------|------|
107
+ | `package_name` | The package name of the application (e.g., 'com.example.app') | Yes | String |
108
+ | `track` | The track of the application (production, beta, alpha, internal) | Yes | String |
109
+ | `version_name` | The version name to update (e.g., '1.0.0') | Yes | String |
110
+ | `user_fraction` | The rollout percentage as a fraction (0.0 to 1.0, exclusive). e.g., 0.1 for 10% rollout | Yes | Float |
111
+ | `json_file_path` | Path to a file containing service account or external account JSON | No* | String |
112
+ | `json_key_data` | Service account or external account JSON data as a string | No* | String |
113
+
114
+ \* Either `json_file_path` or `json_key_data` must be provided
115
+
116
+ ## Example
117
+
118
+ Here are examples of how to use each action in your `Fastfile`:
119
+
120
+ ```ruby
121
+ lane :halt_release do
122
+ halt_google_play_release(
123
+ package_name: "com.example.app",
124
+ track: "production",
125
+ version_name: "1.0.0",
126
+ json_file_path: "path/to/service-account.json"
127
+ )
128
+ end
129
+
130
+ lane :resume_release do
131
+ resume_google_play_release(
132
+ package_name: "com.example.app",
133
+ track: "production",
134
+ version_name: "1.0.0",
135
+ json_file_path: "path/to/external-account.json"
136
+ )
137
+ end
138
+
139
+ lane :update_rollout do
140
+ update_google_play_release_rollout(
141
+ package_name: "com.example.app",
142
+ track: "production",
143
+ version_name: "1.0.0",
144
+ user_fraction: 0.5,
145
+ json_file_path: "path/to/service-account.json"
146
+ )
147
+ end
148
+ ```
149
+
150
+ ## Run tests for this plugin
151
+
152
+ To run both the tests, and code style validation, run
153
+
154
+ ```
155
+ rake
156
+ ```
157
+
158
+ To automatically fix many of the styling issues, use
159
+ ```
160
+ rubocop -a
161
+ ```
162
+
163
+ ## Issues and Feedback
164
+
165
+ For any other issues and feedback about this plugin, please submit it to this repository.
166
+
167
+ ## Troubleshooting
168
+
169
+ If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
170
+
171
+ ## Using _fastlane_ Plugins
172
+
173
+ For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
174
+
175
+ ## About _fastlane_
176
+
177
+ _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,81 @@
1
+ require 'fastlane/action'
2
+ require_relative '../client'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ class HaltGooglePlayReleaseAction < Action
7
+ def self.run(params)
8
+ package_name = params[:package_name]
9
+ track = params[:track]
10
+ version_name = params[:version_name]
11
+ json_file_path = params[:json_file_path]
12
+ json_key_data = params[:json_key_data]
13
+ client = Fastlane::GooglePlayTrackUpdater::GooglePlayClient.new(
14
+ json_file_path: json_file_path,
15
+ json_key_data: json_key_data
16
+ )
17
+ client.halt_release(
18
+ package_name: package_name,
19
+ track: track,
20
+ version_name: version_name
21
+ )
22
+ end
23
+
24
+ def self.description
25
+ 'Halts an active staged rollout or completed for a specific version on a Google Play track.'
26
+ end
27
+
28
+ def self.authors
29
+ ['Takuma Homma']
30
+ end
31
+
32
+ def self.return_value
33
+ # If your method provides a return value, you can describe here what it does
34
+ end
35
+
36
+ def self.details
37
+ 'Halts an active staged rollout or completed for a specific version on a Google Play track.'
38
+ end
39
+
40
+ def self.available_options
41
+ [
42
+ FastlaneCore::ConfigItem.new(key: :package_name,
43
+ env_name: 'HALT_GOOGLE_PLAY_RELEASE_PACKAGE_NAME',
44
+ description: 'The package name of the application. e.g. \'com.example.app\'',
45
+ optional: false,
46
+ type: String),
47
+ FastlaneCore::ConfigItem.new(key: :track,
48
+ env_name: "HALT_GOOGLE_PLAY_RELEASE_TRACK",
49
+ description: 'The track of the application. The available tracks are: production, beta, alpha, internal',
50
+ optional: false,
51
+ type: String),
52
+ FastlaneCore::ConfigItem.new(key: :version_name,
53
+ env_name: 'HALT_GOOGLE_PLAY_RELEASE_VERSION_NAME',
54
+ description: 'The version name to update. e.g. \'1.0.0\'',
55
+ optional: false,
56
+ type: String),
57
+ FastlaneCore::ConfigItem.new(key: :json_file_path,
58
+ env_name: 'HALT_GOOGLE_PLAY_RELEASE_JSON_FILE_PATH',
59
+ description: 'The path to a file containing service account or external account JSON, used to authenticate with Google',
60
+ optional: true,
61
+ type: String),
62
+ FastlaneCore::ConfigItem.new(key: :json_key_data,
63
+ env_name: 'HALT_GOOGLE_PLAY_RELEASE_JSON_KEY_DATA',
64
+ description: 'The file data containing service account or external account JSON, used to authenticate with Google',
65
+ optional: true,
66
+ type: String)
67
+
68
+ ]
69
+ end
70
+
71
+ def self.is_supported?(platform)
72
+ # Adjust this if your plugin only works for a particular platform (iOS vs. Android, for example)
73
+ # See: https://docs.fastlane.tools/advanced/#control-configuration-by-lane-and-by-platform
74
+ #
75
+ # [:ios, :mac, :android].include?(platform)
76
+
77
+ platform == :android
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,81 @@
1
+ require 'fastlane/action'
2
+ require_relative '../client'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ class ResumeGooglePlayReleaseAction < Action
7
+ def self.run(params)
8
+ package_name = params[:package_name]
9
+ track = params[:track]
10
+ version_name = params[:version_name]
11
+ json_file_path = params[:json_file_path]
12
+ json_key_data = params[:json_key_data]
13
+ client = Fastlane::GooglePlayTrackUpdater::GooglePlayClient.new(
14
+ json_file_path: json_file_path,
15
+ json_key_data: json_key_data
16
+ )
17
+ client.resume_release(
18
+ package_name: package_name,
19
+ track: track,
20
+ version_name: version_name
21
+ )
22
+ end
23
+
24
+ def self.description
25
+ 'Resumes a halted staged rollout for a specific version on a Google Play track.'
26
+ end
27
+
28
+ def self.authors
29
+ ["Takuma Homma"]
30
+ end
31
+
32
+ def self.return_value
33
+ # If your method provides a return value, you can describe here what it does
34
+ end
35
+
36
+ def self.details
37
+ 'Resumes a halted staged rollout for a specific version on a Google Play track.'
38
+ end
39
+
40
+ def self.available_options
41
+ [
42
+ FastlaneCore::ConfigItem.new(key: :package_name,
43
+ env_name: 'RESUME_GOOGLE_PLAY_RELEASE_PACKAGE_NAME',
44
+ description: 'The package name of the application. e.g. \'com.example.app\'',
45
+ optional: false,
46
+ type: String),
47
+ FastlaneCore::ConfigItem.new(key: :track,
48
+ env_name: "RESUME_GOOGLE_PLAY_RELEASE_TRACK",
49
+ description: 'The track of the application. The available tracks are: production, beta, alpha, internal',
50
+ optional: false,
51
+ type: String),
52
+ FastlaneCore::ConfigItem.new(key: :version_name,
53
+ env_name: 'RESUME_GOOGLE_PLAY_RELEASE_VERSION_NAME',
54
+ description: 'The version name to update. e.g. \'1.0.0\'',
55
+ optional: false,
56
+ type: String),
57
+ FastlaneCore::ConfigItem.new(key: :json_file_path,
58
+ env_name: 'RESUME_GOOGLE_PLAY_RELEASE_JSON_FILE_PATH',
59
+ description: 'The path to a file containing service account or external account JSON, used to authenticate with Google',
60
+ optional: true,
61
+ type: String),
62
+ FastlaneCore::ConfigItem.new(key: :json_key_data,
63
+ env_name: 'RESUME_GOOGLE_PLAY_RELEASE_JSON_KEY_DATA',
64
+ description: 'The file data containing service account or external account JSON, used to authenticate with Google',
65
+ optional: true,
66
+ type: String)
67
+
68
+ ]
69
+ end
70
+
71
+ def self.is_supported?(platform)
72
+ # Adjust this if your plugin only works for a particular platform (iOS vs. Android, for example)
73
+ # See: https://docs.fastlane.tools/advanced/#control-configuration-by-lane-and-by-platform
74
+ #
75
+ # [:ios, :mac, :android].include?(platform)
76
+
77
+ platform == :android
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,88 @@
1
+ require 'fastlane/action'
2
+ require_relative '../client'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ class UpdateGooglePlayReleaseRolloutAction < Action
7
+ def self.run(params)
8
+ package_name = params[:package_name]
9
+ track = params[:track]
10
+ version_name = params[:version_name]
11
+ user_fraction = params[:user_fraction]
12
+ json_file_path = params[:json_file_path]
13
+ json_key_data = params[:json_key_data]
14
+ client = Fastlane::GooglePlayTrackUpdater::GooglePlayClient.new(
15
+ json_file_path: json_file_path,
16
+ json_key_data: json_key_data
17
+ )
18
+ client.update_rollout(
19
+ package_name: package_name,
20
+ track: track,
21
+ version_name: version_name,
22
+ user_fraction: user_fraction
23
+ )
24
+ end
25
+
26
+ def self.description
27
+ 'Updates the rollout percentage for a staged rollout on a Google Play track.'
28
+ end
29
+
30
+ def self.authors
31
+ ["Takuma Homma"]
32
+ end
33
+
34
+ def self.return_value
35
+ # If your method provides a return value, you can describe here what it does
36
+ end
37
+
38
+ def self.details
39
+ 'Updates the rollout percentage for a staged rollout on a Google Play track.'
40
+ end
41
+
42
+ def self.available_options
43
+ [
44
+ FastlaneCore::ConfigItem.new(key: :package_name,
45
+ env_name: 'UPDATE_GOOGLE_PLAY_RELEASE_ROLLOUT_PACKAGE_NAME',
46
+ description: 'The package name of the application. e.g. \'com.example.app\'',
47
+ optional: false,
48
+ type: String),
49
+ FastlaneCore::ConfigItem.new(key: :track,
50
+ env_name: "UPDATE_GOOGLE_PLAY_RELEASE_ROLLOUT_TRACK",
51
+ description: 'The track of the application. The available tracks are: production, beta, alpha, internal',
52
+ optional: false,
53
+ type: String),
54
+ FastlaneCore::ConfigItem.new(key: :version_name,
55
+ env_name: 'UPDATE_GOOGLE_PLAY_RELEASE_ROLLOUT_VERSION_NAME',
56
+ description: 'The version name to update. e.g. \'1.0.0\'',
57
+ optional: false,
58
+ type: String),
59
+ FastlaneCore::ConfigItem.new(key: :user_fraction,
60
+ env_name: 'UPDATE_GOOGLE_PLAY_RELEASE_ROLLOUT_USER_FRACTION',
61
+ description: 'The rollout percentage as a fraction (0.0 to 1.0, exclusive). e.g. 0.1 for 10% rollout',
62
+ optional: false,
63
+ type: Float),
64
+ FastlaneCore::ConfigItem.new(key: :json_file_path,
65
+ env_name: 'UPDATE_GOOGLE_PLAY_RELEASE_ROLLOUT_JSON_FILE_PATH',
66
+ description: 'The path to a file containing service account or external account JSON, used to authenticate with Google',
67
+ optional: true,
68
+ type: String),
69
+ FastlaneCore::ConfigItem.new(key: :json_key_data,
70
+ env_name: 'UPDATE_GOOGLE_PLAY_RELEASE_ROLLOUT_JSON_KEY_DATA',
71
+ description: 'The file data containing service account or external account JSON, used to authenticate with Google',
72
+ optional: true,
73
+ type: String)
74
+
75
+ ]
76
+ end
77
+
78
+ def self.is_supported?(platform)
79
+ # Adjust this if your plugin only works for a particular platform (iOS vs. Android, for example)
80
+ # See: https://docs.fastlane.tools/advanced/#control-configuration-by-lane-and-by-platform
81
+ #
82
+ # [:ios, :mac, :android].include?(platform)
83
+
84
+ platform == :android
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,240 @@
1
+ require 'googleauth'
2
+ require 'google/apis/androidpublisher_v3'
3
+ require 'json'
4
+
5
+ module Fastlane
6
+ module GooglePlayTrackUpdater
7
+ class GooglePlayClient
8
+ attr_accessor :android_publisher_service
9
+
10
+ AndroidPublisher = Google::Apis::AndroidpublisherV3
11
+
12
+ # Initializes a new GooglePlayClient instance with authentication credentials
13
+ #
14
+ # @param json_file_path [String, nil] Path to a file containing service account or external account JSON
15
+ # @param json_key_data [String, nil] Service account or external account JSON data as a string
16
+ # @raise [FastlaneCore::Interface::FastlaneError] If neither or both authentication parameters are provided
17
+ # @raise [FastlaneCore::Interface::FastlaneError] If the JSON type is not 'service_account' or 'external_account'
18
+ def initialize(json_file_path: nil, json_key_data: nil)
19
+ if json_file_path.nil? && json_key_data.nil?
20
+ UI.user_error!('Specify exactly one of \'json_file_path: \' or \'json_key_data: \' for service/external account authentication.')
21
+ end
22
+
23
+ account_raw_json = if json_file_path
24
+ File.open(File.expand_path(json_file_path))
25
+ elsif json_key_data
26
+ StringIO.new(json_key_data)
27
+ end
28
+ account_json = JSON.parse(account_raw_json.read)
29
+ account_raw_json.rewind
30
+
31
+ case account_json['type']
32
+ when 'external_account'
33
+ auth_client = Google::Auth::ExternalAccount::Credentials.make_creds(json_key_io: account_raw_json, scope: AndroidPublisher::AUTH_ANDROIDPUBLISHER)
34
+ when 'service_account'
35
+ auth_client = Google::Auth::ServiceAccountCredentials.make_creds(json_key_io: account_raw_json, scope: AndroidPublisher::AUTH_ANDROIDPUBLISHER)
36
+ else
37
+ UI.user_error!("Invalid Google Credentials JSON: type: #{account_json['type']} is not available.")
38
+ end
39
+
40
+ auth_client.fetch_access_token!
41
+
42
+ service = AndroidPublisher::AndroidPublisherService.new
43
+ service.authorization = auth_client
44
+ self.android_publisher_service = service
45
+ end
46
+
47
+ # Halts an active staged rollout or completed release for a specific version on a Google Play track
48
+ #
49
+ # @param package_name [String] The package name of the application (e.g., 'com.example.app')
50
+ # @param track [String] The track of the application (production, beta, alpha, internal)
51
+ # @param version_name [String] The version name to halt (e.g., '1.0.0')
52
+ # @raise [FastlaneCore::Interface::FastlaneError] If the release with the specified version is not found
53
+ # @raise [Google::Apis::Error] If the API request fails
54
+ def halt_release(package_name:, track:, version_name:)
55
+ validate_inputs_release_status(package_name: package_name, track: track, version_name: version_name)
56
+
57
+ begin
58
+ edit = android_publisher_service.insert_edit(package_name)
59
+ edit_id = edit.id
60
+
61
+ current_track = android_publisher_service.get_edit_track(package_name, edit_id, track)
62
+
63
+ target_releases = current_track.releases.select do |release|
64
+ release.name == version_name
65
+ end
66
+
67
+ if target_releases.empty?
68
+ UI.user_error!("Could not find a release with version '#{version_name}' on track: '#{track}'.")
69
+ end
70
+
71
+ is_halted = false
72
+
73
+ target_releases.each do |release|
74
+ next unless release.status == 'completed' || release.status == 'inProgress'
75
+
76
+ release.status = 'halted'
77
+ UI.message("Preparing to halt release for version '#{version_name}' on track: #{track}...")
78
+ is_halted = true
79
+ end
80
+
81
+ if is_halted
82
+ android_publisher_service.update_edit_track(package_name, edit_id, track, current_track)
83
+ android_publisher_service.commit_edit(package_name, edit_id)
84
+ UI.success("Successfully changed status to 'halted' for version '#{version_name}' on track: #{track}.")
85
+ else
86
+ UI.message("No releases found to halt for version '#{version_name}' on track: #{track}.")
87
+ end
88
+ rescue Google::Apis::Error => e
89
+ UI.error!("Failed to halt release for version '#{version_name}' on track: #{track}. Google Api Error: #{e.message}")
90
+ end
91
+ end
92
+
93
+ # Resumes a halted staged rollout for a specific version on a Google Play track
94
+ #
95
+ # Changes the status from 'halted' to either 'completed' (if no user_fraction is set)
96
+ # or 'inProgress' (if user_fraction is set for staged rollout)
97
+ #
98
+ # @param package_name [String] The package name of the application (e.g., 'com.example.app')
99
+ # @param track [String] The track of the application (production, beta, alpha, internal)
100
+ # @param version_name [String] The version name to resume (e.g., '1.0.0')
101
+ # @raise [FastlaneCore::Interface::FastlaneError] If the release with the specified version is not found
102
+ # @raise [Google::Apis::Error] If the API request fails
103
+ def resume_release(package_name:, track:, version_name:)
104
+ validate_inputs_release_status(package_name: package_name, track: track, version_name: version_name)
105
+
106
+ begin
107
+ edit = android_publisher_service.insert_edit(package_name)
108
+ edit_id = edit.id
109
+
110
+ completed_changed = false
111
+ in_progress_changed = false
112
+
113
+ current_track = android_publisher_service.get_edit_track(package_name, edit_id, track)
114
+
115
+ target_releases = current_track.releases.select do |release|
116
+ release.name == version_name
117
+ end
118
+
119
+ if target_releases.empty?
120
+ UI.user_error!("Could not find a release with version '#{version_name}' on track: '#{track}'.")
121
+ end
122
+
123
+ target_releases.each do |release|
124
+ next unless release.status == 'halted'
125
+
126
+ if release.user_fraction.nil?
127
+ release.status = 'completed'
128
+ completed_changed = true
129
+ else
130
+ release.status = 'inProgress'
131
+ in_progress_changed = true
132
+ end
133
+ UI.message("Preparing to resume release for version '#{version_name}' on track: #{track}...")
134
+ end
135
+
136
+ if completed_changed || in_progress_changed
137
+ android_publisher_service.update_edit_track(package_name, edit_id, track, current_track)
138
+ android_publisher_service.commit_edit(package_name, edit_id)
139
+ changed_release = if in_progress_changed
140
+ 'inProgress'
141
+ elsif completed_changed
142
+ 'completed'
143
+ end
144
+ UI.success("Successfully changed status to #{changed_release || 'completed'} for version '#{version_name}' on track: #{track}.")
145
+ else
146
+ UI.message("No halted releases found for version '#{version_name}' on track: #{track}.")
147
+ end
148
+ rescue Google::Apis::Error => e
149
+ UI.error!("Failed to resume release for '#{version_name}' on track: #{track}. Google Api Error: #{e.message}")
150
+ end
151
+ end
152
+
153
+ # Updates the rollout percentage for a staged rollout on a Google Play track
154
+ #
155
+ # Only updates releases with 'inProgress' status
156
+ #
157
+ # @param package_name [String] The package name of the application (e.g., 'com.example.app')
158
+ # @param track [String] The track of the application (production, beta, alpha, internal)
159
+ # @param version_name [String] The version name to update (e.g., '1.0.0')
160
+ # @param user_fraction [Float] The rollout percentage as a fraction (0.0 to 1.0, exclusive). e.g., 0.1 for 10% rollout
161
+ # @raise [FastlaneCore::Interface::FastlaneError] If the release with the specified version is not found
162
+ # @raise [FastlaneCore::Interface::FastlaneError] If user_fraction is not within the valid range (0.0, 1.0)
163
+ # @raise [Google::Apis::Error] If the API request fails
164
+ def update_rollout(package_name:, track:, version_name:, user_fraction:)
165
+ validate_inputs_rollout(package_name: package_name, track: track, version_name: version_name, user_fraction: user_fraction)
166
+
167
+ begin
168
+ edit = android_publisher_service.insert_edit(package_name)
169
+ edit_id = edit.id
170
+
171
+ current_track = android_publisher_service.get_edit_track(package_name, edit_id, track)
172
+
173
+ target_releases = current_track.releases.select do |release|
174
+ release.name == version_name
175
+ end
176
+
177
+ if target_releases.empty?
178
+ UI.user_error!("Could not find a release with version '#{version_name}' on track: '#{track}'.")
179
+ end
180
+
181
+ is_rollout_updated = false
182
+
183
+ target_releases.each do |release|
184
+ next unless release.status == 'inProgress'
185
+
186
+ release.user_fraction = user_fraction.to_f
187
+ UI.verbose("Preparing to update rollout to #{user_fraction} for version '#{version_name}' on track: #{track}...")
188
+ is_rollout_updated = true
189
+ end
190
+
191
+ if is_rollout_updated
192
+ android_publisher_service.update_edit_track(package_name, edit_id, track, current_track)
193
+ android_publisher_service.commit_edit(package_name, edit_id)
194
+ UI.success("Successfully updated rollout to #{user_fraction} for version '#{version_name}' on track: #{track}.")
195
+ else
196
+ UI.message("No inProgress releases found to update rollout for version '#{version_name}' on track: #{track}.")
197
+ end
198
+ rescue Google::Apis::Error => e
199
+ UI.error!("Failed to update rollout for version '#{version_name}' on track: #{track}. Google Api Error: #{e.message}")
200
+ end
201
+ end
202
+
203
+ private
204
+
205
+ def validate_inputs_release_status(package_name:, track:, version_name:)
206
+ if package_name.nil? || package_name.empty?
207
+ UI.user_error!('Specify the package name using the \'package_name:\' .')
208
+ end
209
+
210
+ if track.nil? || track.empty?
211
+ UI.user_error!('Specify the track using the \'track:\' .')
212
+ end
213
+
214
+ if version_name.nil? || version_name.empty?
215
+ UI.user_error!('Specify the version_name using the \'version_name:\' .')
216
+ end
217
+ end
218
+
219
+ def validate_inputs_rollout(package_name:, track:, version_name:, user_fraction:)
220
+ if package_name.nil? || package_name.empty?
221
+ UI.user_error!('Specify the package name using the \'package_name:\' .')
222
+ end
223
+
224
+ if track.nil? || track.empty?
225
+ UI.user_error!('Specify the track using the \'track:\' .')
226
+ end
227
+
228
+ if version_name.nil? || version_name.empty?
229
+ UI.user_error!('Specify the version_name using the \'version_name:\' .')
230
+ end
231
+
232
+ fraction = user_fraction&.to_f
233
+
234
+ if fraction.nil? || fraction <= 0.0 || fraction >= 1.0
235
+ UI.user_error!('Invalid \'user_fraction:\' provided. Please specify a value from 0 and 1 (exclusive). e.g., 0.1 for 10% rollout.')
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,13 @@
1
+ require 'fastlane_core/ui/ui'
2
+
3
+ module Fastlane
4
+ UI = FastlaneCore::UI unless Fastlane.const_defined?(:UI)
5
+
6
+ module Helper
7
+ # class GooglePlayTrackUpdaterHelper
8
+ # class methods that you define here become available in your action
9
+ # as `Helper::GooglePlayTrackUpdaterHelper.your_method`
10
+
11
+ # end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module Fastlane
2
+ module GooglePlayTrackUpdater
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ require 'fastlane/plugin/google_play_track_updater/version'
2
+
3
+ module Fastlane
4
+ module GooglePlayTrackUpdater
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::GooglePlayTrackUpdater.all_classes.each do |current|
15
+ require current
16
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-google_play_track_updater
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Takuma Homma
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: google-apis-androidpublisher_v3
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.3'
26
+ description: fastlane plugin for Google Play track management. Includes halt_google_play_release,
27
+ resume_google_play_release, and update_google_play_release_rollout actions to control
28
+ release statuses and rollout fractions.
29
+ email: nagomimatcha@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE
35
+ - README.md
36
+ - lib/fastlane/plugin/google_play_track_updater.rb
37
+ - lib/fastlane/plugin/google_play_track_updater/actions/halt_google_play_release_action.rb
38
+ - lib/fastlane/plugin/google_play_track_updater/actions/resume_google_play_release_action.rb
39
+ - lib/fastlane/plugin/google_play_track_updater/actions/update_google_play_release_rollout_action.rb
40
+ - lib/fastlane/plugin/google_play_track_updater/client.rb
41
+ - lib/fastlane/plugin/google_play_track_updater/helper/google_play_track_updater_helper.rb
42
+ - lib/fastlane/plugin/google_play_track_updater/version.rb
43
+ homepage: https://github.com/mataku/fastlane-plugin-google_play_track_updater
44
+ licenses:
45
+ - MIT
46
+ metadata:
47
+ rubygems_mfa_required: 'true'
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '2.6'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.6.9
63
+ specification_version: 4
64
+ summary: Control Google Play tracks by halting, resuming, or updating rollout fractions.
65
+ test_files: []