@@ -6,7 +6,12 @@ import chalk from 'chalk';
66import { StatusCodes } from 'http-status-codes' ;
77import { ListrTaskWrapper } from 'listr2' ;
88
9- import { getAppVersionDeploymentStatusUrl , getDeploymentClientUpload , getDeploymentSignedUrl } from 'consts/urls' ;
9+ import {
10+ getAppVersionDeploymentStatusUrl ,
11+ getDeploymentClientUpload ,
12+ getDeploymentSecurityScanUrl ,
13+ getDeploymentSignedUrl ,
14+ } from 'consts/urls' ;
1015import { execute } from 'services/api-service' ;
1116import { getCurrentWorkingDirectory } from 'services/env-service' ;
1217import {
@@ -17,7 +22,11 @@ import {
1722 verifyClientDirectory ,
1823} from 'services/files-service' ;
1924import { pollPromise } from 'services/polling-service' ;
20- import { appVersionDeploymentStatusSchema , signedUrlSchema } from 'services/schemas/push-service-schemas' ;
25+ import {
26+ appVersionDeploymentStatusSchema ,
27+ securityScanResponseSchema ,
28+ signedUrlSchema ,
29+ } from 'services/schemas/push-service-schemas' ;
2130import { PushCommandTasksContext } from 'types/commands/push' ;
2231import { HttpError } from 'types/errors' ;
2332import { Region } from 'types/general/region' ;
@@ -26,6 +35,7 @@ import { HttpMethodTypes } from 'types/services/api-service';
2635import {
2736 AppVersionDeploymentStatus ,
2837 DeploymentStatusTypesSchema ,
38+ SecurityScanResponse ,
2939 SignedUrl ,
3040 uploadClient ,
3141} from 'types/services/push-service' ;
@@ -38,12 +48,19 @@ const MAX_FILE_SIZE_MB = 75;
3848const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024 ;
3949const MAX_RECURSION_DEPTH = 10 ;
4050
41- export const getSignedStorageUrl = async ( appVersionId : number , region ?: Region ) : Promise < string > => {
51+ export const getSignedStorageUrl = async (
52+ appVersionId : number ,
53+ region ?: Region ,
54+ securityScan ?: boolean ,
55+ ) : Promise < string > => {
4256 const DEBUG_TAG = 'get_signed_storage_url' ;
4357 try {
4458 const baseSignUrl = getDeploymentSignedUrl ( appVersionId ) ;
4559 const url = appsUrlBuilder ( baseSignUrl ) ;
46- const query = addRegionToQuery ( { } , region ) ;
60+ let query = addRegionToQuery ( { } , region ) ;
61+ if ( securityScan ) {
62+ query = { ...query , securityScan : true } ;
63+ }
4764
4865 const response = await execute < SignedUrl > (
4966 {
@@ -104,6 +121,31 @@ export const getAppVersionDeploymentStatus = async (appVersionId: number, region
104121 }
105122} ;
106123
124+ export const getDeploymentSecurityScan = async (
125+ appVersionId : number ,
126+ region ?: Region ,
127+ ) : Promise < SecurityScanResponse > => {
128+ try {
129+ const baseUrl = getDeploymentSecurityScanUrl ( appVersionId ) ;
130+ const url = appsUrlBuilder ( baseUrl ) ;
131+ const query = addRegionToQuery ( { } , region ) ;
132+
133+ const response = await execute < SecurityScanResponse > (
134+ {
135+ query,
136+ url,
137+ headers : { Accept : 'application/json' } ,
138+ method : HttpMethodTypes . GET ,
139+ } ,
140+ securityScanResponseSchema ,
141+ ) ;
142+ return response ;
143+ } catch ( error_ : any | HttpError ) {
144+ const error = error_ instanceof HttpError ? error_ : new Error ( 'Failed to fetch security scan results.' ) ;
145+ throw error ;
146+ }
147+ } ;
148+
107149export const pollForDeploymentStatus = async (
108150 appVersionId : number ,
109151 retryAfter : number ,
@@ -123,6 +165,7 @@ export const pollForDeploymentStatus = async (
123165 DeploymentStatusTypesSchema . building ,
124166 DeploymentStatusTypesSchema [ 'building-infra' ] ,
125167 DeploymentStatusTypesSchema [ 'building-app' ] ,
168+ DeploymentStatusTypesSchema [ 'security-scan' ] ,
126169 DeploymentStatusTypesSchema [ 'deploying-app' ] ,
127170 ] ;
128171 const response = await getAppVersionDeploymentStatus ( appVersionId , region ) ;
@@ -251,7 +294,7 @@ export const buildAssetToDeployTask = async (
251294
252295export const prepareEnvironmentTask = async ( ctx : PushCommandTasksContext ) => {
253296 try {
254- const signedCloudStorageUrl = await getSignedStorageUrl ( ctx . appVersionId , ctx . region ) ;
297+ const signedCloudStorageUrl = await getSignedStorageUrl ( ctx . appVersionId , ctx . region , ctx . securityScan ) ;
255298 const archiveContent = readFileData ( ctx . archivePath ! ) ;
256299 ctx . signedCloudStorageUrl = signedCloudStorageUrl ;
257300 ctx . archiveContent = archiveContent ;
@@ -288,6 +331,7 @@ const STATUS_TO_PROGRESS_VALUE: Record<keyof typeof DeploymentStatusTypesSchema,
288331 [ DeploymentStatusTypesSchema . building ] : PROGRESS_STEP * 10 ,
289332 [ DeploymentStatusTypesSchema [ 'building-infra' ] ] : PROGRESS_STEP * 25 ,
290333 [ DeploymentStatusTypesSchema [ 'building-app' ] ] : PROGRESS_STEP * 50 ,
334+ [ DeploymentStatusTypesSchema [ 'security-scan' ] ] : PROGRESS_STEP * 60 ,
291335 [ DeploymentStatusTypesSchema [ 'deploying-app' ] ] : PROGRESS_STEP * 75 ,
292336 [ DeploymentStatusTypesSchema . successful ] : PROGRESS_STEP * 100 ,
293337} ;
@@ -304,9 +348,20 @@ const setCustomTip = (tip?: string, color = 'green') => {
304348 return tip ? `\n ${ chalk . italic ( chalkColor ( tip ) ) } ` : '' ;
305349} ;
306350
351+ const writeSecurityScanResultsToDisk = ( securityScanResults : any , appVersionId : number ) : string => {
352+ const timestamp = new Date ( ) . toISOString ( ) . split ( '.' ) [ 0 ] . replaceAll ( ':' , '-' ) ;
353+ const fileName = `security-scan-${ appVersionId } -${ timestamp } .json` ;
354+ const filePath = path . join ( process . cwd ( ) , fileName ) ;
355+
356+ fs . writeFileSync ( filePath , JSON . stringify ( securityScanResults , null , 2 ) , 'utf8' ) ;
357+
358+ return filePath ;
359+ } ;
360+
307361const finalizeDeployment = (
308362 deploymentStatus : AppVersionDeploymentStatus ,
309363 task : ListrTaskWrapper < PushCommandTasksContext , any > ,
364+ ctx : PushCommandTasksContext ,
310365) => {
311366 switch ( deploymentStatus . status ) {
312367 case DeploymentStatusTypesSchema . failed : {
@@ -317,7 +372,23 @@ const finalizeDeployment = (
317372
318373 case DeploymentStatusTypesSchema . successful : {
319374 const deploymentUrl = `Deployment successfully finished, deployment url: ${ deploymentStatus . deployment ! . url } ` ;
320- task . title = deploymentUrl ;
375+
376+ if ( deploymentStatus . securityScanResults ) {
377+ const scanResultsPath = writeSecurityScanResultsToDisk ( deploymentStatus . securityScanResults , ctx . appVersionId ) ;
378+ ctx . securityScanResultsPath = scanResultsPath ;
379+
380+ const summary = deploymentStatus . securityScanResults . summary ;
381+ const errors = chalk . red ( `✖ ${ summary . error } errors` ) ;
382+ const warnings = chalk . yellow ( `▲ ${ summary . warning } warnings` ) ;
383+ const notes = chalk . cyan ( `ℹ ${ summary . note } info` ) ;
384+ const scanSummary = `Security scan completed with ${ summary . total } findings:\n ${ errors } \t${ warnings } \t${ notes } ` ;
385+ const downloadLink = `Results saved to: ${ scanResultsPath } ` ;
386+
387+ task . title = `${ scanSummary } \n${ downloadLink } \n${ deploymentUrl } ` ;
388+ } else {
389+ task . title = deploymentUrl ;
390+ }
391+
321392 break ;
322393 }
323394
@@ -348,5 +419,5 @@ export const handleDeploymentTask = async (
348419 } ,
349420 } ) ;
350421
351- finalizeDeployment ( deploymentStatus , task ) ;
422+ finalizeDeployment ( deploymentStatus , task , ctx ) ;
352423} ;
0 commit comments