Skip to content

Commit

Permalink
[GooglePlayReleaseV3] Fix task behavior when the "Update APK" option …
Browse files Browse the repository at this point in the history
…unchecked (#193)

Added 'Update only store listing option', which provides the possibility to update store listing metadata without reassigning APK files.
  • Loading branch information
Alexandr Smolaykov authored Jul 30, 2020
1 parent b9d2740 commit 014f169
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 28 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
53 changes: 34 additions & 19 deletions Tasks/google-play-release/GooglePlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -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();
Expand All @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -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[],
Expand Down Expand Up @@ -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<googleutil.ReleaseNotes[]> {
async function addAllReleaseNotes(apkVersionCodes: number[], languageCode: string, directory: string): Promise<googleutil.ReleaseNotes[]> {
const changelogDir: string = path.join(directory, 'changelogs');

const changelogs: string[] = filterDirectoryContents(changelogDir, stat => stat.isFile());
Expand Down Expand Up @@ -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<googleutil.ReleaseNotes[]> {
async function addMetadata(edits: pub3.Resource$Edits, apkVersionCodes: number[], metadataRootDirectory: string): Promise<googleutil.ReleaseNotes[]> {
const metadataLanguageCodes: string[] = filterDirectoryContents(metadataRootDirectory, stat => stat.isDirectory());
tl.debug(`Found language codes: ${metadataLanguageCodes}`);

Expand All @@ -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<googleutil.ReleaseNotes[]> {
async function uploadMetadataWithLanguageCode(edits: pub3.Resource$Edits, apkVersionCodes: number[], languageCode: string, directory: string): Promise<googleutil.ReleaseNotes[]> {
console.log(tl.loc('UploadingMetadataForLanguage', directory, languageCode));

tl.debug(`Adding localized store listing for language code ${languageCode} from ${directory}`);
Expand All @@ -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) ||
Expand Down Expand Up @@ -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)}`);

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down Expand Up @@ -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"
}
4 changes: 3 additions & 1 deletion Tasks/google-play-release/googleutil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Track> {
tl.debug('Updating track');
const release: AndroidRelease = {
versionCodes: (typeof versionCode === 'number' ? [versionCode] : versionCode)
};
Expand Down Expand Up @@ -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;
}

Expand Down
17 changes: 14 additions & 3 deletions Tasks/google-play-release/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
],
"version": {
"Major": "3",
"Minor": "172",
"Minor": "174",
"Patch": "0"
},
"minimumAgentVersion": "1.83.0",
Expand Down Expand Up @@ -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."
},
{
Expand Down Expand Up @@ -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"
}
}
}
17 changes: 14 additions & 3 deletions Tasks/google-play-release/task.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
],
"version": {
"Major": "3",
"Minor": "172",
"Minor": "174",
"Patch": "0"
},
"minimumAgentVersion": "1.83.0",
Expand Down Expand Up @@ -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"
},
{
Expand Down Expand Up @@ -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"
}
}
Binary file added images/update-store-listing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 014f169

Please sign in to comment.