From 014f1691b5d1ea88812ca82797bc4ecabb8006b3 Mon Sep 17 00:00:00 2001 From: Alexandr Smolaykov Date: Thu, 30 Jul 2020 21:31:31 +0300 Subject: [PATCH] [GooglePlayReleaseV3] Fix task behavior when the "Update APK" option unchecked (#193) Added 'Update only store listing option', which provides the possibility to update store listing metadata without reassigning APK files. --- README.md | 6 +- Tasks/google-play-release/GooglePlay.ts | 53 +++++++++++------- .../resources.resjson/en-US/resources.resjson | 5 +- Tasks/google-play-release/googleutil.ts | 4 +- Tasks/google-play-release/task.json | 17 +++++- Tasks/google-play-release/task.loc.json | 17 +++++- images/update-store-listing.png | Bin 0 -> 2939 bytes 7 files changed, 74 insertions(+), 28 deletions(-) create mode 100644 images/update-store-listing.png diff --git a/README.md b/README.md index 4cd95429..cad7984a 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,11 @@ $(Specified Directory) └ $(versioncodes).txt ``` -9. **Update APK(s)** *(Boolean, Optional)* - By default, the task will update the specified binary APK file(s) on your app release. By unselecting this option you can update metadata keeping the APKs untouched. Default value is _true_. +9. **Update only store listing** *(Boolean, Optional)* - By default, the task will update the specified track and selected APK file(s) will be assigned to the related track. By selecting this option you can update only store listing. Default value is _false_. + + ![Advanced Options](images//update-store-listing.png) + +10. **Update APK(s)** *(Boolean, Optional)* - By default, the task will update the specified binary APK file(s) on your app release. By unselecting this option you can update metadata keeping the APKs untouched. Default value is _true_. ![Update APKs](images/update-apks.png) diff --git a/Tasks/google-play-release/GooglePlay.ts b/Tasks/google-play-release/GooglePlay.ts index d266dbad..30cec381 100644 --- a/Tasks/google-play-release/GooglePlay.ts +++ b/Tasks/google-play-release/GooglePlay.ts @@ -4,6 +4,8 @@ import * as tl from 'azure-pipelines-task-lib/task'; import * as glob from 'glob'; import * as apkReader from 'adbkit-apkreader'; import * as googleutil from './googleutil'; +import { androidpublisher_v3 as pub3 } from 'googleapis'; +import { JWT } from 'google-auth-library'; async function run() { try { @@ -28,7 +30,7 @@ async function run() { key.client_email = serviceEndpoint.parameters['username']; key.private_key = serviceEndpoint.parameters['password'].replace(/\\n/g, '\n'); } - const mainApkPattern = tl.getPathInput('apkFile', true); + const mainApkPattern: string = tl.getPathInput('apkFile', true); tl.debug(`Main APK pattern: ${mainApkPattern}`); const mainApkFile: string = resolveGlobPath(mainApkPattern); @@ -58,10 +60,11 @@ async function run() { const userFraction: number = Number(userFractionSupplied ? tl.getInput('userFraction', false) : 1.0); const shouldAttachMetadata: boolean = tl.getBoolInput('shouldAttachMetadata', false); + const updateStoreListing: boolean = tl.getBoolInput('updateStoreListing', false); const shouldUploadApks: boolean = tl.getBoolInput('shouldUploadApks', false); - const shouldPickObbFile = tl.getBoolInput('shouldPickObbFile', false); - const shouldPickObbFileForAdditonalApks = tl.getBoolInput('shouldPickObbFileForAdditonalApks', false); + const shouldPickObbFile: boolean = tl.getBoolInput('shouldPickObbFile', false); + const shouldPickObbFileForAdditonalApks: boolean = tl.getBoolInput('shouldPickObbFileForAdditonalApks', false); let changelogFile: string = null; let languageCode: string = null; @@ -78,10 +81,10 @@ async function run() { const apkVersionCodes: number[] = []; // The submission process is composed - // of a transction with the following steps: + // of a transaction with the following steps: // ----------------------------------------- // #1) Extract the package name from the specified APK file - // #2) Get an OAuth token by authentincating the service account + // #2) Get an OAuth token by authenticating the service account // #3) Create a new editing transaction // #4) Upload the new APK(s) // #5) Specify the track that should be used for the new APK (e.g. alpha, beta) @@ -93,11 +96,11 @@ async function run() { googleutil.updateGlobalParams(globalParams, 'packageName', packageName); tl.debug('Initializing JWT.'); - const jwtClient: any = googleutil.getJWT(key); + const jwtClient: JWT = googleutil.getJWT(key); globalParams.auth = jwtClient; tl.debug('Initializing Google Play publisher API.'); - const edits: any = googleutil.publisher.edits; + const edits: pub3.Resource$Edits = googleutil.publisher.edits; tl.debug('Authorize JWT.'); await jwtClient.authorize(); @@ -108,7 +111,10 @@ async function run() { googleutil.updateGlobalParams(globalParams, 'editId', edit.id); let requireTrackUpdate = false; - if (shouldUploadApks) { + + if (updateStoreListing) { + tl.debug('Selected store listing update -> skip APK reading'); + } else if (shouldUploadApks) { tl.debug(`Uploading ${apkFileList.length} APK(s).`); requireTrackUpdate = true; @@ -154,7 +160,10 @@ async function run() { console.log(tl.loc('AttachingMetadataToRelease')); tl.debug(`Uploading metadata from ${metadataRootPath}`); releaseNotes = await addMetadata(edits, apkVersionCodes, metadataRootPath); - requireTrackUpdate = true; + if (updateStoreListing) { + tl.debug('Selected store listing update -> skip update track'); + } + requireTrackUpdate = !updateStoreListing; } else if (changelogFile) { tl.debug(`Uploading the common change log ${changelogFile} to all versions`); const commonNotes = await getCommonReleaseNotes(languageCode, changelogFile); @@ -172,8 +181,13 @@ async function run() { tl.debug('Committing the edit transaction in Google Play.'); await edits.commit(); - console.log(tl.loc('AptPublishSucceed')); - console.log(tl.loc('TrackInfo', track)); + if (updateStoreListing) { + console.log(tl.loc('StoreListUpdateSucceed')); + } else { + console.log(tl.loc('AptPublishSucceed')); + console.log(tl.loc('TrackInfo', track)); + } + tl.setResult(tl.TaskResult.Succeeded, tl.loc('Success')); } catch (e) { if (e) { @@ -194,11 +208,12 @@ async function run() { * @param {string} versionCodeListType type of version code replacement filter, i.e. 'all', 'list', or 'expression' * @param {string | string[]} versionCodeFilter version code filter, i.e. either a list of version code or a regular expression string. * @param {double} userFraction the fraction of users to get update + * @param {googleutil.ReleaseNotes[]} releaseNotes optional release notes to be attached as part of the update * @returns {Promise} track A promise that will return result from updating a track * { track: string, versionCodes: [integer], userFraction: double } */ async function updateTrack( - edits: any, + edits: pub3.Resource$Edits, packageName: string, track: string, apkVersionCodes: number[], @@ -310,7 +325,7 @@ function getChangelog(changelogFile: string): string { * @param {string} directory Directory with a changesogs folder where release notes can be found. * @returns nothing */ -async function addAllReleaseNotes(apkVersionCodes: any, languageCode: string, directory: string): Promise { +async function addAllReleaseNotes(apkVersionCodes: number[], languageCode: string, directory: string): Promise { const changelogDir: string = path.join(directory, 'changelogs'); const changelogs: string[] = filterDirectoryContents(changelogDir, stat => stat.isFile()); @@ -394,7 +409,7 @@ function filterDirectoryContents(directory: string, filter: (stats: tl.FsStats) * @param {string} metadataRootDirectory Path to the folder where the Fastlane metadata structure is found. eg the folders under this directory should be the language codes * @returns nothing */ -async function addMetadata(edits: any, apkVersionCodes: number[], metadataRootDirectory: string): Promise { +async function addMetadata(edits: pub3.Resource$Edits, apkVersionCodes: number[], metadataRootDirectory: string): Promise { const metadataLanguageCodes: string[] = filterDirectoryContents(metadataRootDirectory, stat => stat.isDirectory()); tl.debug(`Found language codes: ${metadataLanguageCodes}`); @@ -418,7 +433,7 @@ async function addMetadata(edits: any, apkVersionCodes: number[], metadataRootDi * @param {string} directory Directory where updated listing details can be found. * @returns nothing */ -async function uploadMetadataWithLanguageCode(edits: any, apkVersionCodes: number[], languageCode: string, directory: string): Promise { +async function uploadMetadataWithLanguageCode(edits: pub3.Resource$Edits, apkVersionCodes: number[], languageCode: string, directory: string): Promise { console.log(tl.loc('UploadingMetadataForLanguage', directory, languageCode)); tl.debug(`Adding localized store listing for language code ${languageCode} from ${directory}`); @@ -440,7 +455,7 @@ async function uploadMetadataWithLanguageCode(edits: any, apkVersionCodes: numbe * @param {string} directory Directory where updated listing details can be found. * @returns nothing */ -async function addLanguageListing(edits: any, languageCode: string, directory: string) { +async function addLanguageListing(edits: pub3.Resource$Edits, languageCode: string, directory: string) { const listingResource: googleutil.AndroidListingResource = createListingResource(languageCode, directory); const isPatch:boolean = (!listingResource.fullDescription) || @@ -527,7 +542,7 @@ function createListingResource(languageCode: string, directory: string): googleu * @param {string} directory Directory where updated listing details can be found. * @returns nothing */ -async function attachImages(edits: any, languageCode: string, directory: string) { +async function attachImages(edits: pub3.Resource$Edits, languageCode: string, directory: string) { const imageList: { [key: string]: string[] } = getImageList(directory); tl.debug(`Found ${languageCode} images: ${JSON.stringify(imageList)}`); @@ -558,7 +573,7 @@ async function attachImages(edits: any, languageCode: string, directory: string) * @param {string} imageType type of images. * @returns nothing */ -async function removeOldImages(edits: any, languageCode: string, imageType: string) { +async function removeOldImages(edits: pub3.Resource$Edits, languageCode: string, imageType: string) { try { let imageRequest: googleutil.PackageParams = { language: languageCode, @@ -690,7 +705,7 @@ function getImageList(directory: string): { [key: string]: string[] } { * @param {string} imagePath Path to image to attempt upload with * @returns nothing */ -async function uploadImage(edits: any, languageCode: string, imageType: string, imagePath: string) { +async function uploadImage(edits: pub3.Resource$Edits, languageCode: string, imageType: string, imagePath: string) { const imageRequest: googleutil.PackageParams = { language: languageCode, imageType: imageType diff --git a/Tasks/google-play-release/Strings/resources.resjson/en-US/resources.resjson b/Tasks/google-play-release/Strings/resources.resjson/en-US/resources.resjson index 29e6a875..cddf1135 100644 --- a/Tasks/google-play-release/Strings/resources.resjson/en-US/resources.resjson +++ b/Tasks/google-play-release/Strings/resources.resjson/en-US/resources.resjson @@ -26,6 +26,8 @@ "loc.input.help.languageCode": "An IETF language tag identifying the language of the release notes as specified in the BCP-47 document. Default value is _en-US_", "loc.input.label.metadataRootPath": "Metadata root directory", "loc.input.help.metadataRootPath": "The path to the metadata folder with the fastlane metadata structure.", + "loc.input.label.updateStoreListing": "Update only store listing", + "loc.input.help.updateStoreListing": " By default, the task will update the specified track and selected APK file(s) will be assigned to the related track. By selecting this option you can update only store listing.", "loc.input.label.shouldUploadApks": "Update APK(s)", "loc.input.help.shouldUploadApks": "By default, the task will update the specified binary APK file(s) on your app release. By unselecting this option you can update metadata keeping the APKs untouched.", "loc.input.label.shouldUploadMappingFile": "Upload deobfuscation file (mapping.txt)", @@ -70,5 +72,6 @@ "loc.messages.CannotUpdateTrack": "Failed to update track %s information. Failed with message: %s.", "loc.messages.CannotReadChangeLog": "Failed to read change log %s. Failed with message: %s.", "loc.messages.CannotCreateListing": "Failed to create the localized %s store listing. Failed with message: %s.", - "loc.messages.IncorrectVersionCodeFilter": "Version code list specified contains incorrect codes: %s" + "loc.messages.IncorrectVersionCodeFilter": "Version code list specified contains incorrect codes: %s", + "loc.messages.StoreListUpdateSucceed": "Store list metadata successfully updated" } \ No newline at end of file diff --git a/Tasks/google-play-release/googleutil.ts b/Tasks/google-play-release/googleutil.ts index e5e65a22..4e12d755 100644 --- a/Tasks/google-play-release/googleutil.ts +++ b/Tasks/google-play-release/googleutil.ts @@ -167,7 +167,6 @@ export async function getTrack(edits: any, packageName: string, track: string): * { track: string, versionCodes: [integer], userFraction: double } */ export async function updateTrack(edits: any, packageName: string, track: string, versionCode: any, userFraction: number, releaseNotes?: ReleaseNotes[]): Promise { - tl.debug('Updating track'); const release: AndroidRelease = { versionCodes: (typeof versionCode === 'number' ? [versionCode] : versionCode) }; @@ -195,7 +194,10 @@ export async function updateTrack(edits: any, packageName: string, track: string }; tl.debug('Additional Parameters: ' + JSON.stringify(requestParameters)); + + tl.debug('Updating track'); const updatedTrack = await edits.tracks.update(requestParameters); + return updatedTrack.data; } diff --git a/Tasks/google-play-release/task.json b/Tasks/google-play-release/task.json index a1a07024..775fd577 100644 --- a/Tasks/google-play-release/task.json +++ b/Tasks/google-play-release/task.json @@ -14,7 +14,7 @@ ], "version": { "Major": "3", - "Minor": "172", + "Minor": "174", "Patch": "0" }, "minimumAgentVersion": "1.83.0", @@ -144,12 +144,22 @@ "helpMarkDown": "The path to the metadata folder with the fastlane metadata structure.", "visibleRule": "shouldAttachMetadata = true" }, + { + "name": "updateStoreListing", + "type": "boolean", + "label": "Update only store listing", + "defaultValue": false, + "required": false, + "helpMarkDown": " By default, the task will update the specified track and selected APK file(s) will be assigned to the related track. By selecting this option you can update only store listing.", + "visibleRule": "shouldAttachMetadata = true" + }, { "name": "shouldUploadApks", "type": "boolean", "label": "Update APK(s)", "defaultValue": true, "required": false, + "visibleRule": "updateStoreListing = false", "helpMarkDown": "By default, the task will update the specified binary APK file(s) on your app release. By unselecting this option you can update metadata keeping the APKs untouched." }, { @@ -262,6 +272,7 @@ "CannotUpdateTrack": "Failed to update track %s information. Failed with message: %s.", "CannotReadChangeLog": "Failed to read change log %s. Failed with message: %s.", "CannotCreateListing": "Failed to create the localized %s store listing. Failed with message: %s.", - "IncorrectVersionCodeFilter": "Version code list specified contains incorrect codes: %s" + "IncorrectVersionCodeFilter": "Version code list specified contains incorrect codes: %s", + "StoreListUpdateSucceed": "Store list metadata successfully updated" } -} \ No newline at end of file +} diff --git a/Tasks/google-play-release/task.loc.json b/Tasks/google-play-release/task.loc.json index b1a3a738..285cf2dc 100644 --- a/Tasks/google-play-release/task.loc.json +++ b/Tasks/google-play-release/task.loc.json @@ -14,7 +14,7 @@ ], "version": { "Major": "3", - "Minor": "172", + "Minor": "174", "Patch": "0" }, "minimumAgentVersion": "1.83.0", @@ -144,12 +144,22 @@ "helpMarkDown": "ms-resource:loc.input.help.metadataRootPath", "visibleRule": "shouldAttachMetadata = true" }, + { + "name": "updateStoreListing", + "type": "boolean", + "label": "ms-resource:loc.input.label.updateStoreListing", + "defaultValue": false, + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.updateStoreListing", + "visibleRule": "shouldAttachMetadata = true" + }, { "name": "shouldUploadApks", "type": "boolean", "label": "ms-resource:loc.input.label.shouldUploadApks", "defaultValue": true, "required": false, + "visibleRule": "updateStoreListing = false", "helpMarkDown": "ms-resource:loc.input.help.shouldUploadApks" }, { @@ -262,7 +272,8 @@ "CannotUpdateTrack": "ms-resource:loc.messages.CannotUpdateTrack", "CannotReadChangeLog": "ms-resource:loc.messages.CannotReadChangeLog", "CannotCreateListing": "ms-resource:loc.messages.CannotCreateListing", - "IncorrectVersionCodeFilter": "ms-resource:loc.messages.IncorrectVersionCodeFilter" + "IncorrectVersionCodeFilter": "ms-resource:loc.messages.IncorrectVersionCodeFilter", + "StoreListUpdateSucceed": "ms-resource:loc.messages.StoreListUpdateSucceed" }, "helpMarkDown": "ms-resource:loc.helpMarkDown" -} \ No newline at end of file +} diff --git a/images/update-store-listing.png b/images/update-store-listing.png new file mode 100644 index 0000000000000000000000000000000000000000..bc6e3c95b4f80b6b5a0e3401c136cfcc8c5e08ab GIT binary patch literal 2939 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 literal 0 HcmV?d00001