import archiver from 'archiver' ; import { IncomingMessage } from 'http' ; import { Readable , Writable } from 'stream' ; import { extract } from 'tar-fs' ; import { createGunzip , ZlibOptions } from 'zlib' ; import { DEFAULT_WORKSPACE } from '../../constants' ; import { CacheType , inflightURL , inflightUrlWithQuery , InstanceOptions , RequestTracingConfig , } from '../../HttpClient' ; import { IgnoreNotFoundRequestConfig , } from '../../HttpClient/middlewares/notFound' ; import { AppBundlePublished , AppFilesList , AppManifest , AppState } from '../../responses' ; import { IOContext } from '../../service/worker/runtime/typings' ; import { InfraClient } from './InfraClient' ; const EMPTY_OBJECT = { } ; const routes = { App : ( app ) => `${ routes . Registry }/${ app }` , AppBundle : ( app , version , path ? ) => { return path ? `${ routes . AppVersion ( app , version ) }/bundle/${ path }` : `${ routes . AppVersion ( app , version ) }/bundle` ; } , AppFile : ( app , version , path ) => `${ routes . AppFiles ( app , version ) }/${ path }` , AppFiles : ( app , version ) => `${ routes . AppVersion ( app , version ) }/files` , AppState : ( app , version ) => `${ routes . AppVersion ( app , version ) }/state` , AppVersion : ( app , version ) => `${ routes . App ( app ) }/${ version }` , Publish : '/v2/registry' , PublishRc : '/v2/registry/rc' , Registry : '/registry' , ResolveDependenciesWithManifest : '/v2/registry/_resolve' , } ; export class Registry extends InfraClient { constructor ( context , options ? ) { super ( 'apps@0.x' , { ... context , workspace : DEFAULT_WORKSPACE } , options ) ; } public getAppState = ( app , version , tracingConfig ? ) => { const metric = 'registry-app-state' ; const inflightKey = inflightUrlWithQuery ; return this . http . get < AppState > ( routes . AppState ( app , version ) , { inflightKey , metric , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } , } ) ; } ; public publishApp = ( files , tag ? , { zlib } = { } , tracingConfig ? ) => { return this . publish ( routes . Publish , files , tag , { zlib } , tracingConfig ) ; } ; public publishAppRc = ( files , tag ? , { zlib } = { } , tracingConfig ? ) => { return this . publish ( routes . PublishRc , files , tag , { zlib } , tracingConfig ) ; } ; public listApps = ( tracingConfig ? ) => { const inflightKey = inflightURL ; const metric = 'registry-list' ; return this . http . get < RegistryAppsList > ( routes . Registry , { inflightKey , metric , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } } ) ; } ; public listVersionsByApp = ( app , tracingConfig ? ) => { const inflightKey = inflightURL ; const metric = 'registry-list-versions' ; return this . http . get < RegistryAppVersionsList > ( routes . App ( app ) , { inflightKey , metric , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } } ) ; } ; public deprecateApp = ( app , version , tracingConfig ? ) => { const metric = 'registry-deprecate' ; return this . http . patch ( routes . AppVersion ( app , version ) , { patchState : 'deprecate' } , { metric , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } } ) ; } ; public undeprecateApp = ( app , version , tracingConfig ? ) => { const metric = 'registry-undeprecate' ; return this . http . patch ( routes . AppVersion ( app , version ) , { patchState : 'undeprecate' } , { metric , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } } ) ; } ; public validateApp = ( app , version , tracingConfig ? ) => { const metric = 'registry-validate' ; return this . http . patch ( routes . AppVersion ( app , version ) , { patchState : 'validate' } , { metric , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } } ) ; } ; public getAppManifest = ( app , version , opts ? , tracingConfig ? ) => { const inflightKey = inflightUrlWithQuery ; const params = opts ; const metric = 'registry-manifest' ; return this . http . get < AppManifest > ( routes . AppVersion ( app , version ) , { inflightKey , metric , params , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } } ) ; } ; public listAppFiles = ( app , version , opts ? , tracingConfig ? ) => { const inflightKey = inflightUrlWithQuery ; const params = opts ; const metric = 'registry-list-files' ; return this . http . get < AppFilesList > ( routes . AppFiles ( app , version ) , { inflightKey , metric , params , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } } ) ; } ; public getAppFile = ( app , version , path , tracingConfig ? ) => { const inflightKey = inflightURL ; const metric = 'registry-get-file' ; return this . http . getBuffer ( routes . AppFile ( app , version , path ) , { cacheable : CacheType . Disk , inflightKey , metric , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } , } ) ; } ; public getAppJSON = < T extends object | null > ( app , version , path , nullIfNotFound ? , tracingConfig ? ) => { const inflightKey = inflightURL ; const metric = 'registry-get-json' ; return this . http . get < T > ( routes . AppFile ( app , version , path ) , { cacheable : CacheType . Any , inflightKey , metric , nullIfNotFound , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } , } as IgnoreNotFoundRequestConfig ) ; } ; public getAppFileStream = ( app , version , path , tracingConfig ? ) : Promise < IncomingMessage > => { const metric = 'registry-get-file-s' ; return this . http . getStream ( routes . AppFile ( app , version , path ) , { metric , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } , } ) ; } ; public getAppBundle = ( app , version , bundlePath , generatePackageJson , tracingConfig ? ) : Promise < Readable > => { const params = generatePackageJson && { _packageJSONEngine : 'npm' , _packageJSONFilter : 'vtex.render-builder@x' } ; const metric = 'registry-get-bundle' ; return this . http . getStream ( routes . AppBundle ( app , version , bundlePath ) , { headers : { Accept : 'application/x-gzip' , 'Accept-Encoding' : 'gzip' , } , metric , params , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } , } ) ; } ; public unpackAppBundle = ( app , version , bundlePath , unpackPath , generatePackageJson , tracingConfig ? ) : Promise < Writable > => { return this . getAppBundle ( app , version , bundlePath , generatePackageJson , tracingConfig ) . then ( stream => stream . pipe ( createGunzip ( ) ) . pipe ( extract ( unpackPath ) ) ) ; } ; public resolveDependenciesWithManifest = ( manifest , filter = '' , tracingConfig ? ) => { const params = { filter } ; const metric = 'registry-resolve-deps' ; return this . http . post < Record < string , string [ ] > > ( routes . ResolveDependenciesWithManifest , manifest , { metric , params , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } } ) ; } ; private publish = async ( route , files , tag ? , { zlib } = { } , tracingConfig ? ) => { if ( ! ( files [ 0 ] && files [ 0 ] . path && files [ 0 ] . content ) ) { throw new Error ( 'Argument files must be an array of {path, content}, where content can be a String, a Buffer or a ReadableStream.' ) ; } const indexOfManifest = files . findIndex ( ( { path } ) => path === 'manifest.json' ) ; if ( indexOfManifest === - 1 ) { throw new Error ( 'No manifest.json file found in files.' ) ; } const zip = archiver ( 'zip' , { zlib } ) ; zip . on ( 'error' , ( e ) => { throw e ; } ) ; const metric = 'registry-publish' ; const request = this . http . post < AppBundlePublished > ( route , zip , { headers : { 'Content-Type' : 'application/zip' } , metric , params : tag ? { tag } : EMPTY_OBJECT , tracing : { requestSpanNameSuffix : metric , ... tracingConfig ?. tracing , } , } ) ; files . forEach ( ( { content , path } ) => zip . append ( content , { name : path } ) ) ; const finalize = zip . finalize ( ) ; try { const [ response ] = await Promise . all ( [ request , finalize ] ) ; response . bundleSize = zip . pointer ( ) ; return response ; } catch ( e ) { e . bundleSize = zip . pointer ( ) ; throw e ; } } ; } interface ZipOptions { zlib ? : ZlibOptions ; } export interface AppsManifestOptions { resolveDeps : boolean ; } export interface ListAppFilesOptions { prefix : string ; } export interface RegistryAppsListItem { partialIdentifier : string ; location : string ; } export interface RegistryAppsList { data : RegistryAppsListItem [ ] ; } export interface RegistryAppVersionsListItem { versionIdentifier : string ; location : string ; } export interface RegistryAppVersionsList { data : RegistryAppVersionsListItem [ ] ; } export interface File { path : string ; content : any ; }