diff --git a/extensions/azurePublish/src/botProjectDeployConfig.ts b/extensions/azurePublish/src/botProjectDeployConfig.ts index 082613e093..7c8eb885e1 100644 --- a/extensions/azurePublish/src/botProjectDeployConfig.ts +++ b/extensions/azurePublish/src/botProjectDeployConfig.ts @@ -17,7 +17,7 @@ export interface BotProjectDeployConfig { projPath: string; // Logger - logger: (string) => any; + logger: (...args: any[]) => void; // Deploy file path, default is .deployment file deployFilePath?: string; diff --git a/extensions/azurePublish/src/deploy.ts b/extensions/azurePublish/src/deploy.ts index fb95666546..0e26915d6d 100644 --- a/extensions/azurePublish/src/deploy.ts +++ b/extensions/azurePublish/src/deploy.ts @@ -15,7 +15,7 @@ export class BotProjectDeploy { private accessToken: string; private projPath: string; private zipPath: string; - private logger: (string) => any; + private logger: (...args: any[]) => void; private runtime: any; constructor(config: BotProjectDeployConfig) { @@ -149,19 +149,27 @@ export class BotProjectDeploy { const publishEndpoint = `https://${ hostname ? hostname : name + '-' + env }.scm.azurewebsites.net/zipdeploy/?isAsync=true`; + const fileReadStream = fs.createReadStream(zipPath, { autoClose: true }); + fileReadStream.on('error', function (err) { + this.logger('%O', err); + throw err; + }); + try { const response = await rp.post({ uri: publishEndpoint, auth: { bearer: token, }, - body: fs.createReadStream(zipPath), + body: fileReadStream, }); this.logger({ status: BotProjectDeployLoggerType.DEPLOY_INFO, message: response, }); } catch (err) { + // close file read stream + fileReadStream.close(); if (err.statusCode === 403) { throw new Error( `Token expired, please run az account get-access-token, then replace the accessToken in your configuration` diff --git a/extensions/azurePublish/src/index.ts b/extensions/azurePublish/src/index.ts index 9b5fe29f1c..281d69a383 100644 --- a/extensions/azurePublish/src/index.ts +++ b/extensions/azurePublish/src/index.ts @@ -166,9 +166,13 @@ export default async (composer: ExtensionRegistration): Promise => { * @param resourcekey */ private async cleanup(resourcekey: string) { - const projFolder = this.getRuntimeFolder(resourcekey); - await emptyDir(projFolder); - await rmdir(projFolder); + try { + const projFolder = this.getRuntimeFolder(resourcekey); + await emptyDir(projFolder); + await rmdir(projFolder); + } catch (error) { + this.logger('$O', error); + } } /** @@ -196,15 +200,17 @@ export default async (composer: ExtensionRegistration): Promise => { // Create the BotProjectDeploy object, which is used to carry out the deploy action. const azDeployer = new BotProjectDeploy({ subId: subscriptionID, // deprecate - not used - logger: (msg: any) => { - this.logger(msg); - this.logMessages.push(JSON.stringify(msg, null, 2)); + logger: (msg: any, ...args: any[]) => { + this.logger(msg, ...args); + if (msg?.status || msg?.message) { + this.logMessages.push(JSON.stringify(msg, null, 2)); - // update the log messages provided to Composer via the status API. - const status = this.getLoadingStatus(botId, profileName, jobId); - status.result.log = this.logMessages.join('\n'); + // update the log messages provided to Composer via the status API. + const status = this.getLoadingStatus(botId, profileName, jobId); + status.result.log = this.logMessages.join('\n'); - this.updateLoadingStatus(botId, profileName, jobId, status); + this.updateLoadingStatus(botId, profileName, jobId, status); + } }, accessToken: accessToken, projPath: this.getProjectFolder(resourcekey, this.mode), @@ -226,7 +232,7 @@ export default async (composer: ExtensionRegistration): Promise => { await this.cleanup(resourcekey); } } catch (error) { - this.logger(error); + this.logger('%O', error); if (error instanceof Error) { this.logMessages.push(error.message); } else if (typeof error === 'object') { @@ -334,34 +340,53 @@ export default async (composer: ExtensionRegistration): Promise => { runtimeCodePath = project.settings.runtime.path; } - // Prepare the temporary project - // this writes all the settings to the root settings/appsettings.json file - await this.init(project, runtimeCodePath, resourcekey, runtime); + try { + // Prepare the temporary project + // this writes all the settings to the root settings/appsettings.json file + await this.init(project, runtimeCodePath, resourcekey, runtime); + + // Merge all the settings + // this combines the bot-wide settings, the environment specific settings, and 2 new fields needed for deployed bots + // these will be written to the appropriate settings file inside the appropriate runtime plugin. + const mergedSettings = mergeDeep(fullSettings, settings); + + // Prepare parameters and then perform the actual deployment action + const customizeConfiguration: CreateAndDeployResources = { + accessToken, + subscriptionID, + name, + environment, + hostname, + luisResource, + }; + await this.performDeploymentAction( + project, + mergedSettings, + runtime, + project.id, + profileName, + jobId, + resourcekey, + customizeConfiguration + ); + } catch (err) { + this.logger('%O', err); + if (err instanceof Error) { + this.logMessages.push(err.message); + } else if (typeof err === 'object') { + this.logMessages.push(JSON.stringify(err)); + } else { + this.logMessages.push(err); + } - // Merge all the settings - // this combines the bot-wide settings, the environment specific settings, and 2 new fields needed for deployed bots - // these will be written to the appropriate settings file inside the appropriate runtime plugin. - const mergedSettings = mergeDeep(fullSettings, settings); + const response = this.getLoadingStatus(project.id, profileName, jobId); + response.status = 500; + response.result.message = this.logMessages[this.logMessages.length - 1]; - // Prepare parameters and then perform the actual deployment action - const customizeConfiguration: CreateAndDeployResources = { - accessToken, - subscriptionID, - name, - environment, - hostname, - luisResource, - }; - await this.performDeploymentAction( - project, - mergedSettings, - runtime, - project.id, - profileName, - jobId, - resourcekey, - customizeConfiguration - ); + await this.updateHistory(project.id, profileName, { status: response.status, ...response.result }); + this.removeLoadingStatus(project.id, profileName, jobId); + this.cleanup(resourcekey); + } }; /************************************************************************************************** @@ -414,7 +439,7 @@ export default async (composer: ExtensionRegistration): Promise => { this.asyncPublish(config, project, resourcekey, jobId); } catch (err) { - console.log(err); + this.logger('%O', err); if (err instanceof Error) { this.logMessages.push(err.message); } else if (typeof err === 'object') { diff --git a/extensions/azurePublish/src/luisAndQnA.ts b/extensions/azurePublish/src/luisAndQnA.ts index 957fff270a..474c763713 100644 --- a/extensions/azurePublish/src/luisAndQnA.ts +++ b/extensions/azurePublish/src/luisAndQnA.ts @@ -10,7 +10,7 @@ import { ILuisConfig, FileInfo, IQnAConfig } from '@botframework-composer/types' import { ICrossTrainConfig, createCrossTrainConfig } from './utils/crossTrainUtil'; import { BotProjectDeployLoggerType } from './botProjectLoggerType'; -import { luImportResolverGenerator } from '@bfc/shared/lib/luBuildResolver' +import { luImportResolverGenerator } from '@bfc/shared/lib/luBuildResolver'; const crossTrainer = require('@microsoft/bf-lu/lib/parser/cross-train/crossTrainer.js'); const luBuild = require('@microsoft/bf-lu/lib/parser/lubuild/builder.js'); diff --git a/extensions/azurePublish/src/schema.ts b/extensions/azurePublish/src/schema.ts index 959aa70772..df1a161cec 100644 --- a/extensions/azurePublish/src/schema.ts +++ b/extensions/azurePublish/src/schema.ts @@ -29,7 +29,8 @@ const schema: JSONSchema7 = { }, runtimeIdentifier: { type: 'string', - title: 'Runtime identifier for hosting bot, default to win-x64, please refer to https://docs.microsoft.com/en-us/dotnet/core/rid-catalog' + title: + 'Runtime identifier for hosting bot, default to win-x64, please refer to https://docs.microsoft.com/en-us/dotnet/core/rid-catalog', }, settings: { type: 'object',