11import * as fs from 'fs/promises' ;
2- import * as fs1 from "fs"
2+ import * as fs1 from 'fs' ;
33import * as path from 'path' ;
44import * as tar from 'tar' ;
55import axios from 'axios' ;
6- import * as unzipper from " unzipper" ;
7-
6+ import * as unzipper from ' unzipper' ;
7+ import { Semaphore } from 'async-mutex' ;
88
99export class CxInstaller {
1010 private readonly platform : string ;
1111 private cliVersion : string ;
1212 private readonly resourceDirPath : string ;
13+ private static installSemaphore = new Semaphore ( 1 ) ; // Semaphore with 1 slot
1314
1415 constructor ( platform : string ) {
1516 this . platform = platform ;
@@ -41,7 +42,7 @@ export class CxInstaller {
4142
4243 return `https://download.checkmarx.com/CxOne/CLI/${ cliVersion } /ast-cli_${ cliVersion } _${ platformString } _x64.${ archiveExtension } ` ;
4344 }
44-
45+
4546 getExecutablePath ( ) : string {
4647 let executablePath ;
4748 if ( this . platform === 'win32' ) {
@@ -53,33 +54,39 @@ export class CxInstaller {
5354 }
5455
5556 async downloadIfNotInstalledCLI ( ) {
56- if ( ! this . checkExecutableExists ( ) ) {
57+ // Acquire the semaphore, ensuring only one installation happens at a time
58+ const [ _ , release ] = await CxInstaller . installSemaphore . acquire ( ) ;
59+ try {
60+ if ( this . checkExecutableExists ( ) ) {
61+ console . log ( 'Executable already installed.' ) ;
62+ return ;
63+ }
64+
5765 const url = await this . getDownloadURL ( ) ;
5866 const zipPath = this . getZipPath ( ) ;
59- try {
60- await this . downloadFile ( url , zipPath ) ;
61- console . log ( 'Downloaded CLI to:' , zipPath ) ;
62-
63- await this . extractArchive ( zipPath , this . resourceDirPath ) ;
64- console . log ( 'Extracted CLI to:' , this . resourceDirPath ) ;
65- console . log ( 'Done!' ) ;
66- } catch ( error ) {
67- console . error ( 'Error:' , error ) ;
68- }
67+
68+ await this . downloadFile ( url , zipPath ) ;
69+ console . log ( 'Downloaded CLI to:' , zipPath ) ;
70+
71+ await this . extractArchive ( zipPath , this . resourceDirPath ) ;
72+ console . log ( 'Extracted CLI to:' , this . resourceDirPath ) ;
73+ } catch ( error ) {
74+ console . error ( 'Error during installation:' , error ) ;
75+ } finally {
76+ // Release the semaphore lock to allow the next waiting process to continue
77+ release ( ) ; // Call the release function
6978 }
7079 }
7180
7281 async extractArchive ( zipPath : string , extractPath : string ) : Promise < void > {
7382 if ( zipPath . endsWith ( '.zip' ) ) {
7483 console . log ( 'Extracting ZIP file...' ) ;
75- // Use unzipper to extract ZIP files
7684 await unzipper . Open . file ( zipPath )
77- . then ( d => d . extract ( { path : extractPath } ) ) ;
85+ . then ( d => d . extract ( { path : extractPath } ) ) ;
7886 console . log ( 'Extracted ZIP file to:' , extractPath ) ;
7987 } else if ( zipPath . endsWith ( '.tar.gz' ) ) {
8088 console . log ( 'Extracting TAR.GZ file...' ) ;
81- // Use tar.extract to extract TAR.GZ files
82- await tar . extract ( { file : zipPath , cwd : extractPath } ) ;
89+ await tar . extract ( { file : zipPath , cwd : extractPath } ) ;
8390 console . log ( 'Extracted TAR.GZ file to:' , extractPath ) ;
8491 } else {
8592 console . error ( 'Unsupported file type. Only .zip and .tar.gz are supported.' ) ;
@@ -89,52 +96,35 @@ export class CxInstaller {
8996 async downloadFile ( url : string , outputPath : string ) {
9097 console . log ( 'Downloading file from:' , url ) ;
9198 const writer = fs1 . createWriteStream ( outputPath ) ;
92- console . log ( 'Downloading file to:' , outputPath ) ;
93- const response = await axios ( { url, responseType : 'stream' } ) ;
94- console . log ( 'Downloading file...' ) ;
99+ const response = await axios ( { url, responseType : 'stream' } ) ;
95100 response . data . pipe ( writer ) ;
96- console . log ( 'Downloaded file' ) ;
101+
97102 return new Promise ( ( resolve , reject ) => {
98103 writer . on ( 'finish' , resolve ) ;
99104 writer . on ( 'error' , reject ) ;
100105 } ) ;
101106 }
102107
103108 getZipPath ( ) : string {
104- let executablePath ;
105- if ( this . platform === 'win32' ) {
106- executablePath = path . join ( this . resourceDirPath , 'cx.zip' ) ;
107- } else {
108- executablePath = path . join ( this . resourceDirPath , 'cx.tar.gz' ) ;
109- }
110- console . log ( 'Zip path:' , executablePath )
111- return executablePath ;
109+ return this . platform === 'win32'
110+ ? path . join ( this . resourceDirPath , 'cx.zip' )
111+ : path . join ( this . resourceDirPath , 'cx.tar.gz' ) ;
112112 }
113113
114114 checkExecutableExists ( ) : boolean {
115- if ( fs1 . existsSync ( this . getExecutablePath ( ) ) ) {
116- console . log ( 'Executable exists:' , this . getExecutablePath ( ) ) ;
117- return true ;
118- } else {
119- return false ;
120- }
115+ return fs1 . existsSync ( this . getExecutablePath ( ) ) ;
121116 }
122117
123- // Method to read the AST CLI version from the file
124118 async readASTCLIVersion ( ) : Promise < string > {
125119 if ( this . cliVersion ) {
126120 return this . cliVersion ;
127121 }
128122 try {
129- console . log ( 'Reading AST CLI version...' ) ;
130123 const versionFilePath = path . join ( process . cwd ( ) , 'checkmarx-ast-cli.version' ) ;
131124 const versionContent = await fs . readFile ( versionFilePath , 'utf-8' ) ;
132- console . log ( 'AST CLI version:' , versionContent . trim ( ) ) ;
133125 return versionContent . trim ( ) ;
134126 } catch ( error ) {
135- console . error ( 'Error reading AST CLI version:' , error ) ;
136- throw error ;
127+ throw new Error ( 'Error reading AST CLI version: ' + error . message ) ;
137128 }
138129 }
139130}
140-
0 commit comments