import urlJoin = require ( "url-join" ) ; import * as path from "path" ; import * as fs from "./fileSystem" ; import { downloadTarball , extractTarball } from "./tarballUtils" ; import * as semVer from "semver" ; import * as httpUtils from "./httpUtils" ; import { PackageInfo } from "./PackageInfo" ; import Debug from "debug" ; const debug = Debug ( "live-plugin-manager.NpmRegistryClient" ) ; export class NpmRegistryClient { defaultHeaders : httpUtils . Headers ; constructor ( private readonly npmUrl , config ) { const staticHeaders = { "accept-encoding" : "gzip" , "accept" : "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*" , "user-agent" : config . userAgent || "live-plugin-manager" } ; const authHeader = createAuthHeader ( config . auth ) ; this . defaultHeaders = { ... staticHeaders , ... authHeader } ; } async get ( name , versionOrTag = "latest" ) { debug ( `Getting npm info for ${ name }:${ versionOrTag }...` ) ; if ( typeof versionOrTag !== "string" ) { versionOrTag = "" ; } if ( typeof name !== "string" ) { throw new Error ( "Invalid package name" ) ; } const data = await this . getNpmData ( name ) ; versionOrTag = versionOrTag . trim ( ) ; const distTags = data [ "dist-tags" ] ; let version = distTags && distTags [ versionOrTag ] ; if ( ! version ) { version = semVer . clean ( versionOrTag ) || versionOrTag ; } let pInfo = data . versions [ version ] ; if ( ! pInfo ) { for ( const pVersion in data . versions ) { if ( ! data . versions . hasOwnProperty ( pVersion ) ) { continue ; } const pVersionInfo = data . versions [ pVersion ] ; if ( ! semVer . satisfies ( pVersionInfo . version , version ) ) { continue ; } if ( ! pInfo || semVer . gt ( pVersionInfo . version , pInfo . version ) ) { pInfo = pVersionInfo ; } } } if ( ! pInfo ) { throw new Error ( `Version '${ versionOrTag } not found` ) ; } return { dist : pInfo . dist , name : pInfo . name , version : pInfo . version } ; } async download ( destinationDirectory , packageInfo ) { if ( ! packageInfo . dist || ! packageInfo . dist . tarball ) { throw new Error ( "Invalid dist.tarball property" ) ; } const tgzFile = await downloadTarball ( packageInfo . dist . tarball , this . defaultHeaders ) ; const pluginDirectory = path . join ( destinationDirectory , packageInfo . name ) ; try { await extractTarball ( tgzFile , pluginDirectory ) ; } finally { await fs . remove ( tgzFile ) ; } return pluginDirectory ; } private async getNpmData ( name ) { const regUrl = urlJoin ( this . npmUrl , encodeNpmName ( name ) ) ; const headers = this . defaultHeaders ; try { const result = await httpUtils . httpJsonGet < NpmData > ( regUrl , headers ) ; if ( ! result ) { throw new Error ( "Response is empty" ) ; } if ( ! result . versions || ! result . name ) { throw new Error ( "Invalid json format" ) ; } return result ; } catch ( err ) { if ( err . message ) { err . message = `Failed to get package '${ name }' ${ err . message }` ; } throw err ; } } } interface NpmData { name : string ; "dist-tags" ? : { [ tag ] : string ; } ; versions : { [ version ] : { dist : { tarball : string ; } ; name : string ; version : string ; } ; } ; } function encodeNpmName ( name ) { return name . replace ( "/" , "%2F" ) ; } export interface NpmRegistryConfig { auth ? : NpmRegistryAuthToken | NpmRegistryAuthBasic ; userAgent ? : string ; } export interface NpmRegistryAuthToken { token : string ; } export interface NpmRegistryAuthBasic { username : string ; password : string ; } function createAuthHeader ( auth ? ) { if ( ! auth ) { return { } ; } if ( isTokenAuth ( auth ) ) { return httpUtils . headersBearerAuth ( auth . token ) ; } else if ( isBasicAuth ( auth ) ) { return httpUtils . headersBasicAuth ( auth . username , auth . password ) ; } else { return { } ; } } function isTokenAuth ( arg ) { return ( arg as NpmRegistryAuthToken ) . token !== undefined ; } function isBasicAuth ( arg ) { return ( arg as NpmRegistryAuthBasic ) . username !== undefined ; }