import path from "path" ; import { PluginLogger , WorkingDirectoryInfo } from "reg-suit-interface" ; import { lookup } from "mime-types" ; import glob from "glob" ; import _ from "lodash" ; export type FileItem = { path : string ; absPath : string ; mimeType : string ; } ; export type RemoteFileItem = { key : string ; remotePath : string ; } ; export type ObjectMetadata = { key : string ; } ; export type ObjectListResult = { isTruncated : boolean ; contents : ObjectMetadata [ ] ; nextMarker ? : string ; } ; const CONCURRENCY_SIZE = 50 ; const DEFAULT_PATTERN = "**/*.{html,js,wasm,png,json,jpeg,jpg,tiff,bmp,gif}" ; export abstract class AbstractPublisher { protected noEmit = false ; protected logger ! : PluginLogger ; protected abstract uploadItem ( key , item ) ; protected abstract downloadItem ( remoteItem , item ) ; protected abstract listItems ( lastKey , prefix ) ; protected abstract getWorkingDirs ( ) ; protected abstract getLocalGlobPattern ( ) ; protected abstract getBucketName ( ) ; protected abstract getBucketRootDir ( ) ; protected createList ( ) { return new Promise < string [ ] > ( ( resolve , reject ) => { glob ( this . getLocalGlobPattern ( ) || DEFAULT_PATTERN , { cwd : this . getWorkingDirs ( ) . base , nodir : true , } , ( err , list ) => { if ( err ) { return reject ( err ) ; } resolve ( list ) ; } ) ; } ) . then ( files => { return files . map ( f => { const mimeType = lookup ( f ) || "unknown" ; return { path : f , absPath : path . resolve ( this . getWorkingDirs ( ) . base , f ) , mimeType , } ; } ) . filter ( item => ! ! item . mimeType ) ; } ) ; } protected resolveInBucket ( key ) { if ( this . getBucketRootDir ( ) ) { return this . getBucketRootDir ( ) + "/" + key ; } else { return key ; } } protected fetchInternal ( key ) { if ( this . noEmit ) return Promise . resolve ( ) ; const actualPrefix = `${ this . resolveInBucket ( key ) }/${ path . basename ( this . getWorkingDirs ( ) . actualDir ) }` ; const progress = this . logger . getProgressBar ( ) ; return new Promise < ObjectMetadata [ ] > ( async ( resolve , reject ) => { const contents = [ ] as ObjectMetadata [ ] ; let nextMarker = "" ; do { let result ; try { result = await this . listItems ( nextMarker , actualPrefix ) ; const curContents = result . contents || [ ] ; if ( curContents . length > 0 ) { Array . prototype . push . apply ( contents , curContents ) ; } nextMarker = result . nextMarker ; } catch ( e ) { nextMarker = "" ; reject ( e ) ; } } while ( nextMarker ) ; resolve ( contents ) ; } ) . then ( contents => { if ( contents . length ) { progress . start ( contents . length , 0 ) ; this . logger . info ( `Download ${ contents . length } files from ${ this . logger . colors . magenta ( this . getBucketName ( ) ) }.` ) ; } return contents . map ( c => { const suffix = c . key ? c . key . replace ( new RegExp ( `^${ actualPrefix }\/` ) , "" ) : "" ; return { path : suffix , absPath : path . join ( this . getWorkingDirs ( ) . expectedDir , suffix ) , mimeType : lookup ( suffix ) , } as FileItem ; } ) ; } ) . then ( items => _ . chunk ( items , CONCURRENCY_SIZE ) ) . then ( chunks => { return chunks . reduce ( ( acc , chunk ) => { return acc . then ( list => { return Promise . all ( chunk . map ( item => { const remotePath = `${ this . resolveInBucket ( key ) }/${ path . basename ( this . getWorkingDirs ( ) . actualDir ) }/${ item . path }` ; return this . downloadItem ( { remotePath , key } , item ) . then ( fi => { progress . increment ( 1 ) ; return fi ; } ) ; } ) ) . then ( items => [ ... list , ... items ] ) ; } ) ; } , Promise . resolve ( [ ] as FileItem [ ] ) ) ; } ) . then ( result => { progress . stop ( ) ; return result ; } ) ; } protected publishInternal ( key ) { const progress = this . logger . getProgressBar ( ) ; return this . createList ( ) . then ( list => { if ( list . length ) { progress . start ( list . length , 0 ) ; if ( ! this . noEmit ) { this . logger . info ( `Upload ${ list . length } files to ${ this . logger . colors . magenta ( this . getBucketName ( ) ) }.` ) ; } else { this . logger . info ( `There are ${ list . length } files to publish` ) ; } } return _ . chunk ( list , CONCURRENCY_SIZE ) ; } ) . then ( chunks => { return chunks . reduce ( ( acc , chunk ) => { return acc . then ( list => { return Promise . all ( chunk . map ( item => { if ( this . noEmit ) return Promise . resolve ( item ) ; return this . uploadItem ( this . resolveInBucket ( key ) , item ) . then ( fi => { progress . increment ( 1 ) ; return fi ; } ) ; } ) ) . then ( items => [ ... list , ... items ] ) ; } ) ; } , Promise . resolve ( [ ] as FileItem [ ] ) ) ; } ) . then ( items => { const indexFile = items . find ( item => item . path . endsWith ( "index.html" ) ) ; return { items , indexFile } ; } ) . then ( result => { progress . stop ( ) ; return result ; } ) ; } }