'use strict' ; import * as fs from 'graceful-fs' ; import * as octokit from '@octokit/rest' ; import * as path from 'path' ; import mv = require ( 'mv' ) ; import request = require ( 'request' ) ; import sha256File = require ( 'sha256-file' ) ; import tmp = require ( 'tmp' ) ; const { pipeline } = require ( 'stream' ) ; import { ReposListReleasesResponseData } from '@octokit/types' ; tmp . setGracefulCleanup ( ) ; const USER_AGENT = 'bazel-stack-vscode' ; /**
 * Configuration type that describes a desired asset from a gh release.
 */ export type GithubReleaseAssetRequest = { /**
     * The gh owner (e.g. "bazelbuild").
     */ owner : string ; /**
     * The gh reposito       ry name (e.g. "buildtools").
     */ repo : string ; /**
     * The tag name of the release (e.g. "3.3.0").
     */ release : string ; /**
     * The name of the asset to download (e.g. "buildifier.exe").
     */ name : string ; } ; export type GithubReleaseAsset = { url : string ; browser_download_url : string ; id : number ; node_id : string ; name : string ; label : string ; state : string ; content_type : string ; size : number ; download_count : number ; created_at : string ; updated_at : string ; uploader : { login : string ; id : number ; node_id : string ; avatar_url : string ; gravatar_id : string ; url : string ; html_url : string ; followers_url : string ; following_url : string ; gists_url : string ; starred_url : string ; subscriptions_url : string ; organizations_url : string ; repos_url : string ; events_url : string ; received_events_url : string ; type : string ; site_admin : boolean ; } ; } ; export type GithubRelease = { url : string ; html_url : string ; assets_url : string ; upload_url : string ; tarball_url : string ; zipball_url : string ; id : number ; node_id : string ; tag_name : string ; target_commitish : string ; name : string ; body : string ; draft : boolean ; prerelease : boolean ; created_at : string ; published_at : string ; author : { login : string ; id : number ; node_id : string ; avatar_url : string ; gravatar_id : string ; url : string ; html_url : string ; followers_url : string ; following_url : string ; gists_url : string ; starred_url : string ; subscriptions_url : string ; organizations_url : string ; repos_url : string ; events_url : string ; received_events_url : string ; type : string ; site_admin : boolean ; } ; assets : GithubReleaseAsset [ ] ; } ; export class GitHubReleaseAssetDownloader { constructor ( private req , private outputDir , private executable ) { } param outputDir getFilepath ( ) { return path . join ( this . outputDir , this . req . release , this . req . name ) ; } newOctokit ( ) { const args = { userAgent : USER_AGENT , } ; const token = getGithubToken ( ) ; if ( token ) { args [ 'auth' ] = token ; } return new octokit . Octokit ( args ) ; } /**
     * Perform the download.
     */ async download ( ) { const filepath = this . getFilepath ( ) ; const mode = this . executable ? 0o755 : 0o644 ; const client = this . newOctokit ( ) ; const asset = await getReleaseAsset ( client , this . req ) ; await downloadAsset ( asset . url , filepath , mode ) ; if ( ! fs . existsSync ( filepath ) ) { throw new Error ( `Downloader should have created file <${ filepath }>.  ` + 'Please check that release ' + `https://github.com/${ this . req . owner }/${ this . req . repo }/releases/${ this . req . release } ` + `has an asset named "${ this . req . name }".  ` + 'If the release does not exist, check your extension settings.  ' + 'If the release exists and asset exists this is likely a bug.  ' + 'Please file an issue at https://github.com/stackb/bazel-stack-vscode/issues' ) ; } return asset ; } } export async function getReleaseAsset ( client , req ) { const releases = await listReleases ( client , req . owner , req . repo ) ; if ( ! releases . length ) { throw new Error ( `No releases found for github.com/${ req . owner }/${ req . name }` ) ; } const release = findRelease ( releases , req . release ) ; if ( ! release ) { throw new Error ( `github.com/${ req . owner }/${ req . repo }/releases/${ req . release } does not exist` ) ; } const assets = await listReleaseAssets ( client , req . owner , req . repo , release . id ) ; if ( ! assets . length ) { throw new Error ( `No assets found for github.com/${ req . owner }/${ req . name }/releases/${ req . release }` ) ; } const asset = findAsset ( assets , req . name ) ; if ( ! asset ) { throw new Error ( `No asset named "${ req . name }" in github.com/${ req . owner }/${ req . name }/releases/${ req . release }` ) ; } return asset ; } export function listReleases ( client , owner , repo ) { return client . repos . listReleases ( { owner : owner , repo : repo , } ) . then ( response => { return response . data ; } ) ; } export function listReleaseAssets ( client , owner , repo , release_id ) { return client . repos . listReleaseAssets ( { owner : owner , repo : repo , release_id : release_id , } ) . then ( resp => { return resp . data ; } ) ; } export function findRelease ( releases , tagName ) { return releases . find ( release => release . tag_name === tagName ) as unknown as GithubRelease | undefined ; } export function findAsset ( assets , assetName ) { return assets . find ( a => a . name === assetName ) as unknown as GithubReleaseAsset ; } param { string } url param { string } filename param { number } mode param { string } mode returns { Promise < void > } export async function downloadAsset ( url , filename , mode , sha256 ? ) { if ( fs . existsSync ( filename ) && sha256 ) { if ( sha256Matches ( filename , sha256 ) ) { return Promise . reject ( `${ filename } already exists and matches requested sha256 ${ sha256 }` ) ; } } const tmpFile = tmp . fileSync ( ) ; return new Promise < void > ( ( resolve , reject ) => { const headers = { Accept : 'application/octet-stream' , 'User-Agent' : 'bazel-stack-vscode' , } ; const token = getGithubToken ( ) ; if ( token ) { headers [ 'Authorization' ] = 'token ' + token ; } const src = request ( { url : url , method : 'GET' , headers : headers , } ) ; const dst = fs . createWriteStream ( tmpFile . name , { mode : mode , } ) ; pipeline ( src , dst , ( err ) => { if ( err ) { reject ( err ) ; return ; } if ( sha256 ) { const actual = sha256File ( tmpFile . name ) ; if ( actual !== sha256 ) { reject ( `${ tmpFile . name } did not match requested sha256: want ${ sha256 }, got ${ actual }` ) ; return ; } } resolve ( ) ; } ) ; } ) . then ( ( ) => { fs . chmodSync ( tmpFile . name , mode ) ; return new Promise ( ( resolve , reject ) => { mv ( tmpFile . name , filename , { mkdirp : true } , err => { if ( err ) { reject ( err ) ; return ; } console . log ( `Renamed ${ tmpFile . name } -> ${ filename }` ) ; tmpFile . removeCallback ( ) ; setTimeout ( ( ) => resolve ( ) , 250 ) ; } ) ; } ) ; } ) ; } function getGithubToken ( ) { return process . env [ 'GITHUB_TOKEN' ] ; } function sha256Matches ( filename , sha256 ) { return sha256File ( filename ) === sha256 ; } export function processPlatformBinaryName ( toolName ) { return platformBinaryName ( process . platform , toolName ) ; } export function platformBinaryName ( platform , toolName ) { if ( platform === 'win32' ) { return toolName + '.exe' ; } if ( platform === 'darwin' ) { return toolName + '.mac' ; } return toolName ; } export function platformOsArchBinaryName ( arch , platform , toolName ) { let osName = 'linux' ; let osArch = 'amd64' ; let ext = '' ; if ( platform === 'win32' ) { osName = 'windows' ; ext = '.exe' ; } else if ( platform === 'darwin' ) { osName = 'darwin' ; } else { switch ( arch ) { case 'arm' : case 'arm64' : osArch = 'arm64' ; break ; } } return `${ toolName }-${ osName }-${ osArch }${ ext }` ; }