1
1
import * as fs from 'fs/promises' ;
2
- import * as fs1 from "fs"
2
+ import * as fs1 from 'fs' ;
3
3
import * as path from 'path' ;
4
4
import * as tar from 'tar' ;
5
5
import axios from 'axios' ;
6
- import * as unzipper from " unzipper" ;
7
-
6
+ import * as unzipper from ' unzipper' ;
7
+ import { Semaphore } from 'async-mutex' ;
8
8
9
9
export class CxInstaller {
10
10
private readonly platform : string ;
11
11
private cliVersion : string ;
12
12
private readonly resourceDirPath : string ;
13
+ private static installSemaphore = new Semaphore ( 1 ) ; // Semaphore with 1 slot
13
14
14
15
constructor ( platform : string ) {
15
16
this . platform = platform ;
@@ -41,7 +42,7 @@ export class CxInstaller {
41
42
42
43
return `https://download.checkmarx.com/CxOne/CLI/${ cliVersion } /ast-cli_${ cliVersion } _${ platformString } _x64.${ archiveExtension } ` ;
43
44
}
44
-
45
+
45
46
getExecutablePath ( ) : string {
46
47
let executablePath ;
47
48
if ( this . platform === 'win32' ) {
@@ -53,33 +54,39 @@ export class CxInstaller {
53
54
}
54
55
55
56
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
+
57
65
const url = await this . getDownloadURL ( ) ;
58
66
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
69
78
}
70
79
}
71
80
72
81
async extractArchive ( zipPath : string , extractPath : string ) : Promise < void > {
73
82
if ( zipPath . endsWith ( '.zip' ) ) {
74
83
console . log ( 'Extracting ZIP file...' ) ;
75
- // Use unzipper to extract ZIP files
76
84
await unzipper . Open . file ( zipPath )
77
- . then ( d => d . extract ( { path : extractPath } ) ) ;
85
+ . then ( d => d . extract ( { path : extractPath } ) ) ;
78
86
console . log ( 'Extracted ZIP file to:' , extractPath ) ;
79
87
} else if ( zipPath . endsWith ( '.tar.gz' ) ) {
80
88
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 } ) ;
83
90
console . log ( 'Extracted TAR.GZ file to:' , extractPath ) ;
84
91
} else {
85
92
console . error ( 'Unsupported file type. Only .zip and .tar.gz are supported.' ) ;
@@ -89,52 +96,35 @@ export class CxInstaller {
89
96
async downloadFile ( url : string , outputPath : string ) {
90
97
console . log ( 'Downloading file from:' , url ) ;
91
98
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' } ) ;
95
100
response . data . pipe ( writer ) ;
96
- console . log ( 'Downloaded file' ) ;
101
+
97
102
return new Promise ( ( resolve , reject ) => {
98
103
writer . on ( 'finish' , resolve ) ;
99
104
writer . on ( 'error' , reject ) ;
100
105
} ) ;
101
106
}
102
107
103
108
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' ) ;
112
112
}
113
113
114
114
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 ( ) ) ;
121
116
}
122
117
123
- // Method to read the AST CLI version from the file
124
118
async readASTCLIVersion ( ) : Promise < string > {
125
119
if ( this . cliVersion ) {
126
120
return this . cliVersion ;
127
121
}
128
122
try {
129
- console . log ( 'Reading AST CLI version...' ) ;
130
123
const versionFilePath = path . join ( process . cwd ( ) , 'checkmarx-ast-cli.version' ) ;
131
124
const versionContent = await fs . readFile ( versionFilePath , 'utf-8' ) ;
132
- console . log ( 'AST CLI version:' , versionContent . trim ( ) ) ;
133
125
return versionContent . trim ( ) ;
134
126
} 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 ) ;
137
128
}
138
129
}
139
130
}
140
-
0 commit comments