Add localization download and upload automation for Jetpack#16733
Add localization download and upload automation for Jetpack#16733mokagio wants to merge 12 commits into
Conversation
|
You can trigger optional UI/connected tests for these changes by visiting CircleCI here. |
|
You can trigger an installable build for these changes by visiting CircleCI here. |
| lane :new_beta_release do |options| | ||
| ios_betabuild_prechecks(options) | ||
| ios_update_metadata(options) | ||
| download_localized_strings_and_metadata(options) |
There was a problem hiding this comment.
This is a new lane that does the .strings download for WordPress (which Jetpack shares) and the metadata download for WordPress and Jetpack.
I decide to name it like that because I found the release toolkit action name misleading. This name seems more accurate to me. What do you think?
In particular, "download" clearly tells me where the data is coming from, where as "update" could be going both way (e.g. it could be the app updating/uploading metadata to ASC)
There was a problem hiding this comment.
💯 + 1000000 on misleading toolkit action names, that has been bothering me for a while and has been a big barrier to entry that made me almost lose my mind the first times I had to work with the toolkit 😅
In the long term I'm hoping we could rename them all (probably deprecating the existing ones and creating new ones in their place, taking the occasion to modernize and refactor their code), but that's for another day.
| # bundle exec fastlane update_metadata_on_app_store_connect with_screenshots:true | ||
| ##################################################################################### | ||
| desc 'Updates the App Store Connect localized metadata' | ||
| lane :update_metadata_on_app_store_connect do |options| |
There was a problem hiding this comment.
We hinted at having a lane replacing the call to deliver in this PR.
The reason I think it's valuable to use a lane is that we could remove the Deliverfile and have the Fastfile as the single source of truth. What do you think?
I went down that path for the Jetpack logic, but kept the current setup for WordPress – there's enough going on in here already 😄
I'm happy to revert to use deliver if you don't think this is a good approach, maybe passing a dedicated Jetpack-Deliverfile to the call. But I'd ask to do it as a followup PR against the next release since this one is getting close to the release finalization date.
There was a problem hiding this comment.
I agree it can make sense to have a single source of truth. But:
- Having that PR ending up doing it one way for Jetpack and another for WordPress actually moves away from that goal, because now we have 3 potential source of truth (
Deliver+ a bit inFastfiletoo for WP, andJetpack-Fastfilefor Jetpack. So personally I would revert the change in that PR to make it only calldeliverfor now – aligning this with the current setup used for WordPress – and work on this on a separate PR - That separate PR thwt would work on having a unique source of truth might lead to a debate (e.g. given
Jetpack-Fastfileis already a separate file, should we put the source of truth inFastfile, making it not obvious when reading theJetpack-Fastfilethat some constants come from the mainFastfilebut are not obivous and visible in theJetpack-Fastfile? Or given we have 2 fastfiles, should we actually consider theDeliverfileas a good thing to be used as a common source of truth for both?
If we only had one Fastfile I'd agree to get rid of the Deliverfile and use the Fastfile as the single source of truth, but given we already split it the fastfile there… I think we should brainstorm what's best and keep that debate for a separate PR
| @@ -1,3 +1,7 @@ | |||
| # frozen_string_literal: true | |||
There was a problem hiding this comment.
This is just a nice to have, but a no-op for the changes in this PR.
| # No need to `cd` into `fastlane` because of how Fastlane manages its paths | ||
| # internally. | ||
| sh './download_metadata.swift jetpack' | ||
|
|
||
| jetpack_metadata_glob = File.join(PROJECT_ROOT_FOLDER, 'fastlane', 'jetpack_metadata/**/*.txt') | ||
| sh "git add #{jetpack_metadata_glob}" | ||
| git_commit( | ||
| path: jetpack_metadata_glob, | ||
| message: 'Update Jetpack metadata translations' | ||
| ) |
There was a problem hiding this comment.
I decided to hand-roll the automation to download metadata using the Swift script in its first iteration instead of extracting directly into the release toolkit.
I'm not actually sure what would be the best way to extract it... Maybe move the script to the release toolkit and pass it a JSON/YML config file where to read all the params such as the GlotPress URL that are currently hardcoded?
|
|
||
| deliver( | ||
| app_identifier: APP_IDENTIFIER, | ||
| app_version: read_version_from_config, |
There was a problem hiding this comment.
I think we should to this (reading the version from the .xcconfig) for all the iOS apps. It would remove the need to modify the Deliverfile during the code freeze.
There was a problem hiding this comment.
Good point and good idea! 👍
| with_screenshots = options.fetch(:with_screenshots, false) | ||
| skip_screenshots = with_screenshots == false | ||
|
|
||
| deliver( |
There was a problem hiding this comment.
All the settings below are ports of what's already defined in Deliverfile.
There was a problem hiding this comment.
See previous comment, it make sense if we agree to split indeed, but it might be too soon to decide how to split and I'd prefer to migrate to the new way for both WordPress and Jetpack all at once, instead of having a mix of new way for Jetpack, old way for WP.
| # Reusing the WordPress one here, the values are the same after all | ||
| app_rating_config_path: File.join(PROJECT_ROOT_FOLDER, 'fastlane', 'metadata', 'ratings_config.json') |
| struct Config { | ||
| let baseFolder: String | ||
| let baseURLString: String | ||
|
|
||
| static let wordPress = Config( | ||
| baseFolder: "./metadata", | ||
| baseURLString: "https://translate.wordpress.org/projects/apps/ios/release-notes/" | ||
| ) | ||
|
|
||
| static let jetpack = Config( | ||
| baseFolder: "./jetpack_metadata", | ||
| baseURLString: "https://translate.wordpress.com/projects/jetpack/apps/ios/release-notes/" | ||
| ) | ||
|
|
||
| static func config(for argument: String?) -> Config { | ||
| guard let argument = argument else { return .wordPress } | ||
| return argument == "jetpack" ? .jetpack : .wordPress | ||
| } | ||
| } |
There was a problem hiding this comment.
Had a bit of Swift fun here. But I worry I ended up with something over-engineered...
There was a problem hiding this comment.
I like it (but it might be because I miss Swift too and playing with it 😅 )
|
|
||
| func downloadTranslation(languageCode: String, folderName: String) { | ||
| func downloadTranslation( | ||
| config: Config = .config(for: CommandLine.arguments.second), |
There was a problem hiding this comment.
There's actually a more idiomatic way of doing this arguments management in the form of ArgumentParser but it seemed out of scope for the context of this PR.
There was a problem hiding this comment.
That would require us to use a Package.swift to pull the ArgumentParser package first via SwiftPM to be then able to use it, tho 😓
| guard let index = key.index(of: Character(UnicodeScalar(0004))) else { | ||
| guard let index = key.firstIndex(of: Character(UnicodeScalar(0004))) else { |
There was a problem hiding this comment.
The warning on this had been nagging me for ages!
AliSoftware
left a comment
There was a problem hiding this comment.
Quick initial review after skimming at code from high level, so you can already start replying/addressing initial feedback; but will require a more deeper review later anyway
| lane :new_beta_release do |options| | ||
| ios_betabuild_prechecks(options) | ||
| ios_update_metadata(options) | ||
| download_localized_strings_and_metadata(options) |
There was a problem hiding this comment.
💯 + 1000000 on misleading toolkit action names, that has been bothering me for a while and has been a big barrier to entry that made me almost lose my mind the first times I had to work with the toolkit 😅
In the long term I'm hoping we could rename them all (probably deprecating the existing ones and creating new ones in their place, taking the occasion to modernize and refactor their code), but that's for another day.
| ios_update_metadata(options) | ||
| # Jetpack: use the custom setup. Notice that we don't need extra steps to | ||
| # download `.strings` because Jetpack uses the same codebase as WordPress. | ||
| download_jetpack_localized_app_store_metadata |
There was a problem hiding this comment.
For info: this lane is defined in the dedicated and separate Jetpack-Fastfile.
| # bundle exec fastlane update_metadata_on_app_store_connect with_screenshots:true | ||
| ##################################################################################### | ||
| desc 'Updates the App Store Connect localized metadata' | ||
| lane :update_metadata_on_app_store_connect do |options| |
There was a problem hiding this comment.
I agree it can make sense to have a single source of truth. But:
- Having that PR ending up doing it one way for Jetpack and another for WordPress actually moves away from that goal, because now we have 3 potential source of truth (
Deliver+ a bit inFastfiletoo for WP, andJetpack-Fastfilefor Jetpack. So personally I would revert the change in that PR to make it only calldeliverfor now – aligning this with the current setup used for WordPress – and work on this on a separate PR - That separate PR thwt would work on having a unique source of truth might lead to a debate (e.g. given
Jetpack-Fastfileis already a separate file, should we put the source of truth inFastfile, making it not obvious when reading theJetpack-Fastfilethat some constants come from the mainFastfilebut are not obivous and visible in theJetpack-Fastfile? Or given we have 2 fastfiles, should we actually consider theDeliverfileas a good thing to be used as a common source of truth for both?
If we only had one Fastfile I'd agree to get rid of the Deliverfile and use the Fastfile as the single source of truth, but given we already split it the fastfile there… I think we should brainstorm what's best and keep that debate for a separate PR
| with_screenshots = options.fetch(:with_screenshots, false) | ||
| skip_screenshots = with_screenshots == false | ||
|
|
||
| deliver( |
There was a problem hiding this comment.
See previous comment, it make sense if we agree to split indeed, but it might be too soon to decide how to split and I'd prefer to migrate to the new way for both WordPress and Jetpack all at once, instead of having a mix of new way for Jetpack, old way for WP.
|
|
||
| deliver( | ||
| app_identifier: APP_IDENTIFIER, | ||
| app_version: read_version_from_config, |
There was a problem hiding this comment.
Good point and good idea! 👍
| version = '' | ||
| # If the file is not available, the method will raise so we should be fine | ||
| # not handling that case. We'll never return an empty string. | ||
| File.open(File.join(PROJECT_ROOT_FOLDER, 'Config', 'Version.public.xcconfig')) do |config| | ||
| configuration = Xcodeproj::Config.new(config) | ||
| version = configuration.attributes['VERSION_SHORT'] | ||
| end | ||
| return version |
There was a problem hiding this comment.
I think that File.open is able to return whatever the block we pass to returns?
Which means we could get rid of the intermediate variable and use this instead:
| version = '' | |
| # If the file is not available, the method will raise so we should be fine | |
| # not handling that case. We'll never return an empty string. | |
| File.open(File.join(PROJECT_ROOT_FOLDER, 'Config', 'Version.public.xcconfig')) do |config| | |
| configuration = Xcodeproj::Config.new(config) | |
| version = configuration.attributes['VERSION_SHORT'] | |
| end | |
| return version | |
| # If the file is not available, the method will raise so we should be fine | |
| # not handling that case. We'll never return an empty string. | |
| File.open(File.join(PROJECT_ROOT_FOLDER, 'Config', 'Version.public.xcconfig')) do |config| | |
| configuration = Xcodeproj::Config.new(config) | |
| configuration.attributes['VERSION_SHORT'] | |
| end |
With the return being implicit in ruby (returning the return value of the last statement, here the File.open, which should itself implicitly return the return value of the last statement of its block, here configuration.attributes['VERSION_SHORT'))
Worth double-checking it works tho, since I haven't tested this and only mention this from memory.
There was a problem hiding this comment.
Thank you for the suggestion @AliSoftware 🙇
I just tried that for an upcoming PR and it works 👌
| struct Config { | ||
| let baseFolder: String | ||
| let baseURLString: String | ||
|
|
||
| static let wordPress = Config( | ||
| baseFolder: "./metadata", | ||
| baseURLString: "https://translate.wordpress.org/projects/apps/ios/release-notes/" | ||
| ) | ||
|
|
||
| static let jetpack = Config( | ||
| baseFolder: "./jetpack_metadata", | ||
| baseURLString: "https://translate.wordpress.com/projects/jetpack/apps/ios/release-notes/" | ||
| ) | ||
|
|
||
| static func config(for argument: String?) -> Config { | ||
| guard let argument = argument else { return .wordPress } | ||
| return argument == "jetpack" ? .jetpack : .wordPress | ||
| } | ||
| } |
There was a problem hiding this comment.
I like it (but it might be because I miss Swift too and playing with it 😅 )
|
|
||
| func downloadTranslation(languageCode: String, folderName: String) { | ||
| func downloadTranslation( | ||
| config: Config = .config(for: CommandLine.arguments.second), |
There was a problem hiding this comment.
That would require us to use a Package.swift to pull the ArgumentParser package first via SwiftPM to be then able to use it, tho 😓
|
|
||
| extension Array where Element == String { | ||
|
|
||
| var second: String? { | ||
| guard count >= 2 else { return .none } | ||
| return self[1] | ||
| } | ||
| } |
There was a problem hiding this comment.
nit (I too want to have some Swift fun! 😄 )
| extension Array where Element == String { | |
| var second: String? { | |
| guard count >= 2 else { return .none } | |
| return self[1] | |
| } | |
| } | |
| extension Array { | |
| var second: Element? { dropFirst().first } | |
| } |
74aa224 to
0b2e571
Compare
Will use it later to add the Jetpack configuration.
0b2e571 to
8ebdc4e
Compare
|
@AliSoftware raised some good points regarding the difference in approaches etc. I'm going to close this one in favor of #16764 for the download part and I'll followup with a different PR or RFC issue regarding the |
|
Please don't delete the branch, I want to keep it around for the other PR and for reference till the work is done. |
|
Followup: the |
Adds the tooling required to download the localized metadata for Jetpack and to upload it to App Store Connect.
How to test
Metadata download
originso that when the release toolkit logic tries to push it won't failgit revert f93a7cfb7bto ensure you'll have localization changes to fetchbundle exec fastlane download_localized_strings_and_metadataHere's the commit history for my test branch if you want to compare.
Metadata upload
Unfortunately, the new lane that uploads metadata to ASC for both WordPress and Jetpack can't be tested end-to-end unless we actually upload the data. If we cancel the
deliversubmission for WordPress, the lane interrupts without giving us a chance to test the Jetpack call. As such, Jetpack needs to be tested in isolation.bundle exec fastlane update_metadata_on_app_store_connectPreview.htmlcontains valid data. In particular, that it's not attempting to upload screenshotsDoes the Preview on path './fastlane/Preview.html' look okay for you? (y/n) nbundle exec fastlane update_jetpack_metadata_on_app_store_connect_Notice how the description says Jetpack, not WordPress 😉
Does the Preview on path './fastlane/Preview.html' look okay for you? (y/n) nOptionally, call one or both lanes with
with_screenshots:trueto see that it will attempt to load the screenshotsObviously, one should only call this lane if they have the screenshots locally
Regression Notes
Potential unintended areas of impact
N.A.
What I did to test those areas of impact (or what existing automated tests I relied on)
N.A.
What automated tests I added (or what prevented me from doing so)
N.A.
RELEASE-NOTES.txtif necessary. N.A.