fastlane-plugin-universal_metadata 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/LICENSE +21 -0
- data/README.md +199 -0
- data/lib/fastlane/plugin/universal_metadata/actions/universal_metadata_action.rb +90 -0
- data/lib/fastlane/plugin/universal_metadata/helper/universal_metadata_const.rb +15 -0
- data/lib/fastlane/plugin/universal_metadata/helper/universal_metadata_init.rb +86 -0
- data/lib/fastlane/plugin/universal_metadata/helper/universal_metadata_metadata.rb +102 -0
- data/lib/fastlane/plugin/universal_metadata/helper/universal_metadata_screenshots.rb +109 -0
- data/lib/fastlane/plugin/universal_metadata/version.rb +5 -0
- data/lib/fastlane/plugin/universal_metadata.rb +16 -0
- metadata +191 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 37aa001169b3af7ebd4de16a163a1582bce986c45a2c8cfb5aba140a1d2d66c0
|
4
|
+
data.tar.gz: 9f97e4cf3563d4dd1ca15b86c4ba22d7dc15f01589a5154d502c483906317b62
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2d6d927ce29ccea66d60fc491b3bdc25b290a25289b6c16f635e7a78bec71807319fbfc5c1684590f0259d816ce3ce592f0913089ccaa4652e97799d588e04e6
|
7
|
+
data.tar.gz: 574d2ef31a422ed2cd99aea07b56e1fccd4e9ab5e1b42107896fa5c74bd0d1ce967351eec82fb6ee31dde770e3489c5764586efc1bd0a580a87bb00655465229
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 La Gregance <>
|
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,199 @@
|
|
1
|
+
# Universal metadata
|
2
|
+
|
3
|
+
By default on Fastlane we use `upload_to_app_store` & `upload_to_play_store` to publish metadata on the store. But each of them use different folder structure to achieve the same goal.
|
4
|
+
|
5
|
+
The goal of this Fastlane plugin is to unify metadata & screenshots structure for both iOS and Android, and finally produce metadata folder that will be used by `upload_to_app_store` & `upload_to_play_store`.
|
6
|
+
This is ideal for React Native or Flutter project.
|
7
|
+
|
8
|
+
# Features
|
9
|
+
|
10
|
+
- Generate every screenshot size derived from a single file
|
11
|
+
- Generate every metadata for both Android & iOS from a single json file
|
12
|
+
|
13
|
+
# Setup
|
14
|
+
|
15
|
+
1. First install the plugin by using `fastlane add_plugin universal_metadata`
|
16
|
+
2. Run the command `fastlane run universal_metdata init:true languages:"en-US,fr-FR"`, this will init the folder `universal-metadata` with this structure
|
17
|
+
|
18
|
+
```
|
19
|
+
universal-metadata
|
20
|
+
├── screenshots
|
21
|
+
│ └── default
|
22
|
+
│ └── en-US
|
23
|
+
│ └── fr-FR
|
24
|
+
├── description
|
25
|
+
│ └── default
|
26
|
+
│ ├── full_description.txt
|
27
|
+
│ └── short_description.txt
|
28
|
+
│ └── en-US
|
29
|
+
│ └── ...
|
30
|
+
│ └── fr-FR
|
31
|
+
│ └── ...
|
32
|
+
├── release-notes
|
33
|
+
│ ├── default.txt
|
34
|
+
│ ├── en-US.txt
|
35
|
+
│ └── fr-FR.txt
|
36
|
+
└── metadata.json
|
37
|
+
```
|
38
|
+
|
39
|
+
|
40
|
+
# Configuration
|
41
|
+
|
42
|
+
## Metadata.json
|
43
|
+
|
44
|
+
Concerning metadata, most of things are configurable in the file `metadata.json`.
|
45
|
+
|
46
|
+
Sometime you will see that value are in the form `{ "default": "A value" }` in this case it means it’s a localized value and you can add translation for specific languages:
|
47
|
+
|
48
|
+
```json
|
49
|
+
{
|
50
|
+
"default": "Hello world !",
|
51
|
+
"fr-FR": "Bonjour le monde !"
|
52
|
+
}
|
53
|
+
```
|
54
|
+
|
55
|
+
If a value had a direct value, it means it cannot be translate.
|
56
|
+
|
57
|
+
## Others metadata
|
58
|
+
|
59
|
+
We decide to put descriptions & release-notes under specific files cause theses metadata are often multilines and so not very convenient to define in a JSON file.
|
60
|
+
|
61
|
+
You can define you description following this structure:
|
62
|
+
|
63
|
+
```
|
64
|
+
description
|
65
|
+
├── default
|
66
|
+
│ ├── full_description.txt
|
67
|
+
│ └── short_description.txt <-- Android only
|
68
|
+
├── en-US
|
69
|
+
│ ├── full_description.txt
|
70
|
+
│ └── short_description.txt
|
71
|
+
└── fr-FR <-- Remove language folder to use default instead
|
72
|
+
├── full_description.txt
|
73
|
+
└── short_description.txt
|
74
|
+
```
|
75
|
+
|
76
|
+
For release-notes it’s pretty close:
|
77
|
+
|
78
|
+
```
|
79
|
+
release-notes
|
80
|
+
├── default.txt
|
81
|
+
├── en-US.txt
|
82
|
+
└── fr-FR.txt <-- Remove language file to use default instead
|
83
|
+
```
|
84
|
+
|
85
|
+
## Screenshots
|
86
|
+
|
87
|
+
Screenshots folder has a similar structure:
|
88
|
+
|
89
|
+
```
|
90
|
+
screenshots
|
91
|
+
├── default
|
92
|
+
│ ├── 1-screenshot.png
|
93
|
+
│ └── 2-screenshot.png
|
94
|
+
├── en-US
|
95
|
+
└── fr-FR <-- Remove language folder to use default instead
|
96
|
+
```
|
97
|
+
|
98
|
+
During the action, your screenshots will be resized to match size required by iOS & Android and put it the good folder.
|
99
|
+
|
100
|
+
⚠️ Default folder is used only if the folder for language doesn’t exist (if language folder is empty, no screenshot will be uploaded).
|
101
|
+
|
102
|
+
You can configure screenshot behavior by adding tags in the name of the file :
|
103
|
+
|
104
|
+
| Tag | Description |
|
105
|
+
| --- | --- |
|
106
|
+
| tablet | Use the screenshot for tablet (without this tag it’s used only for phone) |
|
107
|
+
| contain | By default screenshot are resized with cover resizeMode. When the tag contain is present, screenshot will be resized with contain resizeMode (note for iOS, using contain tag can result to white border to match the exact size required by the App Store) |
|
108
|
+
| apple | If this tag is present, use the screenshot only for iOS |
|
109
|
+
| android | If this tag is present, use the screenshot only for Android |
|
110
|
+
|
111
|
+
So for example you can add a screenshot `random-name-apple-contain.png` that will be use only for iOS and use resizeMode contain.
|
112
|
+
|
113
|
+
ℹ️ The order of the screenshots on the store depends of alphabetical order of the screenshots names.
|
114
|
+
|
115
|
+
## Result
|
116
|
+
|
117
|
+
By running `fastlane run universal_metdata languages:"en-US,fr-FR"` you will get the following result:
|
118
|
+
|
119
|
+
```
|
120
|
+
screenshots
|
121
|
+
│ └── fr
|
122
|
+
│ ├── screen-1.png
|
123
|
+
│ └── screen-2.png
|
124
|
+
metadata
|
125
|
+
├── android
|
126
|
+
│ └── fr
|
127
|
+
│ ├── images
|
128
|
+
│ │ ├── phoneScreenshots
|
129
|
+
│ │ │ ├── screen-1.png
|
130
|
+
│ │ │ └── screen-2.png
|
131
|
+
│ │ ├── sevenInchScreenshots
|
132
|
+
│ │ │ ├── screen-1.png
|
133
|
+
│ │ │ └── screen-2.png
|
134
|
+
│ │ └── tenInchScreenshots
|
135
|
+
│ │ │ ├── screen-1.png
|
136
|
+
│ │ │ └── screen-2.png
|
137
|
+
│ ├── full_description.txt
|
138
|
+
│ ├── short_description.txt
|
139
|
+
│ ├── title.txt
|
140
|
+
│ └── video.txt
|
141
|
+
├── fr
|
142
|
+
│ ├── name.txt
|
143
|
+
│ ├── subtitle.txt
|
144
|
+
│ ├── privacy_url.txt
|
145
|
+
│ ├── description.txt
|
146
|
+
│ ├── keywords.txt
|
147
|
+
│ ├── release_notes.txt
|
148
|
+
│ ├── support_url.txt
|
149
|
+
│ ├── marketing_url.txt
|
150
|
+
│ └── promotional_text.txt
|
151
|
+
├── copyright.txt
|
152
|
+
├── primary_category.txt
|
153
|
+
├── secondary_category.txt
|
154
|
+
├── primary_first_sub_category.txt
|
155
|
+
├── primary_second_sub_category.txt
|
156
|
+
├── secondary_first_sub_category.txt
|
157
|
+
├── secondary_second_sub_category.txt
|
158
|
+
├── first_name.txt
|
159
|
+
├── last_name.txt
|
160
|
+
├── phone_number.txt
|
161
|
+
├── email_address.txt
|
162
|
+
├── demo_user.txt
|
163
|
+
├── demo_password.txt
|
164
|
+
└── notes.txt
|
165
|
+
```
|
166
|
+
|
167
|
+
Now you are able to run `upload_to_app_store` & `upload_to_play_store` without pain.
|
168
|
+
|
169
|
+
ℹ️ Don’t forget that you can use universal_metadata action in your lane, before uploading to store for example 😉
|
170
|
+
|
171
|
+
## Params
|
172
|
+
|
173
|
+
You can use different params with the action:
|
174
|
+
|
175
|
+
| Param | ENV Variable | Description | Default |
|
176
|
+
| --- | --- | --- | --- |
|
177
|
+
| languages | LANGUAGES | List of languages to use (comma-separated) | |
|
178
|
+
| ios_screenshots_dir | IOS_SCREENSHOTS_DIR | The target dir for ios screenshots | fastlane/screenshots |
|
179
|
+
| skip_screenshots_resize | SKIP_SCREENSHOTS_RESIZE | Don’t resize screenshot if true | false |
|
180
|
+
| skip_screenshots | SKIP_SCREENSHOTS | Don’t manage screenshot at all if true | false |
|
181
|
+
|
182
|
+
## Tips & Warning
|
183
|
+
|
184
|
+
⚠️ Your metadata folder will be erased each time you run the action, be sure to not having sensitive data on it.
|
185
|
+
Also since it will be auto-generated, we recommend to add it to your `.gitignore`
|
186
|
+
|
187
|
+
ℹ️ Personally I like to have all the result under fastlane/metadata, that’s why i use the `ios_screenshots_dir`
|
188
|
+
params with the value `fastlane/metadata/screenshots`.
|
189
|
+
|
190
|
+
|
191
|
+
Because it’s not the default place for it, I have to add this to my Deliverfile:
|
192
|
+
```
|
193
|
+
# Cause we use non-standard path for screenshots, we have to add this options
|
194
|
+
ignore_language_directory_validation(true)
|
195
|
+
screenshots_path("./fastlane/metadata/screenshots")
|
196
|
+
|
197
|
+
# Prevent same screenshot from being uploaded multiple times
|
198
|
+
overwrite_screenshots(true)
|
199
|
+
```
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'fastlane/action'
|
2
|
+
require_relative '../helper/universal_metadata_metadata'
|
3
|
+
|
4
|
+
module Fastlane
|
5
|
+
module Actions
|
6
|
+
class UniversalMetadataAction < Action
|
7
|
+
def self.run(params)
|
8
|
+
languages = params[:languages].split(',')
|
9
|
+
|
10
|
+
if params[:init] then
|
11
|
+
Helper::UniversalMetadataInit.init(languages)
|
12
|
+
return
|
13
|
+
end
|
14
|
+
|
15
|
+
universal_metadata_folder = Helper::UniversalMetadataConst.universal_metadata_folder
|
16
|
+
metadata_folder = Helper::UniversalMetadataConst.metadata_folder
|
17
|
+
|
18
|
+
#####################################################
|
19
|
+
############## GENERATE METADATA FILES ##############
|
20
|
+
#####################################################
|
21
|
+
metadata = JSON.parse(File.read(File.join(universal_metadata_folder, 'metadata.json')))
|
22
|
+
Helper::UniversalMetadataMetadata.generateMetadata(metadata, languages, metadata_folder)
|
23
|
+
|
24
|
+
|
25
|
+
######################################################
|
26
|
+
######### GENERATE SCREENSHOTS FOR EACH SIZE #########
|
27
|
+
######################################################
|
28
|
+
Helper::UniversalMetadataScreenshots.generateScreenshots(languages, params, metadata_folder)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.description
|
32
|
+
"Unify metadata files for iOS & Android"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.authors
|
36
|
+
["La Gregance"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.return_value
|
40
|
+
# If your method provides a return value, you can describe here what it does
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.details
|
44
|
+
# Optional:
|
45
|
+
"Unify metadata files for iOS & Android"
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.available_options
|
49
|
+
[
|
50
|
+
FastlaneCore::ConfigItem.new(key: :init,
|
51
|
+
description: "Init universal-metadata folder",
|
52
|
+
optional: true,
|
53
|
+
default_value: false,
|
54
|
+
type: Boolean),
|
55
|
+
FastlaneCore::ConfigItem.new(key: :languages,
|
56
|
+
env_name: "LANGUAGES",
|
57
|
+
description: "Languages to which generate metadata folder (comma separated)",
|
58
|
+
optional: false,
|
59
|
+
type: String),
|
60
|
+
FastlaneCore::ConfigItem.new(key: :ios_screenshots_dir,
|
61
|
+
env_name: "IOS_SCREENSHOTS_DIR",
|
62
|
+
description: "Folder that will store the screenshots for iOS",
|
63
|
+
optional: true,
|
64
|
+
default_value: "fastlane/screenshots",
|
65
|
+
type: String),
|
66
|
+
FastlaneCore::ConfigItem.new(key: :skip_screenshots_resize,
|
67
|
+
env_name: "SKIP_SCREENSHOTS_RESIZE",
|
68
|
+
description: "If true, we don't resize screenshots before moving them",
|
69
|
+
optional: true,
|
70
|
+
default_value: false,
|
71
|
+
type: Boolean),
|
72
|
+
FastlaneCore::ConfigItem.new(key: :skip_screenshots,
|
73
|
+
env_name: "SKIP_SCREENSHOTS",
|
74
|
+
description: "If true, skip copying screenshot",
|
75
|
+
optional: true,
|
76
|
+
default_value: false,
|
77
|
+
type: Boolean)
|
78
|
+
]
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.is_supported?(platform)
|
82
|
+
# Adjust this if your plugin only works for a particular platform (iOS vs. Android, for example)
|
83
|
+
# See: https://docs.fastlane.tools/advanced/#control-configuration-by-lane-and-by-platform
|
84
|
+
#
|
85
|
+
# [:ios, :mac, :android].include?(platform)
|
86
|
+
true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'fastlane_core/ui/ui'
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
module Helper
|
5
|
+
class UniversalMetadataConst
|
6
|
+
def self.universal_metadata_folder
|
7
|
+
'fastlane/universal-metadata'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.metadata_folder
|
11
|
+
'fastlane/metadata'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,86 @@
|
|
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 UniversalMetadataInit
|
8
|
+
# class methods that you define here become available in your action
|
9
|
+
# as `Helper::UniversalMetadataInit.your_method`
|
10
|
+
#
|
11
|
+
def self.init(languages)
|
12
|
+
folder = Helper::UniversalMetadataConst.universal_metadata_folder
|
13
|
+
if File.exist?(folder) then
|
14
|
+
UI.error("Folder universal-metadata already exists")
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
UI.message("Init universal-metadata folder")
|
19
|
+
|
20
|
+
# 1. Create file tree
|
21
|
+
FileUtils.mkdir_p(File.join(folder, 'screenshots/default'))
|
22
|
+
|
23
|
+
FileUtils.mkdir_p(File.join(folder, 'release-notes'))
|
24
|
+
File.write(File.join(folder, 'release-notes', 'default.txt'), "Default release note")
|
25
|
+
|
26
|
+
FileUtils.mkdir_p(File.join(folder, 'description/default'))
|
27
|
+
File.write(File.join(folder, 'description/default', 'full_description.txt'), "Default full description")
|
28
|
+
File.write(File.join(folder, 'description/default', 'short_description.txt'), "Default short description")
|
29
|
+
|
30
|
+
for lang in languages
|
31
|
+
FileUtils.mkdir_p(File.join(folder, 'screenshots', lang))
|
32
|
+
|
33
|
+
FileUtils.mkdir_p(File.join(folder, 'description', lang))
|
34
|
+
File.write(File.join(folder, 'description', lang, 'full_description.txt'), lang + " full description")
|
35
|
+
File.write(File.join(folder, 'description', lang, 'short_description.txt'), lang + " short description")
|
36
|
+
|
37
|
+
File.write(File.join(folder, 'release-notes', lang+'.txt'), lang + " release note")
|
38
|
+
end
|
39
|
+
|
40
|
+
# 2. Create metadata.json
|
41
|
+
metadata = {
|
42
|
+
"name": {
|
43
|
+
"default": "AppName"
|
44
|
+
},
|
45
|
+
"subtitle": {
|
46
|
+
"default": ""
|
47
|
+
},
|
48
|
+
"privacy_url": {
|
49
|
+
"default": ""
|
50
|
+
},
|
51
|
+
"copyright": "2023 La Gregance",
|
52
|
+
"apple": {
|
53
|
+
"primary_category": "",
|
54
|
+
"secondary_category": "",
|
55
|
+
"primary_first_sub_category": "",
|
56
|
+
"primary_second_sub_category": "",
|
57
|
+
"secondary_first_sub_category": "",
|
58
|
+
"secondary_second_sub_category": "",
|
59
|
+
"keywords": {
|
60
|
+
"default": ""
|
61
|
+
},
|
62
|
+
"support_url": {
|
63
|
+
"default": ""
|
64
|
+
},
|
65
|
+
"marketing_url": {
|
66
|
+
"default": ""
|
67
|
+
},
|
68
|
+
"promotional_text": {
|
69
|
+
"default": ""
|
70
|
+
},
|
71
|
+
"reviewInfos": {
|
72
|
+
"first_name": "",
|
73
|
+
"last_name": "",
|
74
|
+
"phone_number": "",
|
75
|
+
"email_address": "",
|
76
|
+
"demo_user": "",
|
77
|
+
"demo_password": "",
|
78
|
+
"notes": ""
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
File.write(File.join(folder, 'metadata.json'), JSON.pretty_generate(metadata))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,102 @@
|
|
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 UniversalMetadataMetadata
|
8
|
+
# class methods that you define here become available in your action
|
9
|
+
# as `Helper::UniversalMetadataMetadata.your_method`
|
10
|
+
#
|
11
|
+
def self.generateMetadata(metadata, languages, metadata_folder)
|
12
|
+
FileUtils.rm_rf(metadata_folder)
|
13
|
+
FileUtils.mkdir_p(File.join(metadata_folder, 'android'))
|
14
|
+
FileUtils.mkdir_p(File.join(metadata_folder, 'review_information'))
|
15
|
+
|
16
|
+
# 1. Non localized files for iOS
|
17
|
+
self.writeFile('copyright.txt', metadata['copyright'])
|
18
|
+
self.writeFile('primary_category.txt', metadata['apple']['primary_category'])
|
19
|
+
self.writeFile('secondary_category.txt', metadata['apple']['secondary_category'])
|
20
|
+
self.writeFile('primary_first_sub_category.txt', metadata['apple']['primary_first_sub_category'])
|
21
|
+
self.writeFile('primary_second_sub_category.txt', metadata['apple']['primary_second_sub_category'])
|
22
|
+
self.writeFile('secondary_first_sub_category.txt', metadata['apple']['secondary_first_sub_category'])
|
23
|
+
self.writeFile('secondary_second_sub_category.txt', metadata['apple']['secondary_second_sub_category'])
|
24
|
+
|
25
|
+
self.writeFile('review_information/first_name.txt', metadata['apple']['reviewInfos']['first_name'])
|
26
|
+
self.writeFile('review_information/last_name.txt', metadata['apple']['reviewInfos']['last_name'])
|
27
|
+
self.writeFile('review_information/phone_number.txt', metadata['apple']['reviewInfos']['phone_number'])
|
28
|
+
self.writeFile('review_information/email_address.txt', metadata['apple']['reviewInfos']['email_address'])
|
29
|
+
self.writeFile('review_information/demo_user.txt', metadata['apple']['reviewInfos']['demo_user'])
|
30
|
+
self.writeFile('review_information/demo_password.txt', metadata['apple']['reviewInfos']['demo_password'])
|
31
|
+
self.writeFile('review_information/notes.txt', metadata['apple']['reviewInfos']['notes'])
|
32
|
+
|
33
|
+
for lang in languages
|
34
|
+
full_description = self.getDescription(true, lang)
|
35
|
+
short_description = self.getDescription(false, lang)
|
36
|
+
|
37
|
+
# 2. Localized files for iOS
|
38
|
+
self.writeFile(lang + '/name.txt', self.localized(metadata['name'], lang))
|
39
|
+
self.writeFile(lang + '/subtitle.txt', self.localized(metadata['subtitle'], lang))
|
40
|
+
self.writeFile(lang + '/privacy_url.txt', self.localized(metadata['privacy_url'], lang))
|
41
|
+
self.writeFile(lang + '/description.txt', full_description)
|
42
|
+
self.writeFile(lang + '/keywords.txt', self.localized(metadata['apple']['keywords'], lang))
|
43
|
+
self.writeFile(lang + '/release_notes.txt', self.getReleaseNotes(lang))
|
44
|
+
self.writeFile(lang + '/support_url.txt', self.localized(metadata['apple']['support_url'], lang))
|
45
|
+
self.writeFile(lang + '/marketing_url.txt', self.localized(metadata['apple']['marketing_url'], lang))
|
46
|
+
self.writeFile(lang + '/promotional_text.txt', self.localized(metadata['apple']['promotional_text'], lang))
|
47
|
+
|
48
|
+
# 3. Localized files for Android
|
49
|
+
self.writeFile('android/' + lang + '/title.txt', self.localized(metadata['name'], lang))
|
50
|
+
self.writeFile('android/' + lang + '/full_description.txt', full_description)
|
51
|
+
self.writeFile('android/' + lang + '/short_description.txt', short_description)
|
52
|
+
self.writeFile('android/' + lang + '/video.txt', "")
|
53
|
+
self.writeFile('android/' + lang + '/changelogs/default.txt', self.getReleaseNotes(lang))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.writeFile(file, content)
|
58
|
+
file = 'fastlane/metadata/' + file
|
59
|
+
FileUtils.mkdir_p(File.dirname(file))
|
60
|
+
File.open(file, "w") { |f| f.write content }
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.getDescription(full, lang)
|
64
|
+
folder = 'fastlane/universal-metadata/description/'
|
65
|
+
file = full ? '/full_description.txt' : '/short_description.txt'
|
66
|
+
if File.exists?(folder + lang + file) then
|
67
|
+
return File.read(folder + lang + file)
|
68
|
+
elsif File.exists?(folder + 'default' + file) then
|
69
|
+
return File.read(folder + 'default' + file)
|
70
|
+
else
|
71
|
+
puts "Warning: " + lang + ' or default not found for description'
|
72
|
+
return ""
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.getReleaseNotes(lang)
|
77
|
+
folder = File.join('fastlane/universal-metadata/release-notes/')
|
78
|
+
|
79
|
+
if File.exists?(File.join(folder, lang + '.txt')) then
|
80
|
+
return File.read(File.join(folder, lang + '.txt'))
|
81
|
+
elsif File.exists?(File.join(folder, 'default.txt')) then
|
82
|
+
return File.read(File.join(folder, 'default.txt'))
|
83
|
+
else
|
84
|
+
puts "Warning: " + lang + ' or default not found for release-notes'
|
85
|
+
return ""
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.localized(metadata, lang)
|
90
|
+
if metadata.key?(lang) then
|
91
|
+
return metadata[lang]
|
92
|
+
elsif metadata.key?('default') then
|
93
|
+
return metadata['default']
|
94
|
+
else
|
95
|
+
puts "Warning: " + lang + ' or default not found in ' + metadata.to_s
|
96
|
+
return ""
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,109 @@
|
|
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 UniversalMetadataScreenshots
|
8
|
+
def self.generateScreenshots(languages, params, metadata_folder)
|
9
|
+
if params[:skip_screenshots] then
|
10
|
+
UI.message("Skip screenshots")
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
ios_screenshot_folder = params[:ios_screenshots_dir]
|
15
|
+
FileUtils.rm_rf(ios_screenshot_folder)
|
16
|
+
FileUtils.mkdir_p(ios_screenshot_folder)
|
17
|
+
|
18
|
+
screenSizes = {
|
19
|
+
# Apple
|
20
|
+
"iPhone-6_5" => { "isTablet" => false, "width" => 1242, "height" => 2688, "isApple" => true },
|
21
|
+
# "iPhone-6_7" => { "isTablet" => false, "width" => 1290, "height" => 2796, "isApple" => true },
|
22
|
+
# "iPhone-6_1" => { "isTablet" => false, "width" => 1179, "height" => 2556, "isApple" => true },
|
23
|
+
# "iPhone-5_8" => { "isTablet" => false, "width" => 1170, "height" => 2532, "isApple" => true },
|
24
|
+
"iPhone-5_5" => { "isTablet" => false, "width" => 1242, "height" => 2208, "isApple" => true },
|
25
|
+
"iPad-12_9" => { "isTablet" => true, "width" => 2048, "height" => 2732, "isApple" => true },
|
26
|
+
"ipadPro129" => { "isTablet" => true, "width" => 2048, "height" => 2732, "isApple" => true },
|
27
|
+
# "iPad-11" => { "isTablet" => true, "width" => 1668, "height" => 2388, "isApple" => true },
|
28
|
+
|
29
|
+
# Android
|
30
|
+
"android-5_8" => { "isTablet" => false, "width" => 1242, "height" => 2208, "isApple" => false, "folder" => 'phoneScreenshots' },
|
31
|
+
"android-7" => { "isTablet" => true, "width" => 2048, "height" => 2732, "isApple" => false, "folder" => 'sevenInchScreenshots' },
|
32
|
+
"android-11" => { "isTablet" => true, "width" => 2048, "height" => 2732, "isApple" => false, "folder" => 'tenInchScreenshots' },
|
33
|
+
}
|
34
|
+
|
35
|
+
for lang in languages
|
36
|
+
for screenshot in self.getScreenshotFiles(lang) do
|
37
|
+
tags = self.getScreenshotTags(screenshot)
|
38
|
+
|
39
|
+
screenSizes.each do |deviceName, deviceInfo|
|
40
|
+
if tags["isApple"] == deviceInfo["isApple"] || tags["isAndroid"] != deviceInfo["isApple"] then
|
41
|
+
if deviceInfo["isApple"] then
|
42
|
+
targetFolder = File.join(ios_screenshot_folder, lang)
|
43
|
+
else
|
44
|
+
targetFolder = File.join(metadata_folder, 'android', lang, 'images', deviceInfo["folder"])
|
45
|
+
end
|
46
|
+
|
47
|
+
FileUtils.mkdir_p(targetFolder)
|
48
|
+
if params[:skip_screenshots_resize] then
|
49
|
+
FileUtils.cp(screenshot, File.join(targetFolder, File.basename(screenshot)))
|
50
|
+
else
|
51
|
+
if deviceInfo["isTablet"] == tags["isTablet"] then
|
52
|
+
self.resizeImage(
|
53
|
+
screenshot,
|
54
|
+
File.join(targetFolder, deviceName + '-' + File.basename(screenshot)),
|
55
|
+
deviceInfo["width"],
|
56
|
+
deviceInfo["height"],
|
57
|
+
tags["resizeContain"],
|
58
|
+
deviceInfo["isApple"]
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.getScreenshotTags(file)
|
69
|
+
file = File.basename(file).downcase
|
70
|
+
tags = {
|
71
|
+
"isTablet" => file.include?('tablet'),
|
72
|
+
"resizeContain" => file.include?('contain'),
|
73
|
+
"isApple" => file.include?('apple') || !file.include?('android'),
|
74
|
+
"isAndroid" => file.include?('android') || !file.include?('apple'),
|
75
|
+
}
|
76
|
+
tags
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.getScreenshotFiles(lang)
|
80
|
+
folder = 'fastlane/universal-metadata/screenshots/'
|
81
|
+
if File.exists?(folder + lang) then
|
82
|
+
return Dir.entries(folder + lang).map { |file| folder + lang + '/' + file }
|
83
|
+
elsif File.exists?(folder + 'default') then
|
84
|
+
return Dir.entries(folder + 'default').map { |file| folder + 'default/' + file }
|
85
|
+
else
|
86
|
+
puts "Warning: " + lang + ' or default not found for screenshots'
|
87
|
+
return []
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.resizeImage(source, target, width, height, resizeContain, addBorder)
|
92
|
+
size = width.to_s + 'x' + height.to_s
|
93
|
+
|
94
|
+
# Add ^ just after the size make the resizeMode to cover (default contain)
|
95
|
+
cmd = 'magick convert "'+source+'" -resize ' + size + (resizeContain ? '' : '^')
|
96
|
+
if addBorder then
|
97
|
+
cmd += ' -gravity center -extent '+size
|
98
|
+
end
|
99
|
+
cmd += ' "'+target+'"'
|
100
|
+
|
101
|
+
result = system(cmd)
|
102
|
+
if !result then
|
103
|
+
puts "Warning: Error resizing file " + source
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'fastlane/plugin/universal_metadata/version'
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
module UniversalMetadata
|
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::UniversalMetadata.all_classes.each do |current|
|
15
|
+
require current
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fastlane-plugin-universal_metadata
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- La Gregance
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-02-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fastlane
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.211.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.211.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec_junit_formatter
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.12.1
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.12.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop-performance
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop-require_tools
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description:
|
154
|
+
email: ''
|
155
|
+
executables: []
|
156
|
+
extensions: []
|
157
|
+
extra_rdoc_files: []
|
158
|
+
files:
|
159
|
+
- LICENSE
|
160
|
+
- README.md
|
161
|
+
- lib/fastlane/plugin/universal_metadata.rb
|
162
|
+
- lib/fastlane/plugin/universal_metadata/actions/universal_metadata_action.rb
|
163
|
+
- lib/fastlane/plugin/universal_metadata/helper/universal_metadata_const.rb
|
164
|
+
- lib/fastlane/plugin/universal_metadata/helper/universal_metadata_init.rb
|
165
|
+
- lib/fastlane/plugin/universal_metadata/helper/universal_metadata_metadata.rb
|
166
|
+
- lib/fastlane/plugin/universal_metadata/helper/universal_metadata_screenshots.rb
|
167
|
+
- lib/fastlane/plugin/universal_metadata/version.rb
|
168
|
+
homepage: https://github.com/LaGregance/fastlane-plugin-universal_metadata
|
169
|
+
licenses:
|
170
|
+
- MIT
|
171
|
+
metadata: {}
|
172
|
+
post_install_message:
|
173
|
+
rdoc_options: []
|
174
|
+
require_paths:
|
175
|
+
- lib
|
176
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '2.6'
|
181
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: '0'
|
186
|
+
requirements: []
|
187
|
+
rubygems_version: 3.1.6
|
188
|
+
signing_key:
|
189
|
+
specification_version: 4
|
190
|
+
summary: Unify metadata files for iOS & Android
|
191
|
+
test_files: []
|