fastlane-plugin-sync_devices 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 +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE +21 -0
- data/README.md +128 -0
- data/lib/fastlane/plugin/sync_devices/actions/sync_devices_action.rb +222 -0
- data/lib/fastlane/plugin/sync_devices/helper/sync_devices_helper/command.rb +174 -0
- data/lib/fastlane/plugin/sync_devices/helper/sync_devices_helper/device_patch.rb +116 -0
- data/lib/fastlane/plugin/sync_devices/helper/sync_devices_helper/devices_file.rb +325 -0
- data/lib/fastlane/plugin/sync_devices/helper/sync_devices_helper/devices_patch.rb +50 -0
- data/lib/fastlane/plugin/sync_devices/helper/sync_devices_helper.rb +13 -0
- data/lib/fastlane/plugin/sync_devices/version.rb +8 -0
- data/lib/fastlane/plugin/sync_devices.rb +18 -0
- metadata +56 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a36daeccb8615f994ee412d9f469e416698636b975796152a2b2c8f27d889b43
|
4
|
+
data.tar.gz: 29a12e29ba18a01f7958eb98ac1b2c4e97a22a4c1f256f3521cf4f67bed4f529
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e3987bece7d43b523a1df6cf1303b39511f310ae2701c0903f385d2e714a7ff61f734f8c21daa3d884de50c118557f0793db7e71337dc748bdaa245684ae335a
|
7
|
+
data.tar.gz: a6ed24116431af31c5cd6fe141c9fc365de7be8ebcb88e3631eb1d461e24958c6c98aec0730d500aae4dbc85d53173db414634ffb7b58a8ae834d708ea8b2134
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Ryosuke Ito <rito.0305@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,128 @@
|
|
1
|
+
# sync\_devices plugin
|
2
|
+
|
3
|
+
[](https://rubygems.org/gems/fastlane-plugin-sync_devices)
|
4
|
+
[](https://github.com/manicmaniac/fastlane-plugin-sync_devices/actions/workflows/test.yml)
|
5
|
+
|
6
|
+
## Getting Started
|
7
|
+
|
8
|
+
This project is a [fastlane](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-sync_devices`, add it to your project by running:
|
9
|
+
|
10
|
+
```bash
|
11
|
+
fastlane add_plugin sync_devices
|
12
|
+
```
|
13
|
+
|
14
|
+
## About sync\_devices
|
15
|
+
|
16
|
+
This plugin provides a single action `sync_devices`.
|
17
|
+
|
18
|
+
`sync_devices` synchronizes your devices with Apple Developer Portal.
|
19
|
+
|
20
|
+
This plugin works similarly to fastlane official [register\_devices](https://docs.fastlane.tools/actions/register_devices/) plugin, but `sync_devices` can disable, enable and rename devices on Apple Developer Portal while `register_devices` is only capable to create new devices.
|
21
|
+
|
22
|
+
Since we can only actually _delete_ a device once a year, `sync_devices` does not _delete_ devices but just disables them when they were removed from a devices file. It's safe because you can re-enable devices whenever you want.
|
23
|
+
|
24
|
+
## Basic Usage
|
25
|
+
|
26
|
+
First of all, you need to create your own `devices.tsv` under your project repository. It is a simple tab-separated text file like the following example.
|
27
|
+
|
28
|
+
```
|
29
|
+
Device ID Device Name Device Platform
|
30
|
+
01234567-89ABCDEF01234567 NAME1 ios
|
31
|
+
abcdef0123456789abcdef0123456789abcdef01 NAME2 ios
|
32
|
+
01234567-89AB-CDEF-0123-4567890ABCDE NAME3 mac
|
33
|
+
ABCDEF01-2345-6789-ABCD-EF0123456789 NAME4 mac
|
34
|
+
```
|
35
|
+
|
36
|
+
Then you can run `sync_devices` from command line.
|
37
|
+
|
38
|
+
Run `sync_devices` in dry-run mode, which does not change remote devices, so that you can see what will be done when it actually runs.
|
39
|
+
|
40
|
+
```
|
41
|
+
fastlane run sync_devices devices_file:devices.tsv dry_run:true
|
42
|
+
```
|
43
|
+
|
44
|
+
After carefully checking if the result is the same as expected, run
|
45
|
+
|
46
|
+
```
|
47
|
+
fastlane run sync_devices devices_file:devices.tsv
|
48
|
+
```
|
49
|
+
|
50
|
+
You will see the remote devices are synchronized with your devices.tsv.
|
51
|
+
|
52
|
+
|
53
|
+
## Advanced Usage
|
54
|
+
|
55
|
+
### Use Property List file instead of TSV
|
56
|
+
|
57
|
+
Apple Developer Portal also accepts a devices file in Property List format like this.
|
58
|
+
|
59
|
+
```xml
|
60
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
61
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
62
|
+
<plist version="1.0">
|
63
|
+
<dict>
|
64
|
+
<key>Device UDIDs</key>
|
65
|
+
<array>
|
66
|
+
<dict>
|
67
|
+
<key>deviceIdentifier</key>
|
68
|
+
<string>01234567-89ABCDEF01234567</string>
|
69
|
+
<key>deviceName</key>
|
70
|
+
<string>NAME1</string>
|
71
|
+
<key>devicePlatform</key>
|
72
|
+
<string>ios</string>
|
73
|
+
</dict>
|
74
|
+
</array>
|
75
|
+
</dict>
|
76
|
+
</plist>
|
77
|
+
```
|
78
|
+
|
79
|
+
If you want to use Property List format, just pass the file to `sync_devices`.
|
80
|
+
|
81
|
+
```
|
82
|
+
fastlane run sync_devices devices_file:devices.deviceids
|
83
|
+
```
|
84
|
+
|
85
|
+
Following Apple's guide, I added `.deviceids` file extension but you can use standard `.xml` or `.plist` as well.
|
86
|
+
|
87
|
+
```
|
88
|
+
fastlane run sync_devices devices_file:devices.xml
|
89
|
+
```
|
90
|
+
|
91
|
+
## Example
|
92
|
+
|
93
|
+
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`.
|
94
|
+
|
95
|
+
## Run tests for this plugin
|
96
|
+
|
97
|
+
To run both the tests, and code style validation, run
|
98
|
+
|
99
|
+
```
|
100
|
+
bundle exec rake
|
101
|
+
```
|
102
|
+
|
103
|
+
To automatically fix many of the styling issues, use
|
104
|
+
```
|
105
|
+
bundle exec rake rubocop:autocorrect
|
106
|
+
```
|
107
|
+
|
108
|
+
You can check other useful tasks by running
|
109
|
+
|
110
|
+
```
|
111
|
+
bundle exec rake -T
|
112
|
+
```
|
113
|
+
|
114
|
+
## Issues and Feedback
|
115
|
+
|
116
|
+
For any other issues and feedback about this plugin, please submit it to [this repository](https://github.com/manicmaniac/fastlane-plugin-sync_devices).
|
117
|
+
|
118
|
+
## Troubleshooting
|
119
|
+
|
120
|
+
If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
|
121
|
+
|
122
|
+
## Using _fastlane_ Plugins
|
123
|
+
|
124
|
+
For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
|
125
|
+
|
126
|
+
## About _fastlane_
|
127
|
+
|
128
|
+
_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,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'credentials_manager'
|
4
|
+
require 'fastlane/action'
|
5
|
+
require_relative '../helper/sync_devices_helper'
|
6
|
+
|
7
|
+
module Fastlane # rubocop:disable Style/Documentation
|
8
|
+
# @see https://rubydoc.info/gems/fastlane/FastlaneCore/UI
|
9
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?(:UI)
|
10
|
+
|
11
|
+
# @see https://rubydoc.info/gems/fastlane/Fastlane/Actions
|
12
|
+
module Actions
|
13
|
+
# Fastlane action to synchronize remote devices on Apple Developer Portal with local devices file.
|
14
|
+
class SyncDevicesAction < Action # rubocop:disable Metrics/ClassLength
|
15
|
+
include Fastlane::Helper::SyncDevicesHelper
|
16
|
+
|
17
|
+
# The main entry point of this plugin.
|
18
|
+
#
|
19
|
+
# @param params [Hash] options passed to this plugin. See {.available_options} for details.
|
20
|
+
# @option params [Boolean] :dry_run
|
21
|
+
# @option params [String] :devices_file
|
22
|
+
# @option params [String, nil] :api_key_path
|
23
|
+
# @option params [Hash, nil] :api_key
|
24
|
+
# @option params [String, nil] :team_id
|
25
|
+
# @option params [String, nil] :team_name
|
26
|
+
# @option params [String, nil] :username
|
27
|
+
# @return [void]
|
28
|
+
# @see https://rubydoc.info/gems/fastlane/Fastlane%2FAction.run Fastlane::Action.run
|
29
|
+
def self.run(params)
|
30
|
+
require 'spaceship/connect_api'
|
31
|
+
|
32
|
+
devices_file = params[:devices_file]
|
33
|
+
UI.user_error!('You must pass `devices_file`. Please check the readme.') unless devices_file
|
34
|
+
spaceship_login(params)
|
35
|
+
new_devices = DevicesFile.load(devices_file)
|
36
|
+
|
37
|
+
UI.message('Fetching list of currently registered devices...')
|
38
|
+
current_devices = Spaceship::ConnectAPI::Device.all
|
39
|
+
patch = DevicesPatch.new(current_devices, new_devices)
|
40
|
+
patch.apply!(dry_run: params[:dry_run])
|
41
|
+
|
42
|
+
UI.success('Successfully registered new devices.')
|
43
|
+
end
|
44
|
+
|
45
|
+
# Authenticates the user with AppStore Connect API or Apple Developer Portal.
|
46
|
+
#
|
47
|
+
# @param (see .run)
|
48
|
+
# @return [void]
|
49
|
+
def self.spaceship_login(params)
|
50
|
+
api_token = Spaceship::ConnectAPI::Token.from(hash: params[:api_key], filepath: params[:api_key_path])
|
51
|
+
if api_token
|
52
|
+
UI.message('Creating authorization token for App Store Connect API')
|
53
|
+
Spaceship::ConnectAPI.token = api_token
|
54
|
+
elsif Spaceship::ConnectAPI.token
|
55
|
+
UI.message('Using existing authorization token for App Store Connect API')
|
56
|
+
else
|
57
|
+
UI.message("Login to App Store Connect (#{params[:username]})")
|
58
|
+
credentials = CredentialsManager::AccountManager.new(user: params[:username])
|
59
|
+
Spaceship::ConnectAPI.login(credentials.user, credentials.password, use_portal: true, use_tunes: false)
|
60
|
+
UI.message('Login Successful')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
private_class_method :spaceship_login
|
64
|
+
|
65
|
+
# Description of this plugin.
|
66
|
+
#
|
67
|
+
# @return [String]
|
68
|
+
# @see https://rubydoc.info/gems/fastlane/Fastlane%2FAction.description Fastlane::Action.description
|
69
|
+
def self.description
|
70
|
+
'Synchronize your devices with Apple Developer Portal.'
|
71
|
+
end
|
72
|
+
|
73
|
+
# Authors of this plugin.
|
74
|
+
#
|
75
|
+
# @return [Array<String>]
|
76
|
+
# @see https://rubydoc.info/gems/fastlane/Fastlane%2FAction.authors Fastlane::Action.authors
|
77
|
+
def self.authors
|
78
|
+
['Ryosuke Ito']
|
79
|
+
end
|
80
|
+
|
81
|
+
# Detailed description of this plugin.
|
82
|
+
#
|
83
|
+
# @return [String]
|
84
|
+
# @see https://rubydoc.info/gems/fastlane/Fastlane%2FAction.details Fastlane::Action.details
|
85
|
+
def self.details
|
86
|
+
<<~DETAILS
|
87
|
+
This will synchronize iOS/Mac devices with the Apple Developer Portal so that you can include them in your provisioning profiles.
|
88
|
+
Unlike `register_devices` action, this action may disable, enable or rename devices.
|
89
|
+
Maybe it sounds dangerous but actually it does not delete anything, so you can recover the changes by yourself if needed.
|
90
|
+
|
91
|
+
The action will connect to the Apple Developer Portal using AppStore Connect API.
|
92
|
+
DETAILS
|
93
|
+
end
|
94
|
+
|
95
|
+
# Available options of this plugin.
|
96
|
+
#
|
97
|
+
# @return [Array<FastlaneCore::ConfigItem>]
|
98
|
+
#
|
99
|
+
# @see https://rubydoc.info/gems/fastlane/Fastlane%2FAction.available_options Fastlane::Action.available_options
|
100
|
+
# @see https://rubydoc.info/gems/fastlane/FastlaneCore/ConfigItem FastlaneCore::ConfigItem
|
101
|
+
def self.available_options # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
102
|
+
[
|
103
|
+
FastlaneCore::ConfigItem.new(
|
104
|
+
key: :dry_run,
|
105
|
+
env_name: 'FL_SYNC_DEVICES_DRY_RUN',
|
106
|
+
description: 'Do not modify the registered devices but just print what will be done',
|
107
|
+
type: Boolean,
|
108
|
+
default_value: false,
|
109
|
+
optional: true
|
110
|
+
),
|
111
|
+
FastlaneCore::ConfigItem.new(
|
112
|
+
key: :devices_file,
|
113
|
+
env_name: 'FL_SYNC_DEVICES_FILE',
|
114
|
+
description: 'Provide a path to a file with the devices to register. ' \
|
115
|
+
'For the format of the file see the examples',
|
116
|
+
optional: true,
|
117
|
+
verify_block: proc do |value|
|
118
|
+
UI.user_error!("Could not find file '#{value}'") unless File.exist?(value)
|
119
|
+
end
|
120
|
+
),
|
121
|
+
FastlaneCore::ConfigItem.new(
|
122
|
+
key: :api_key_path,
|
123
|
+
env_names: %w[FL_SYNC_DEVICES_API_KEY_PATH APP_STORE_CONNECT_API_KEY_PATH],
|
124
|
+
description: 'Path to your App Store Connect API Key JSON file ' \
|
125
|
+
'(https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-json-file)',
|
126
|
+
optional: true,
|
127
|
+
conflicting_options: [:api_key],
|
128
|
+
verify_block: proc do |value|
|
129
|
+
UI.user_error!("Couldn't find API key JSON file at path '#{value}'") unless File.exist?(value)
|
130
|
+
end
|
131
|
+
),
|
132
|
+
FastlaneCore::ConfigItem.new(
|
133
|
+
key: :api_key,
|
134
|
+
env_names: %w[FL_SYNC_DEVICES_API_KEY APP_STORE_CONNECT_API_KEY],
|
135
|
+
description: 'Your App Store Connect API Key information ' \
|
136
|
+
'(https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-hash-option)',
|
137
|
+
type: Hash,
|
138
|
+
default_value: Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::APP_STORE_CONNECT_API_KEY],
|
139
|
+
default_value_dynamic: true,
|
140
|
+
optional: true,
|
141
|
+
sensitive: true,
|
142
|
+
conflicting_options: [:api_key_path]
|
143
|
+
),
|
144
|
+
FastlaneCore::ConfigItem.new(
|
145
|
+
key: :team_id,
|
146
|
+
env_name: 'SYNC_DEVICES_TEAM_ID',
|
147
|
+
code_gen_sensitive: true,
|
148
|
+
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:team_id),
|
149
|
+
default_value_dynamic: true,
|
150
|
+
description: "The ID of your Developer Portal team if you're in multiple teams",
|
151
|
+
optional: true,
|
152
|
+
verify_block: proc do |value|
|
153
|
+
ENV['FASTLANE_TEAM_ID'] = value.to_s
|
154
|
+
end
|
155
|
+
),
|
156
|
+
FastlaneCore::ConfigItem.new(
|
157
|
+
key: :team_name,
|
158
|
+
env_name: 'SYNC_DEVICES_TEAM_NAME',
|
159
|
+
description: "The name of your Developer Portal team if you're in multiple teams",
|
160
|
+
optional: true,
|
161
|
+
code_gen_sensitive: true,
|
162
|
+
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:team_name),
|
163
|
+
default_value_dynamic: true,
|
164
|
+
verify_block: proc do |value|
|
165
|
+
ENV['FASTLANE_TEAM_NAME'] = value.to_s
|
166
|
+
end
|
167
|
+
),
|
168
|
+
FastlaneCore::ConfigItem.new(
|
169
|
+
key: :username,
|
170
|
+
env_name: 'DELIVER_USER',
|
171
|
+
description: 'Optional: Your Apple ID',
|
172
|
+
optional: true,
|
173
|
+
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:apple_id),
|
174
|
+
default_value_dynamic: true
|
175
|
+
)
|
176
|
+
]
|
177
|
+
end
|
178
|
+
|
179
|
+
# Whether if this plugin is supported on the platform.
|
180
|
+
#
|
181
|
+
# @param _platform [Symbol] unused
|
182
|
+
# @return [true]
|
183
|
+
# @see https://rubydoc.info/gems/fastlane/Fastlane%2FAction.is_supported%3F Fastlane::Action.is_supported?
|
184
|
+
def self.is_supported?(_platform) # rubocop:disable Naming/PredicateName
|
185
|
+
true
|
186
|
+
end
|
187
|
+
|
188
|
+
# Example usages of this plugin.
|
189
|
+
#
|
190
|
+
# @return [Array<String>]
|
191
|
+
# @see https://rubydoc.info/gems/fastlane/Fastlane%2FAction.example_code Fastlane::Action.example_code
|
192
|
+
def self.example_code
|
193
|
+
[
|
194
|
+
<<~EX_1,
|
195
|
+
# Provide TSV file
|
196
|
+
sync_devices(devices_file: '/path/to/devices.txt')
|
197
|
+
EX_1
|
198
|
+
<<~EX_2,
|
199
|
+
# Provide Property List file, with configuring credentials
|
200
|
+
sync_devices(
|
201
|
+
devices_file: '/path/to/devices.deviceids',
|
202
|
+
team_id: 'ABCDEFGHIJ',
|
203
|
+
api_key_path: '/path/to/api_key.json'
|
204
|
+
)
|
205
|
+
EX_2
|
206
|
+
<<~EX_3
|
207
|
+
# Just check what will occur
|
208
|
+
sync_devices(devices_file: '/path/to/devices.txt', dry_run: true)
|
209
|
+
EX_3
|
210
|
+
]
|
211
|
+
end
|
212
|
+
|
213
|
+
# Category of this plugin.
|
214
|
+
#
|
215
|
+
# @return [Symbol]
|
216
|
+
# @see https://rubydoc.info/gems/fastlane/Fastlane%2FAction.category Fastlane::Action.category
|
217
|
+
def self.category
|
218
|
+
:code_signing
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spaceship/connect_api'
|
4
|
+
|
5
|
+
module Fastlane
|
6
|
+
module Helper
|
7
|
+
module SyncDevicesHelper
|
8
|
+
# Namespace to organize command classes.
|
9
|
+
module Command
|
10
|
+
# Abstract base class of command object.
|
11
|
+
#
|
12
|
+
# @attr device [Spaceship::ConnectAPI::Device]
|
13
|
+
Base = Struct.new(:device) do
|
14
|
+
# Communicate with AppStore Connect and change the remote device.
|
15
|
+
# @abstract
|
16
|
+
# @return [void]
|
17
|
+
def run
|
18
|
+
# :nocov:
|
19
|
+
raise NotImplementedError
|
20
|
+
# :nocov:
|
21
|
+
end
|
22
|
+
|
23
|
+
# Description of this command.
|
24
|
+
# @abstract
|
25
|
+
# @return [String]
|
26
|
+
def description
|
27
|
+
# :nocov:
|
28
|
+
raise NotImplementedError
|
29
|
+
# :nocov:
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Command to do nothing.
|
34
|
+
class Noop < Base
|
35
|
+
# @return (see Base#run)
|
36
|
+
def run
|
37
|
+
# Does nothing.
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return (see Base#description)
|
41
|
+
def description
|
42
|
+
"Skipped #{device.name} (#{device.udid})"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Command to disable an existing device.
|
47
|
+
# @see https://developer.apple.com/documentation/appstoreconnectapi/modify_a_registered_device
|
48
|
+
class Disable < Base
|
49
|
+
# @return (see Base#run)
|
50
|
+
def run
|
51
|
+
Spaceship::ConnectAPI::Device.disable(device.udid)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return (see Base#description)
|
55
|
+
def description
|
56
|
+
"Disabled #{device.name} (#{device.udid})"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Command to enable an existing device.
|
61
|
+
# @see https://developer.apple.com/documentation/appstoreconnectapi/modify_a_registered_device
|
62
|
+
class Enable < Base
|
63
|
+
# @return (see Base#run)
|
64
|
+
def run
|
65
|
+
Spaceship::ConnectAPI::Device.enable(device.udid)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return (see Base#description)
|
69
|
+
def description
|
70
|
+
"Enabled #{device.name} (#{device.udid})"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Command to rename an existing device.
|
75
|
+
# @see https://developer.apple.com/documentation/appstoreconnectapi/modify_a_registered_device
|
76
|
+
class Rename < Base
|
77
|
+
# @return [String]
|
78
|
+
attr_reader :name
|
79
|
+
|
80
|
+
# @param device [Spaceship::ConnectAPI::Device]
|
81
|
+
# @param name [String]
|
82
|
+
def initialize(device, name)
|
83
|
+
super(device)
|
84
|
+
@name = name
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return (see Base#run)
|
88
|
+
def run
|
89
|
+
Spaceship::ConnectAPI::Device.rename(device.udid, name)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return (see Base#description)
|
93
|
+
def description
|
94
|
+
"Renamed #{device.name} to #{name} (#{device.udid})"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Command to disable and rename an existing device.
|
99
|
+
# @see https://developer.apple.com/documentation/appstoreconnectapi/modify_a_registered_device
|
100
|
+
class DisableAndRename < Base
|
101
|
+
# @return [String]
|
102
|
+
attr_reader :name
|
103
|
+
|
104
|
+
# @param device [Spaceship::ConnectAPI::Device]
|
105
|
+
# @param name [String]
|
106
|
+
def initialize(device, name)
|
107
|
+
super(device)
|
108
|
+
@name = name
|
109
|
+
end
|
110
|
+
|
111
|
+
# @return (see Base#run)
|
112
|
+
def run
|
113
|
+
Spaceship::ConnectAPI::Device.modify(
|
114
|
+
device.udid,
|
115
|
+
enabled: false,
|
116
|
+
new_name: name
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
# @return (see Base#description)
|
121
|
+
def description
|
122
|
+
"Disabled and renamed #{device.name} to #{name} (#{device.udid})"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Command to enable and rename an existing device.
|
127
|
+
# @see https://developer.apple.com/documentation/appstoreconnectapi/modify_a_registered_device
|
128
|
+
class EnableAndRename < Base
|
129
|
+
# @return [String] name
|
130
|
+
attr_reader :name
|
131
|
+
|
132
|
+
# @param device [Spaceship::ConnectAPI::Device]
|
133
|
+
# @param name [String]
|
134
|
+
def initialize(device, name)
|
135
|
+
super(device)
|
136
|
+
@name = name
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return (see Base#run)
|
140
|
+
def run
|
141
|
+
Spaceship::ConnectAPI::Device.modify(
|
142
|
+
device.udid,
|
143
|
+
enabled: true,
|
144
|
+
new_name: name
|
145
|
+
)
|
146
|
+
end
|
147
|
+
|
148
|
+
# @return (see Base#description)
|
149
|
+
def description
|
150
|
+
"Enabled and renamed #{device.name} to #{name} (#{device.udid})"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Command to register a new device.
|
155
|
+
# @see https://developer.apple.com/documentation/appstoreconnectapi/register_a_new_device
|
156
|
+
class Create < Base
|
157
|
+
# @return (see Base#run)
|
158
|
+
def run
|
159
|
+
Spaceship::ConnectAPI::Device.create(
|
160
|
+
name: device.name,
|
161
|
+
platform: device.platform,
|
162
|
+
udid: device.udid
|
163
|
+
)
|
164
|
+
end
|
165
|
+
|
166
|
+
# @return (see Base#description)
|
167
|
+
def description
|
168
|
+
"Created #{device.name} (#{device.udid})"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'command'
|
4
|
+
|
5
|
+
module Fastlane
|
6
|
+
module Helper
|
7
|
+
module SyncDevicesHelper
|
8
|
+
# Represents differences between 2 devices.
|
9
|
+
class DevicePatch
|
10
|
+
# @return [Spaceship::ConnectAPI::Device]
|
11
|
+
attr_reader :old_device, :new_device
|
12
|
+
|
13
|
+
# @param old_device [Spaceship::ConnectAPI::Device, nil]
|
14
|
+
# @param new_device [Spaceship::ConnectAPI::Device, nil]
|
15
|
+
def initialize(old_device, new_device)
|
16
|
+
@old_device = old_device
|
17
|
+
@new_device = new_device
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Boolean]
|
21
|
+
def renamed?
|
22
|
+
!!old_device && !!new_device && old_device.name != new_device.name
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean]
|
26
|
+
def enabled?
|
27
|
+
!!old_device && !old_device.enabled? && !!new_device&.enabled?
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Boolean]
|
31
|
+
def disabled?
|
32
|
+
!!old_device && old_device.enabled? && !new_device&.enabled?
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Boolean]
|
36
|
+
def created?
|
37
|
+
old_device.nil? && !!new_device&.enabled?
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Boolean]
|
41
|
+
def platform_changed?
|
42
|
+
!!old_device && !!new_device && old_device.platform != new_device.platform
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Command::Base]
|
46
|
+
# @raise [UnsupportedOperation]
|
47
|
+
def command # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
48
|
+
raise UnsupportedOperation.change_platform(old_device, new_device) if platform_changed?
|
49
|
+
|
50
|
+
case [renamed?, enabled?, disabled?, created?]
|
51
|
+
when [false, false, false, false]
|
52
|
+
Command::Noop.new(old_device)
|
53
|
+
when [false, false, false, true]
|
54
|
+
Command::Create.new(new_device)
|
55
|
+
when [false, false, true, false]
|
56
|
+
Command::Disable.new(old_device)
|
57
|
+
when [false, true, false, false]
|
58
|
+
Command::Enable.new(old_device)
|
59
|
+
when [true, false, false, false]
|
60
|
+
Command::Rename.new(old_device, new_device.name)
|
61
|
+
when [true, false, true, false]
|
62
|
+
Command::DisableAndRename.new(old_device, new_device.name)
|
63
|
+
when [true, true, false, false]
|
64
|
+
Command::EnableAndRename.new(old_device, new_device.name)
|
65
|
+
else
|
66
|
+
# :nocov:
|
67
|
+
raise UnsupportedOperation.internal_inconsistency(self, old_device, new_device)
|
68
|
+
# :nocov:
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Generic error that is raised if no operation is defined in AppStore Connect API for diff of devices.
|
74
|
+
class UnsupportedOperation < StandardError
|
75
|
+
# @return [Spaceship::ConnectAPI::Device]
|
76
|
+
attr_reader :old_device, :new_device
|
77
|
+
|
78
|
+
# @param message [String]
|
79
|
+
# @param old_device [Spaceship::ConnectAPI::Device]
|
80
|
+
# @param new_device [Spaceship::ConnectAPI::Device]
|
81
|
+
def initialize(message, old_device, new_device)
|
82
|
+
super(message)
|
83
|
+
@old_device = old_device
|
84
|
+
@new_device = new_device
|
85
|
+
end
|
86
|
+
|
87
|
+
# @param old_device [Spaceship::ConnectAPI::Device]
|
88
|
+
# @param new_device [Spaceship::ConnectAPI::Device]
|
89
|
+
# @return [UnsupportedOperation]
|
90
|
+
def self.change_platform(old_device, new_device)
|
91
|
+
message = "Cannot change platform of the device '#{new_device.udid}' " \
|
92
|
+
"(#{old_device.platform} -> #{new_device.platform})"
|
93
|
+
new(message, old_device, new_device)
|
94
|
+
end
|
95
|
+
|
96
|
+
# @param patch [DevicePatch]
|
97
|
+
# @param old_device [Spaceship::ConnectAPI::Device]
|
98
|
+
# @param new_device [Spaceship::ConnectAPI::Device]
|
99
|
+
# @return [UnsupportedOperation]
|
100
|
+
def self.internal_inconsistency(patch, old_device, new_device)
|
101
|
+
info = {
|
102
|
+
renamed?: patch.renamed?,
|
103
|
+
enabled?: patch.enabled?,
|
104
|
+
disabled?: patch.disabled?,
|
105
|
+
created?: patch.created?
|
106
|
+
}
|
107
|
+
new(
|
108
|
+
"Cannot change #{old_device} to #{new_device} because of internal inconsistency. #{info}",
|
109
|
+
old_device,
|
110
|
+
new_device
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
module Helper
|
5
|
+
module SyncDevicesHelper
|
6
|
+
# Collection of methods that manipulates TSV or XML devices file.
|
7
|
+
#
|
8
|
+
# @see https://developer.apple.com/account/resources/downloads/Multiple-Upload-Samples.zip
|
9
|
+
module DevicesFile # rubocop:disable Metrics/ModuleLength
|
10
|
+
# Loads a devices file and parse it as an array of devices.
|
11
|
+
# If the file extension is one of +.deviceids+, +.plist+ and +.xml+, this method delegates to {.load_plist},
|
12
|
+
# otherwise to {.load_tsv}.
|
13
|
+
#
|
14
|
+
# @param path [String] path to the output file
|
15
|
+
# @return [Array<Spaceship::ConnectAPI::Device>]
|
16
|
+
def self.load(path)
|
17
|
+
return load_plist(path) if %w[.deviceids .plist .xml].include?(File.extname(path))
|
18
|
+
|
19
|
+
load_tsv(path)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param path [String]
|
23
|
+
# @return [Array<Spaceship::ConnectAPI::Device>]
|
24
|
+
def self.load_tsv(path)
|
25
|
+
require 'csv'
|
26
|
+
require 'spaceship/connect_api'
|
27
|
+
|
28
|
+
table = CSV.read(path, headers: true, col_sep: "\t")
|
29
|
+
validate_headers(table.headers, path)
|
30
|
+
|
31
|
+
devices = table.map.with_index(2) do |row, line_number|
|
32
|
+
validate_row(row, path, line_number)
|
33
|
+
Spaceship::ConnectAPI::Device.new(
|
34
|
+
nil,
|
35
|
+
{
|
36
|
+
name: row['Device Name'],
|
37
|
+
udid: row['Device ID'],
|
38
|
+
platform: parse_platform(row['Device Platform'], path),
|
39
|
+
status: Spaceship::ConnectAPI::Device::Status::ENABLED
|
40
|
+
}
|
41
|
+
)
|
42
|
+
end
|
43
|
+
validate_devices(devices, path)
|
44
|
+
devices
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param path [String]
|
48
|
+
# @return [Array<Spaceship::ConnectAPI::Device>]
|
49
|
+
def self.load_plist(path)
|
50
|
+
require 'cfpropertylist'
|
51
|
+
require 'spaceship/connect_api'
|
52
|
+
|
53
|
+
plist = CFPropertyList::List.new(file: path)
|
54
|
+
items = CFPropertyList.native_types(plist.value)['Device UDIDs']
|
55
|
+
devices = items.map.with_index do |item, index|
|
56
|
+
validate_dict_item(item, index, path)
|
57
|
+
Spaceship::ConnectAPI::Device.new(
|
58
|
+
nil,
|
59
|
+
{
|
60
|
+
name: item['deviceName'],
|
61
|
+
udid: item['deviceIdentifier'],
|
62
|
+
platform: parse_platform(item['devicePlatform'], path),
|
63
|
+
status: Spaceship::ConnectAPI::Device::Status::ENABLED
|
64
|
+
}
|
65
|
+
)
|
66
|
+
end
|
67
|
+
validate_devices(devices, path)
|
68
|
+
devices
|
69
|
+
end
|
70
|
+
|
71
|
+
# Dumps devices to devices file specified by path.
|
72
|
+
# This method delegates to either of {.dump_tsv} or {.dump_plist} depending on +format+.
|
73
|
+
#
|
74
|
+
# @param devices [Array<Spaceship::ConnectAPI::Device>] device objects to dump
|
75
|
+
# @param path [String] path to the output file
|
76
|
+
# @param format [:tsv, :plist] output format
|
77
|
+
# @return [void]
|
78
|
+
def self.dump(devices, path, format: :tsv)
|
79
|
+
case format
|
80
|
+
when :tsv
|
81
|
+
dump_tsv(devices, path)
|
82
|
+
when :plist
|
83
|
+
dump_plist(devices, path)
|
84
|
+
else
|
85
|
+
raise "Unsupported format '#{format}'."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param devices [Array<Spaceship::ConnectAPI::Device>] device objects to dump
|
90
|
+
# @param path [String] path to the output file
|
91
|
+
# @return [void]
|
92
|
+
def self.dump_tsv(devices, path)
|
93
|
+
require 'csv'
|
94
|
+
|
95
|
+
CSV.open(path, 'w', col_sep: "\t", headers: true, write_headers: true) do |csv|
|
96
|
+
csv << HEADERS
|
97
|
+
devices.each do |device|
|
98
|
+
csv << [device.udid, device.name, device.platform]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# @param devices [Array<Spaceship::ConnectAPI::Device>] device objects to dump
|
104
|
+
# @param path [String] path to the output file
|
105
|
+
# @return [void]
|
106
|
+
def self.dump_plist(devices, path)
|
107
|
+
require 'cfpropertylist'
|
108
|
+
|
109
|
+
plist = CFPropertyList::List.new
|
110
|
+
plist.value = CFPropertyList.guess(
|
111
|
+
{
|
112
|
+
'Device UDIDs' => devices.map do |device|
|
113
|
+
{
|
114
|
+
deviceIdentifier: device.udid,
|
115
|
+
deviceName: device.name,
|
116
|
+
devicePlatform: device.platform.downcase
|
117
|
+
}
|
118
|
+
end
|
119
|
+
}
|
120
|
+
)
|
121
|
+
plist.save(path, CFPropertyList::List::FORMAT_XML)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Maximum length of a device name that is permitted by Apple Developer Portal.
|
125
|
+
#
|
126
|
+
# @return [Integer]
|
127
|
+
MAX_DEVICE_NAME_LENGTH = 50
|
128
|
+
|
129
|
+
# @param devices [Array<Spaceship::ConnectAPI::Device>] device objects to dump
|
130
|
+
# @param path [String]
|
131
|
+
# @return [void]
|
132
|
+
# @raise [InvalidDevicesFile]
|
133
|
+
def self.validate_devices(devices, path) # rubocop:disable Metrics/AbcSize
|
134
|
+
seen_udids = []
|
135
|
+
devices.each do |device|
|
136
|
+
udid = device.udid&.downcase
|
137
|
+
unless udid.match(udid_regex_for_platform(device.platform))
|
138
|
+
raise InvalidDevicesFile.invalid_udid(device.udid, path)
|
139
|
+
end
|
140
|
+
raise InvalidDevicesFile.udid_not_unique(device.udid, path) if seen_udids.include?(udid)
|
141
|
+
|
142
|
+
if device.name.size > MAX_DEVICE_NAME_LENGTH
|
143
|
+
raise InvalidDevicesFile.device_name_too_long(device.name, path)
|
144
|
+
end
|
145
|
+
|
146
|
+
seen_udids << udid
|
147
|
+
end
|
148
|
+
end
|
149
|
+
private_class_method :validate_devices
|
150
|
+
|
151
|
+
# @param platform_string [String]
|
152
|
+
# @param path [String]
|
153
|
+
# @return [String]
|
154
|
+
def self.parse_platform(platform_string, path)
|
155
|
+
Spaceship::ConnectAPI::BundleIdPlatform.map(platform_string || 'ios')
|
156
|
+
rescue RuntimeError => e
|
157
|
+
if e.message.include?('Cannot find a matching platform')
|
158
|
+
raise InvalidDevicesFile.unknown_platform(platform_string, path)
|
159
|
+
end
|
160
|
+
|
161
|
+
raise
|
162
|
+
end
|
163
|
+
private_class_method :parse_platform
|
164
|
+
|
165
|
+
HEADERS = ['Device ID', 'Device Name', 'Device Platform'].freeze
|
166
|
+
private_constant :HEADERS
|
167
|
+
|
168
|
+
SHORT_HEADERS = HEADERS[0..1].freeze
|
169
|
+
private_constant :SHORT_HEADERS
|
170
|
+
|
171
|
+
# @param [Array<String>] headers
|
172
|
+
# @param [String] path
|
173
|
+
# @raise [InvalidDevicesFile]
|
174
|
+
def self.validate_headers(headers, path)
|
175
|
+
raise InvalidDevicesFile.invalid_headers(path, 1) unless [HEADERS, SHORT_HEADERS].include?(headers.compact)
|
176
|
+
end
|
177
|
+
private_class_method :validate_headers
|
178
|
+
|
179
|
+
# @param row [CSV::Row]
|
180
|
+
# @param path [String]
|
181
|
+
# @param line_number [Integer]
|
182
|
+
# @return [void]
|
183
|
+
# @raise [InvalidDevicesFile]
|
184
|
+
def self.validate_row(row, path, line_number)
|
185
|
+
case row.fields.compact.size
|
186
|
+
when 0, 1
|
187
|
+
raise InvalidDevicesFile.columns_too_short(path, line_number)
|
188
|
+
when 2, 3
|
189
|
+
# Does nothing
|
190
|
+
else
|
191
|
+
raise InvalidDevicesFile.columns_too_long(path, line_number)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
private_class_method :validate_row
|
195
|
+
|
196
|
+
REQUIRED_KEYS = %w[deviceName deviceIdentifier].freeze
|
197
|
+
private_constant :REQUIRED_KEYS
|
198
|
+
|
199
|
+
# @param item [Hash<String, String>]
|
200
|
+
# @param index [Integer]
|
201
|
+
# @param path [String]
|
202
|
+
# @return [void]
|
203
|
+
# @raise [InvalidDevicesFile]
|
204
|
+
def self.validate_dict_item(item, index, path)
|
205
|
+
REQUIRED_KEYS.each do |key|
|
206
|
+
unless item.key?(key)
|
207
|
+
entry = ":Device UDIDs:#{index}:#{key}"
|
208
|
+
raise InvalidDevicesFile.missing_key(entry, path)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
private_class_method :validate_dict_item
|
213
|
+
|
214
|
+
# @param platform [String]
|
215
|
+
# @return [Regexp]
|
216
|
+
# @raise [TypeError] when platform is not in {Spaceship::ConnectAPI::BundleIdPlatform::ALL}.
|
217
|
+
def self.udid_regex_for_platform(platform)
|
218
|
+
case platform
|
219
|
+
when Spaceship::ConnectAPI::BundleIdPlatform::IOS
|
220
|
+
# @see https://www.theiphonewiki.com/wiki/UDID
|
221
|
+
/^(?:[0-9]{8}-[0-9a-f]{16}|[0-9a-f]{40})$/
|
222
|
+
when Spaceship::ConnectAPI::BundleIdPlatform::MAC_OS
|
223
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
224
|
+
else
|
225
|
+
# :nocov:
|
226
|
+
raise TypeError, "Unknown platform '#{platform}' is not in #{Spaceship::ConnectAPI::BundleIdPlatform::ALL}."
|
227
|
+
# :nocov:
|
228
|
+
end
|
229
|
+
end
|
230
|
+
private_class_method :udid_regex_for_platform
|
231
|
+
end
|
232
|
+
|
233
|
+
# Generic error that is raised if device file is not in the valid format.
|
234
|
+
class InvalidDevicesFile < StandardError
|
235
|
+
# URL of example devices files.
|
236
|
+
SAMPLE_FILE_URL = 'https://developer.apple.com/account/resources/downloads/Multiple-Upload-Samples.zip'
|
237
|
+
|
238
|
+
# @return [String]
|
239
|
+
attr_reader :path
|
240
|
+
# @return [Integer]
|
241
|
+
attr_reader :line_number
|
242
|
+
# @return [String, nil]
|
243
|
+
attr_reader :entry
|
244
|
+
|
245
|
+
# @param message [String]
|
246
|
+
# @param path [String]
|
247
|
+
# @param line_number [String, nil]
|
248
|
+
# @param entry [String, nil]
|
249
|
+
def initialize(message, path, line_number: nil, entry: nil)
|
250
|
+
super(format(message, { location: [path, line_number].join(':'), url: SAMPLE_FILE_URL }))
|
251
|
+
@path = path
|
252
|
+
@line_number = line_number
|
253
|
+
@entry = entry
|
254
|
+
end
|
255
|
+
|
256
|
+
# @param path [String]
|
257
|
+
# @param line_number [Integer]
|
258
|
+
# @return [InvalidDevicesFile]
|
259
|
+
def self.invalid_headers(path, line_number)
|
260
|
+
message = 'Invalid header line at %<location>s, please provide a file according to ' \
|
261
|
+
'the Apple Sample UDID file (%<url>s)'
|
262
|
+
new(message, path, line_number: line_number)
|
263
|
+
end
|
264
|
+
|
265
|
+
# @param path [String]
|
266
|
+
# @param line_number [Integer]
|
267
|
+
# @return [InvalidDevicesFile]
|
268
|
+
def self.columns_too_short(path, line_number)
|
269
|
+
message = 'Invalid device line at %<location>s, ensure you are using tabs (NOT spaces). ' \
|
270
|
+
"See Apple's sample/spec here: %<url>s"
|
271
|
+
new(message, path, line_number: line_number)
|
272
|
+
end
|
273
|
+
|
274
|
+
# @param path [String]
|
275
|
+
# @param line_number [Integer]
|
276
|
+
# @return [InvalidDevicesFile]
|
277
|
+
def self.columns_too_long(path, line_number)
|
278
|
+
message = 'Invalid device line at %<location>s, please provide a file according to ' \
|
279
|
+
'the Apple Sample UDID file (%<url>s)'
|
280
|
+
new(message, path, line_number: line_number)
|
281
|
+
end
|
282
|
+
|
283
|
+
# @param entry [String]
|
284
|
+
# @param path [String]
|
285
|
+
# @return [InvalidDevicesFile]
|
286
|
+
def self.missing_key(entry, path)
|
287
|
+
message = "Invalid device file at %<location>s, each item must have a required key '#{entry}', " \
|
288
|
+
"See Apple's sample/spec here: %<url>s"
|
289
|
+
new(message, path, entry: entry)
|
290
|
+
end
|
291
|
+
|
292
|
+
# @param udid [String]
|
293
|
+
# @param path [String]
|
294
|
+
# @return [InvalidDevicesFile]
|
295
|
+
def self.invalid_udid(udid, path)
|
296
|
+
new("Invalid UDID '#{udid}' at %<location>s, the UDID is not in the correct format", path)
|
297
|
+
end
|
298
|
+
|
299
|
+
# @param udid [String]
|
300
|
+
# @param path [String]
|
301
|
+
# @return [InvalidDevicesFile]
|
302
|
+
def self.udid_not_unique(udid, path)
|
303
|
+
message = "Invalid UDID '#{udid}' at %<location>s, there's another device with the same UDID is defined"
|
304
|
+
new(message, path)
|
305
|
+
end
|
306
|
+
|
307
|
+
# @param name [String]
|
308
|
+
# @param path [String]
|
309
|
+
# @return [InvalidDevicesFile]
|
310
|
+
def self.device_name_too_long(name, path)
|
311
|
+
message = "Invalid device name '#{name}' at %<location>s, a device name " \
|
312
|
+
"must be less than or equal to #{DevicesFile::MAX_DEVICE_NAME_LENGTH} characters long"
|
313
|
+
new(message, path)
|
314
|
+
end
|
315
|
+
|
316
|
+
# @param platform [String]
|
317
|
+
# @param path [String]
|
318
|
+
# @return [InvalidDevicesFile]
|
319
|
+
def self.unknown_platform(platform, path)
|
320
|
+
new("Unknown platform '#{platform}' at %<location>s", path)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'device_patch'
|
4
|
+
|
5
|
+
module Fastlane # rubocop:disable Style/Documentation
|
6
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?(:UI)
|
7
|
+
|
8
|
+
module Helper
|
9
|
+
module SyncDevicesHelper
|
10
|
+
# Represents a collection of {DevicePatch}.
|
11
|
+
class DevicesPatch
|
12
|
+
# @return [Spaceship::ConnectAPI::Device]
|
13
|
+
attr_reader :old_devices, :new_devices
|
14
|
+
# @return [Array<DevicePatch>]
|
15
|
+
attr_reader :commands
|
16
|
+
|
17
|
+
# @param old_devices [Array<Spaceship::ConnectAPI::Device>]
|
18
|
+
# @param new_devices [Array<Spaceship::ConnectAPI::Device>]
|
19
|
+
def initialize(old_devices, new_devices) # rubocop:disable Metrics/AbcSize
|
20
|
+
@old_devices = old_devices
|
21
|
+
@new_devices = new_devices
|
22
|
+
|
23
|
+
old_device_by_udid = old_devices.group_by { |d| d.udid.downcase }.transform_values(&:first)
|
24
|
+
new_device_by_udid = new_devices.group_by { |d| d.udid.downcase }.transform_values(&:first)
|
25
|
+
@commands = (old_device_by_udid.keys + new_device_by_udid.keys)
|
26
|
+
.sort
|
27
|
+
.uniq
|
28
|
+
.map do |udid|
|
29
|
+
old_device = old_device_by_udid[udid]
|
30
|
+
new_device = new_device_by_udid[udid]
|
31
|
+
DevicePatch.new(old_device, new_device).command
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param dry_run [Boolean]
|
36
|
+
# @return [void]
|
37
|
+
def apply!(dry_run: false)
|
38
|
+
@commands.each do |command|
|
39
|
+
if dry_run
|
40
|
+
UI.message("(dry-run) #{command.description}")
|
41
|
+
else
|
42
|
+
command.run
|
43
|
+
UI.message(command.description)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
# @see https://rubydoc.info/gems/fastlane/Fastlane/Helper Fastlane::Helper
|
5
|
+
module Helper
|
6
|
+
# Root namespace of +fastlane-plugin-sync_devices+ helpers.
|
7
|
+
module SyncDevicesHelper
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require_relative 'sync_devices_helper/devices_file'
|
13
|
+
require_relative 'sync_devices_helper/devices_patch'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'sync_devices/version'
|
4
|
+
require_relative 'sync_devices/actions/sync_devices_action'
|
5
|
+
|
6
|
+
module Fastlane
|
7
|
+
# Root namespace of +fastlane-plugin-sync_devices+ plugin.
|
8
|
+
module SyncDevices
|
9
|
+
# @return [Array<String>]
|
10
|
+
def self.all_classes
|
11
|
+
Dir[File.expand_path('**/{actions,helper}/*.rb', __dir__)]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Fastlane::SyncDevices.all_classes.each do |current|
|
17
|
+
require current
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fastlane-plugin-sync_devices
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryosuke Ito
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-04-18 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Synchronize your devices with Apple Developer Portal.
|
14
|
+
email: rito.0305@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- CHANGELOG.md
|
20
|
+
- LICENSE
|
21
|
+
- README.md
|
22
|
+
- lib/fastlane/plugin/sync_devices.rb
|
23
|
+
- lib/fastlane/plugin/sync_devices/actions/sync_devices_action.rb
|
24
|
+
- lib/fastlane/plugin/sync_devices/helper/sync_devices_helper.rb
|
25
|
+
- lib/fastlane/plugin/sync_devices/helper/sync_devices_helper/command.rb
|
26
|
+
- lib/fastlane/plugin/sync_devices/helper/sync_devices_helper/device_patch.rb
|
27
|
+
- lib/fastlane/plugin/sync_devices/helper/sync_devices_helper/devices_file.rb
|
28
|
+
- lib/fastlane/plugin/sync_devices/helper/sync_devices_helper/devices_patch.rb
|
29
|
+
- lib/fastlane/plugin/sync_devices/version.rb
|
30
|
+
homepage: https://github.com/manicmaniac/fastlane-plugin-sync_devices
|
31
|
+
licenses:
|
32
|
+
- MIT
|
33
|
+
metadata:
|
34
|
+
bug_tracker_uri: https://github.com/manicmaniac/fastlane-plugin-sync_devices/issues
|
35
|
+
rubygems_mfa_required: 'true'
|
36
|
+
source_code_uri: https://github.com/manicmaniac/fastlane-plugin-sync_devices
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 2.7.0
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubygems_version: 3.4.6
|
53
|
+
signing_key:
|
54
|
+
specification_version: 4
|
55
|
+
summary: Synchronize your devices with Apple Developer Portal.
|
56
|
+
test_files: []
|