import _fetch from "electron-fetch" ; import byline from "byline" ; import fs from "fs" ; import fsExtra from "fs-extra" ; import os from "os" ; import path from "path" ; import spawnAsync from "@expo/spawn-async" ; import { URL } from "@cliqz/url-parser" ; import { createHash } from "crypto" ; import { omit , pick } from "remeda" ; import { promisify } from "util" ; import { GIT_CLONE_ABSOLUTE_DIR , OUTPUT_ABSOLUTE_DIR } from "./const" ; import { PROVIDER_REPO_MAP } from "src/shared/proton-apps-constants" ; const fetch = typeof _fetch === "function" ? _fetch : ( _fetch as any ) . default as typeof _fetch ; if ( typeof fetch !== "function" ) { throw new Error ( `Unexpected "${ nameof ( fetch ) }" type: ${ typeof fetch }` ) ; } export const makeConsoleTextYellow = ( value ) : string => { return "\x1b[0m" + "\x1b[33m" + value + "\x1b[0m" ; } ; export const CONSOLE_LOG = ( ... args ) => { return console . log ( makeConsoleTextYellow ( ">>>" ) , ... args ) ; } ; export function resolveGitOutputBackupDir ( { repoType , tag = PROVIDER_REPO_MAP [ repoType ] . tag , suffix , } ) { return path . join ( GIT_CLONE_ABSOLUTE_DIR , "./backup" , repoType , `./${ tag }${ suffix ? ( "-" + suffix ) : "" }` ) ; } export function formatStreamChunk ( chunk ) { return Buffer . from ( chunk , "utf-8" ) . toString ( ) ; } export async function execShell ( [ command , args , options ] , { printStdOut = true , printStdErr = true , printEnvWhitelist = [ ] , doNotRethrow = false , doNotDropNodeOptionsRelatedEnvVars = false , } = { } ) { { const stringifiedOptions = JSON . stringify ( { ... ( options && omit ( options , [ "env" ] ) ) , ... { whitelistedEnv : options ?. env && printEnvWhitelist ?. length ? pick ( options . env , [ ... printEnvWhitelist ] ) : undefined , } , } ) ; const optionsPart = stringifiedOptions !== "{}" ? ` (options: ${ stringifiedOptions })` : "" ; CONSOLE_LOG ( `Executing Shell command${ optionsPart }: ${ makeConsoleTextYellow ( [ command , ... ( args ?? [ ] ) ] . join ( " " ) ) }` ) ; } const spawnPromise = spawnAsync ( command , args , { ... options , env : { ... process . env , ... options ?. env , ... ( ! doNotDropNodeOptionsRelatedEnvVars && { NODE_OPTIONS : "" , npm_config_node_options : "" , } ) , } , } ) ; const { stdout , stderr } = spawnPromise . child ; const print = ( std ) : void => { byline ( std ) . on ( "data" , ( chunk ) => { console . log ( formatStreamChunk ( chunk ) ) ; } ) ; } ; if ( printStdOut && stdout ) { print ( stdout ) ; } if ( printStdErr && stderr ) { print ( stderr ) ; } try { return await spawnPromise ; } catch ( _ ) { const error = _ as Unpacked < ReturnType < typeof spawnAsync > > ; if ( doNotRethrow ) { return error ; } ( ( ) => { const omitProps = [ "output" , "stderr" , "stdout" ] ; omitProps . forEach ( ( omitProp ) => { if ( omitProp in error ) { delete error [ omitProp ] ; } } ) ; } ) ( ) ; throw error ; } } export async function fetchUrl ( ... [ url , options ] ) { CONSOLE_LOG ( `Downloading ${ String ( url ) }` ) ; const response = await fetch ( url , options ) ; if ( ! response . ok ) { throw new Error ( `Downloading failed: ${ JSON . stringify ( pick ( response , [ "status" , "statusText" ] ) ) }` ) ; } return response ; } export async function resolveGitCommitInfo ( { dir } ) { return ( await Promise . all ( ( [ { prop : "shortCommit" , gitArgs : [ "rev-parse" , "--short" , "HEAD" ] } , { prop : "commit" , gitArgs : [ "rev-parse" , "HEAD" ] } , ] as const ) . map ( async ( { gitArgs , prop } ) => execShell ( [ "git" , gitArgs , { cwd : dir } ] ) . then ( ( { stdout } ) => ( { value : stdout . replace ( /(\r\n|\n|\r)/gm , "" ) , gitArgs , prop } ) ) ) ) ) . reduce ( ( accumulator , { value , gitArgs , prop } ) => { if ( ! value ) { throw new Error ( `"${ JSON . stringify ( gitArgs ) }" git command returned empty value: ${ value }` ) ; } return { ... accumulator , [ prop ] : value } ; } , { shortCommit : "" , commit : "" } ) ; } export const calculateHash = async ( filePath , algorithm = "sha256" ) : Promise < { hash : string ; type : typeof algorithm ; } > => { return new Promise ( ( resolve , reject ) => { const hash = createHash ( algorithm ) ; fs . createReadStream ( filePath ) . on ( "data" , ( data ) => hash . update ( data ) ) . on ( "end" , ( ) => resolve ( { hash : hash . digest ( "hex" ) , type : algorithm } ) ) . on ( "error" , reject ) ; } ) ; } ; export const resolveExecutable = async ( url , sha256 , subdirectory ) : Promise < { command : string ; } > => { const destFile = path . join ( OUTPUT_ABSOLUTE_DIR , "./executables" , subdirectory , ( ( ) => { const fileName = new URL ( url ) . pathname . split ( "/" ) . pop ( ) ; if ( ! fileName ) { throw new Error ( `Failed to parse executable file name from the "${ url }"` ) ; } return fileName ; } ) ( ) ) ; const calculateHashes = async ( ) : Promise < { actual : string ; expected : string ; equal : boolean ; } > => { const actual = ( await calculateHash ( destFile ) ) . hash ; const expected = sha256 ; const result = { actual , expected , equal : actual === expected } as const ; CONSOLE_LOG ( `Calculated "${ destFile }" checksum: ${ JSON . stringify ( result ) }` ) ; return result ; } ; if ( ! await fsExtra . pathExists ( destFile ) || ! ( await calculateHashes ( ) ) . equal ) { const response = await fetchUrl ( url ) ; fsExtra . ensureDirSync ( path . dirname ( destFile ) ) ; await promisify ( fs . writeFile ) ( destFile , await response . buffer ( ) ) ; } { const hashes = await calculateHashes ( ) ; if ( ! hashes . equal ) { throw new Error ( `Hashes verification failed: ${ JSON . stringify ( hashes ) }` ) ; } } if ( os . platform ( ) !== "win32" ) { await execShell ( [ "chmod" , [ "+x" , destFile ] ] ) ; } return { command : destFile } ; } ; export const catchTopLeventAsync = ( asyncFn ) : void => { ( async ( ) => { await asyncFn ( ) ; } ) ( ) . catch ( ( error ) => { CONSOLE_LOG ( error ) ; process . exit ( 1 ) ; } ) ; } ; export const applyPatch = async ( { patchFile , cwd } ) : Promise < void > => { await execShell ( [ "git" , [ "apply" , "--ignore-whitespace" , "--reject" , patchFile , ] , { cwd } , ] ) ; } ;