@bubblewrap/core
Version:
Core Library to generate, build and sign TWA projects
196 lines (195 loc) • 8.78 kB
JavaScript
;
/*
* Copyright 2021 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.GooglePlay = exports.PlayStoreTracks = void 0;
exports.asPlayStoreTrack = asPlayStoreTrack;
const googleapis_1 = require("googleapis");
const fs_1 = require("fs");
// Possible values for release tracks
const TRACK_VALUES = ['alpha', 'beta', 'internal', 'production'];
exports.PlayStoreTracks = [...TRACK_VALUES];
const PLAY_API_TIMEOUT_MS = 180000;
function asPlayStoreTrack(input) {
if (!input) {
return null;
}
return TRACK_VALUES.includes(input) ? input : null;
}
class GooglePlay {
/**
* Constructs a Google Play object with the gradleWrapper so we can use a
* gradle plugin to communicate with Google Play.
*
* @param serviceAccountJsonFilePath This is the service account file to communicate with the
* play publisher API.
*/
constructor(serviceAccountJsonFilePath) {
this._googlePlayApi = this.getAndroidClient(serviceAccountJsonFilePath);
if (!this._googlePlayApi) {
throw new Error('Could not create a Google Play API client');
}
}
/**
* Generates an editId that should be used in all play operations.
* @param packageName - The package that will be worked upon.
* @param operation - The Play Operation which requires an editId injected
* @param commitEdit - Whether or not this edit is a action or query edit. true indicates an action edit.
* @returns - A PlayOperationResult which contains the output of the operation in a field that was worked upon.
*/
async performPlayOperation(packageName, operation, commitEdit = false) {
const editId = await this.startPlayOperation(packageName);
const result = await operation(editId);
if (!commitEdit) {
await this.endPlayOperation(packageName, editId);
}
return result;
}
/**
* This calls the publish bundle command and publishes an existing artifact to Google
* Play.
* Calls the following Play API commands in order:
* Edits.Insert
* Edits.Bundles.Upload
* Edits.Tracks.Update
* Edits.Commit
* https://developers.google.com/android-publisher/api-ref/rest/v3/edits.bundles/upload
* https://developers.google.com/android-publisher/edits#workflow
* @param track - Specifies the track that the user would like to publish to.
* @param filepath - Filepath of the App bundle you would like to upload.
* @param packageName - Package name of the bundle.
* @param retainedBundles - All bundles that should be retained on upload. This is useful for
* ChromeOS only releases.
*/
async publishBundle(track, filepath, packageName, retainedBundles, editId) {
const result = await this._googlePlayApi.edits.bundles.upload({
ackBundleInstallationWarning: false,
editId: editId,
packageName: packageName,
media: {
body: (0, fs_1.createReadStream)(filepath),
},
}, {
timeout: PLAY_API_TIMEOUT_MS,
});
const versionCodeUploaded = result.data.versionCode;
if (!versionCodeUploaded) {
throw new Error('Version code could not be found from Play API.');
}
const retainedBundlesStr = retainedBundles.map((n) => n.toString());
await this.addBundleToTrack(track, [versionCodeUploaded.toString(), ...retainedBundlesStr], packageName, editId);
await this._googlePlayApi.edits.commit({
changesNotSentForReview: false,
editId: editId,
packageName: packageName,
});
}
/**
* This calls the Edits.Tracks.Update play publisher api command. This will do the updating of
* the user selected track for the reelase to the play store.
* @param track - Specifies the track that the user would like to publish to.
* @param versionCodes - Specifies all versions of the app bundle to be included on release
* (including retained artifacts).
* @param packageName - packageName of the bundle.
* @param editId - The current edit hosted on Google Play.
*/
async addBundleToTrack(track, versionCodes, packageName, editId) {
const tracksUpdate = {
track: track,
packageName: packageName,
editId: editId,
requestBody: {
releases: [{
versionCodes: versionCodes,
status: 'completed',
}],
track: track,
},
};
await this._googlePlayApi.edits.tracks.update(tracksUpdate);
}
/**
* Connects to the Google Play Console and retrieves a list of all Android App Bundles for the
* given packageName. Finds the largest versionCode of those bundles and returns it. Considers
* both ChromeOS and Android Releases.
* @param packageName - The packageName of the versionCode we are looking up.
*/
async getLargestVersionCode(packageName, editId) {
const bundleResponse = await this._googlePlayApi.edits.bundles.list({ packageName: packageName, editId: editId });
if (!bundleResponse.data.bundles) {
throw new Error('No bundles found from Google Play');
}
const versionCode = Math.max(...bundleResponse.data.bundles.map((bundle) => bundle.versionCode));
return versionCode;
}
/**
* Starts an edit on the Play servers. This is the basis for any play publishing api operation.
* @param packageName - the packageName of the app we want to interact with.
*/
async startPlayOperation(packageName) {
const edit = await this._googlePlayApi.edits.insert({ packageName: packageName });
const editId = edit.data.id;
if (!editId) {
throw new Error('Could not create a Google Play edit');
}
return editId;
}
/**
* Cancels the edit in progress on the Play server.
* @param packageName - The packageName of the app we are interacting with.
* @param editId - The editId that is currently in progress.
*/
async endPlayOperation(packageName, editId) {
await this._googlePlayApi.edits.delete({ editId: editId, packageName: packageName });
}
/**
* Checks to see if the version that we want to retain already exists within the Play Store.
* @param packageName - The packageName of the versionCode we are looking up.
* @param versionCode - The version code of the APK / Bundle we want to retain.
*/
async versionExists(packageName, versionCode, editId) {
var _a, _b;
const uploadedApks = await this._googlePlayApi.edits.apks.list({ packageName: packageName, editId: editId });
let found = (_a = uploadedApks.data.apks) === null || _a === void 0 ? void 0 : _a.find((obj) => obj.versionCode == versionCode);
if (found) {
return true;
}
const uploadedBundles = await this._googlePlayApi.edits.bundles.list({ packageName: packageName, editId: editId });
found = (_b = uploadedBundles.data.bundles) === null || _b === void 0 ? void 0 : _b.find((obj) => obj.versionCode == versionCode);
if (found) {
return true;
}
return false;
}
/**
* This fetches the Android client using the bubblewrap configuration file.
* @param serviceAccountJsonFilePath - The file path to the service account file. This allows
* communication to the Play Publisher API.
*/
getAndroidClient(serviceAccountJsonFilePath) {
// Initialize the Google API Client from service account credentials
const jwtClient = new googleapis_1.google.auth.JWT({
keyFile: serviceAccountJsonFilePath,
scopes: ['https://www.googleapis.com/auth/androidpublisher'],
});
// Connect to the Google Play Developer API with JWT Client
return googleapis_1.google.androidpublisher({
version: 'v3',
auth: jwtClient,
});
}
}
exports.GooglePlay = GooglePlay;