windows-dev-center-vsts-ext.../tasks/store-flight-V1/flight.ts

234 строки
8.1 KiB
TypeScript

/*
* Behavior for the Flight task. Takes authentication information, app information, and packages,
* and flights to the Store.
*/
import api = require('../common/apiHelper');
import request = require('../common/requestHelper');
import Q = require('q');
import tl = require('azure-pipelines-task-lib');
/** Core parameters for the flight task. */
export interface CoreFlightParams
{
endpoint: string;
/** Name of the flight we are publishing packages to */
flightName: string;
/** The credentials used to authenticate to the store. */
authentication: request.Credentials;
/**
* If true, delete any pending submissions before starting a new one.
* Otherwise, fail the task if a submission is pending.
*/
force: boolean;
/** A list of paths to the packages to be uploaded. */
packages: string[];
/** A path where the zip file to be uploaded to the dev center will be stored. */
zipFilePath: string;
/** If true, we will exit immediately after commit without polling submission till app is published. */
skipPolling: boolean;
/**If provided, specifies number of packages per unique target device family and target platform to keep in the flight group. */
numberOfPackagesToKeep?: number;
/** If provided, specified the number of hours to differ until the packages in this submission become mandatory. */
mandatoryUpdateDifferHours?: number;
}
export interface AppIdParam
{
appId: string;
}
export interface AppNameParam
{
appName: string;
}
export type ParamsWithAppId = AppIdParam & CoreFlightParams;
export type ParamsWithAppName = AppNameParam & CoreFlightParams;
export type FlightParams = ParamsWithAppId | ParamsWithAppName;
/**
* Type guard: indicates whether these parameters contain an App Id or not.
*/
export function hasAppId(p: FlightParams): p is ParamsWithAppId
{
return (<ParamsWithAppId>p).appId != undefined;
}
/**
* The parameters given to the task. They're declared here to be
* available to every step of the task without explicitly threading them through.
*/
var taskParams: FlightParams;
/** The current token used for authentication. */
var currentToken: request.AccessToken;
var appId: string;
var flightId: string;
export async function flightTask(params: FlightParams)
{
taskParams = params;
/* We expect the endpoint part of this to not have a slash at the end.
* This is because authenticating to 'endpoint/' will give us an
* invalid token, while authenticating to 'endpoint' will work */
api.ROOT = taskParams.endpoint + api.API_URL_VERSION_PART;
console.log('Authenticating...');
currentToken = await request.authenticate(taskParams.endpoint, taskParams.authentication);
console.log('Obtaining app information...');
var appResource = await getAppResource();
appId = appResource.id; // Globally set app ID for future steps.
console.log(`Obtaining flight resource for flight ${taskParams.flightName} in app ${appId}`);
var flightResource = await getFlightResource(taskParams.flightName);
flightId = flightResource.flightId; // Globally set app ID for future steps.
// Delete pending submission if force is turned on (only one pending submission can exist)
if (taskParams.force && flightResource.pendingFlightSubmission != undefined)
{
console.log('Deleting existing flight submission...');
await deleteFlightSubmission(flightResource.pendingFlightSubmission.resourceLocation);
}
console.log('Creating flight submission...');
var flightSubmissionResource = await createFlightSubmission();
var submissionUrl = `https://developer.microsoft.com/en-us/dashboard/apps/${appId}/submissions/${flightSubmissionResource.id}`;
console.log(`Submission ${submissionUrl} was created successfully`);
if (taskParams.numberOfPackagesToKeep != null)
{
console.log('Deleting old packages...');
api.deleteOldPackages(flightSubmissionResource.flightPackages, taskParams.numberOfPackagesToKeep);
}
console.log('Updating package delivery options...');
await api.updatePackageDeliveryOptions( flightSubmissionResource, taskParams.mandatoryUpdateDifferHours);
console.log('Updating flight submission...');
await putFlightSubmission(flightSubmissionResource);
console.log('Creating zip file...');
var zip = api.createZipFromPackages(taskParams.packages);
if (Object.keys(zip.files).length > 0)
{
await api.persistZip(zip, taskParams.zipFilePath, flightSubmissionResource.fileUploadUrl);
}
console.log('Committing flight submission...');
await commitFlightSubmission(flightSubmissionResource.id);
if (taskParams.skipPolling)
{
console.log('Skip polling option is checked. Skipping polling...');
console.log(`Click here ${submissionUrl} to check the status of the submission in Dev Center`);
}
else
{
console.log('Polling flight submission...');
var resourceLocation = `applications/${appId}/flights/${flightId}/submissions/${flightSubmissionResource.id}`;
await api.pollSubmissionStatus(currentToken, resourceLocation, flightSubmissionResource.targetPublishMode);
}
// Attach summary file for easy access to submission on Dev Center from release Summary tab
var summaryText = api.buildSummaryText(appResource.primaryName, flightResource.friendlyName, submissionUrl, taskParams.skipPolling ? 'publishing' : 'in the store');
api.attachSubmissionSummary(summaryText);
tl.setResult(tl.TaskResult.Succeeded, 'Flight submission completed');
}
/**
* @return Promises the resource associated with the application given to the task.
*/
async function getAppResource()
{
var appId;
if (hasAppId(taskParams))
{
appId = taskParams.appId;
}
else
{
tl.debug(`Getting app ID from name ${taskParams.appName}`);
appId = await api.getAppIdByName(currentToken, taskParams.appName);
}
return api.getAppResource(currentToken, appId);
}
function getFlightResource(flightName: string, currentPage?: string): Q.Promise<any>
{
if (currentPage === undefined)
{
currentPage = `applications/${appId}/listflights`;
}
tl.debug(`\tSearching for flight ${flightName} on ${currentPage}`);
var requestParams = {
url: api.ROOT + currentPage,
method: 'GET'
};
return request.performAuthenticatedRequest<any>(currentToken, requestParams).then(body =>
{
var foundFlightResource = (<any[]>body.value).find(x => x.friendlyName == flightName);
if (foundFlightResource)
{
tl.debug(`Flight found with ID ${foundFlightResource.flightId}`);
return foundFlightResource;
}
if (body['@nextLink'] === undefined)
{
throw new Error(`No flight with name "${flightName}" was found`);
}
return getFlightResource(flightName, body['@nextLink']);
});
}
/** Promises the deletion of a flight submission resource */
function deleteFlightSubmission(location: string): Q.Promise<void>
{
return api.deleteSubmission(currentToken, `${api.ROOT}applications/${appId}/${location}`);
}
/** Promises a resource for a new flight submission. */
function createFlightSubmission(): Q.Promise<any>
{
return api.createSubmission(currentToken, `${api.ROOT}applications/${appId}/flights/${flightId}/submissions`);
}
/**
* Adds packages to a flight submission resource as Pending Upload, then commits the submission.
* @return Promises the update of the submission resource.
*/
function putFlightSubmission(flightSubmissionResource: any): Q.Promise<void>
{
api.includePackagesInSubmission(taskParams.packages, flightSubmissionResource.flightPackages);
var url = `${api.ROOT}applications/${appId}/flights/${flightId}/submissions/${flightSubmissionResource.id}`;
return api.putSubmission(currentToken, url, flightSubmissionResource);
}
/** Promises the committing of the given flight submission. */
function commitFlightSubmission(flightSubmissionId: string): Q.Promise<void>
{
return api.commitSubmission(currentToken, `${api.ROOT}applications/${appId}/flights/${flightId}/submissions/${flightSubmissionId}/commit`);
}