11import { IBotProject } from '@botframework-composer/types' ;
22import { join } from 'path' ;
3- import { createReadStream , createWriteStream } from 'fs' ;
4- import { ensureDirSync , remove } from 'fs-extra' ;
3+ import { createWriteStream } from 'fs' ;
4+ import { ensureDirSync } from 'fs-extra' ;
55import fetch , { RequestInit } from 'node-fetch' ;
6+ import stream from 'stream' ;
67
78import {
89 PVAPublishJob ,
@@ -42,47 +43,39 @@ export const publish = async (
4243 const { comment = '' } = metadata ;
4344
4445 try {
46+ logger . log ( 'Starting publish to Power Virtual Agents.' ) ;
4547 // authenticate with PVA
4648 const base = baseUrl || getBaseUrl ( ) ;
4749 const creds = getAuthCredentials ( base ) ;
4850 const accessToken = await getAccessToken ( creds ) ;
4951
50- // TODO: Investigate optimizing stream logic before enabling extension.
51- // (https://github.com/microsoft/BotFramework-Composer/pull/4446#discussion_r510314378)
52-
53- // where we will store the bot .zip
54- const zipDir = join ( process . env . COMPOSER_TEMP_DIR as string , 'pva-publish' ) ;
55- ensureDirSync ( zipDir ) ;
56- const zipPath = join ( zipDir , 'bot.zip' ) ;
57-
58- // write the .zip to disk
59- const zipWriteStream = createWriteStream ( zipPath ) ;
52+ // write the .zip to a buffer in memory
53+ logger . log ( 'Writing bot content to in-memory buffer.' ) ;
54+ const botContentWriter = new stream . Writable ( ) ;
55+ const botContentData = [ ] ;
56+ botContentWriter . _write = ( chunk , encoding , callback ) => {
57+ botContentData . push ( chunk ) ;
58+ callback ( ) ; // let the internal write() call know that the _write() was successful
59+ } ;
6060 await new Promise ( ( resolve , reject ) => {
61- project . exportToZip ( ( archive : NodeJS . ReadStream & { finalize : ( ) => void ; on : ( ev , listener ) => void } ) => {
62- archive . on ( 'error' , ( err ) => {
63- console . error ( 'Got error trying to export to zip: ' , err ) ;
64- reject ( err . message ) ;
65- } ) ;
66- archive . pipe ( zipWriteStream ) ;
67- archive . on ( 'end' , ( ) => {
68- archive . unpipe ( ) ;
69- zipWriteStream . end ( ) ;
70- resolve ( ) ;
71- } ) ;
72- } ) ;
61+ project . exportToZip (
62+ { files : [ '*.botproject' ] , directories : [ '/knowledge-base/' ] } ,
63+ ( archive : NodeJS . ReadStream & { finalize : ( ) => void ; on : ( ev , listener ) => void } ) => {
64+ archive . on ( 'error' , ( err ) => {
65+ console . error ( 'Got error trying to export to zip: ' , err ) ;
66+ reject ( err . message ) ;
67+ } ) ;
68+ archive . on ( 'end' , ( ) => {
69+ archive . unpipe ( ) ;
70+ logger . log ( 'Done reading bot content.' ) ;
71+ resolve ( ) ;
72+ } ) ;
73+ archive . pipe ( botContentWriter ) ;
74+ }
75+ ) ;
7376 } ) ;
74-
75- // open up the .zip for reading
76- const zipReadStream = createReadStream ( zipPath ) ;
77- await new Promise ( ( resolve , reject ) => {
78- zipReadStream . on ( 'error' , ( err ) => {
79- reject ( err ) ;
80- } ) ;
81- zipReadStream . once ( 'readable' , ( ) => {
82- resolve ( ) ;
83- } ) ;
84- } ) ;
85- const length = zipReadStream . readableLength ;
77+ const botContent = Buffer . concat ( botContentData ) ;
78+ logger . log ( 'In-memory buffer created from bot content.' ) ;
8679
8780 // initiate the publish job
8881 let url = `${ base } api/botmanagement/${ API_VERSION } /environments/${ envId } /bots/${ botId } /composer/publishoperations?deleteMissingDependencies=${ deleteMissingDependencies } ` ;
@@ -91,15 +84,16 @@ export const publish = async (
9184 }
9285 const res = await fetch ( url , {
9386 method : 'POST' ,
94- body : zipReadStream ,
87+ body : botContent ,
9588 headers : {
9689 ...getAuthHeaders ( accessToken , tenantId ) ,
9790 'Content-Type' : 'application/zip' ,
98- 'Content-Length' : length . toString ( ) ,
91+ 'Content-Length' : botContent . buffer . byteLength ,
9992 'If-Match' : project . eTag ,
10093 } ,
10194 } ) ;
10295 const job : PVAPublishJob = await res . json ( ) ;
96+ logger . log ( 'Publish job started: %O' , job ) ;
10397
10498 // transform the PVA job to a publish response
10599 const result = xformJobToResult ( job ) ;
@@ -109,7 +103,7 @@ export const publish = async (
109103 ensurePublishProfileHistory ( botProjectId , profileName ) ;
110104 publishHistory [ botProjectId ] [ profileName ] . unshift ( result ) ;
111105
112- remove ( zipDir ) ; // clean up zip -- fire and forget
106+ logger . log ( 'Publish call successful.' ) ;
113107
114108 return {
115109 status : result . status ,
@@ -173,6 +167,9 @@ export const getStatus = async (
173167 logger . log ( 'Got updated status from publish job: %O' , job ) ;
174168
175169 // transform the PVA job to a publish response
170+ if ( ! job . lastUpdateTimeUtc ) {
171+ job . lastUpdateTimeUtc = Date . now ( ) . toString ( ) ; // patch update time if server doesn't send one
172+ }
176173 const result = xformJobToResult ( job ) ;
177174
178175 // update publish history
@@ -291,13 +288,19 @@ const xformJobToResult = (job: PVAPublishJob): PublishResult => {
291288 eTag : job . importedContentEtag ,
292289 id : job . operationId , // what is this used for in Composer?
293290 log : ( job . diagnostics || [ ] ) . map ( ( diag ) => `---\n${ JSON . stringify ( diag , null , 2 ) } \n---\n` ) . join ( '\n' ) ,
294- message : getUserFriendlyMessage ( job . state ) ,
291+ message : getUserFriendlyMessage ( job ) ,
295292 time : new Date ( job . lastUpdateTimeUtc ) ,
296293 status : getStatusFromJobState ( job . state ) ,
294+ action : getAction ( job ) ,
297295 } ;
298296 return result ;
299297} ;
300298
299+ const getAction = ( job ) => {
300+ if ( job . state !== 'Done' || job . testUrl == null || job . testUrl == undefined ) return null ;
301+ return { href : job . testUrl , label : 'Test in Power Virtual Agents' } ;
302+ } ;
303+
301304const getStatusFromJobState = ( state : PublishState ) : number => {
302305 switch ( state ) {
303306 case 'Done' :
@@ -337,8 +340,8 @@ const getOperationIdOfLastJob = (botProjectId: string, profileName: string): str
337340 return '' ;
338341} ;
339342
340- const getUserFriendlyMessage = ( state : PublishState ) : string => {
341- switch ( state ) {
343+ const getUserFriendlyMessage = ( job : PVAPublishJob ) : string => {
344+ switch ( job . state ) {
342345 case 'Done' :
343346 return 'Publish successful.' ;
344347
0 commit comments