@@ -373,41 +373,46 @@ const prepareProductionDeploy = async ({ api, siteData, options, command }) => {
373373 }
374374}
375375
376- // @ts -expect-error TS(7006) FIXME: Parameter 'actual' implicitly has an 'any' type.
377- const hasErrorMessage = ( actual , expected ) => {
376+ const hasErrorMessage = ( actual : unknown , expected : string ) : boolean => {
378377 if ( typeof actual === 'string' ) {
379378 return actual . includes ( expected )
380379 }
381380 return false
382381}
383382
384- // @ts -expect-error TS(7031) FIXME: Binding element 'error_' implicitly has an 'any' t... Remove this comment to see the full error message
385- const reportDeployError = ( { error_, failAndExit } ) => {
383+ interface DeployError extends Error {
384+ json ?: { message ?: string }
385+ status ?: unknown
386+ }
387+ const reportDeployError = ( {
388+ error,
389+ failAndExit,
390+ } : {
391+ error : DeployError
392+ failAndExit : ( err : unknown ) => never
393+ } ) : never => {
386394 switch ( true ) {
387- case error_ . name === 'JSONHTTPError' : {
388- const message = error_ ? .json ?. message ?? ''
395+ case error . name === 'JSONHTTPError' : {
396+ const message = error . json ?. message ?? ''
389397 if ( hasErrorMessage ( message , 'Background Functions not allowed by team plan' ) ) {
390398 return failAndExit ( `\n${ BACKGROUND_FUNCTIONS_WARNING } ` )
391399 }
392- warn ( `JSONHTTPError: ${ message } ${ error_ . status } ` )
393- warn ( `\n${ JSON . stringify ( error_ , null , ' ' ) } \n` )
394- failAndExit ( error_ )
395- return
400+ warn ( `JSONHTTPError: ${ message } ${ error . status } ` )
401+ warn ( `\n${ JSON . stringify ( error , null , ' ' ) } \n` )
402+ return failAndExit ( error )
396403 }
397- case error_ . name === 'TextHTTPError' : {
398- warn ( `TextHTTPError: ${ error_ . status } ` )
399- warn ( `\n${ error_ } \n` )
400- failAndExit ( error_ )
401- return
404+ case error . name === 'TextHTTPError' : {
405+ warn ( `TextHTTPError: ${ error . status } ` )
406+ warn ( `\n${ error } \n` )
407+ return failAndExit ( error )
402408 }
403- case hasErrorMessage ( error_ . message , 'Invalid filename' ) : {
404- warn ( error_ . message )
405- failAndExit ( error_ )
406- return
409+ case hasErrorMessage ( error . message , 'Invalid filename' ) : {
410+ warn ( error . message )
411+ return failAndExit ( error )
407412 }
408413 default : {
409- warn ( `\n${ JSON . stringify ( error_ , null , ' ' ) } \n` )
410- failAndExit ( error_ )
414+ warn ( `\n${ JSON . stringify ( error , null , ' ' ) } \n` )
415+ return failAndExit ( error )
411416 }
412417 }
413418}
@@ -531,9 +536,11 @@ const runDeploy = async ({
531536 skipFunctionsCache,
532537 // @ts -expect-error TS(7031) FIXME: Binding element 'title' implicitly has an 'any' ty... Remove this comment to see the full error message
533538 title,
539+ deployId : existingDeployId ,
534540} : {
535541 functionsFolder ?: string
536542 command : BaseCommand
543+ deployId ?: string
537544} ) : Promise < {
538545 siteId : string
539546 siteName : string
@@ -546,28 +553,35 @@ const runDeploy = async ({
546553 sourceZipFileName ?: string
547554} > => {
548555 let results
549- let deployId
556+ let deployId = existingDeployId
550557 let uploadSourceZipResult
551558
552559 try {
553- if ( deployToProduction ) {
554- await prepareProductionDeploy ( { siteData, api, options, command } )
555- }
556-
557- const draft = options . draft || ( ! deployToProduction && ! alias )
558- const createDeployBody = { draft, branch : alias , include_upload_url : options . uploadSourceZip }
559-
560- results = await api . createSiteDeploy ( { siteId, title, body : createDeployBody } )
561- deployId = results . id
560+ // We won't have a deploy ID if we run the command with `--no-build`.
561+ // In this case, we must create the deploy.
562+ if ( ! deployId ) {
563+ if ( deployToProduction ) {
564+ await prepareProductionDeploy ( { siteData, api, options, command } )
565+ }
562566
563- // Handle source zip upload if requested and URL provided
564- if ( options . uploadSourceZip && results . source_zip_upload_url && results . source_zip_filename ) {
565- uploadSourceZipResult = await uploadSourceZip ( {
566- sourceDir : site . root ,
567- uploadUrl : results . source_zip_upload_url ,
568- filename : results . source_zip_filename ,
569- statusCb : silent ? ( ) => { } : deployProgressCb ( ) ,
570- } )
567+ const draft = options . draft || ( ! deployToProduction && ! alias )
568+ const createDeployBody = { draft, branch : alias , include_upload_url : options . uploadSourceZip }
569+
570+ const createDeployResponse = await api . createSiteDeploy ( { siteId, title, body : createDeployBody } )
571+ deployId = createDeployResponse . id as string
572+
573+ if (
574+ options . uploadSourceZip &&
575+ createDeployResponse . source_zip_upload_url &&
576+ createDeployResponse . source_zip_filename
577+ ) {
578+ uploadSourceZipResult = await uploadSourceZip ( {
579+ sourceDir : site . root ,
580+ uploadUrl : createDeployResponse . source_zip_upload_url ,
581+ filename : createDeployResponse . source_zip_filename ,
582+ statusCb : silent ? ( ) => { } : deployProgressCb ( ) ,
583+ } )
584+ }
571585 }
572586
573587 const internalFunctionsFolder = await getInternalFunctionsDir ( { base : site . root , packagePath, ensureExists : true } )
@@ -628,11 +642,12 @@ const runDeploy = async ({
628642 skipFunctionsCache,
629643 siteRoot : site . root ,
630644 } )
631- } catch ( error_ ) {
645+ } catch ( error ) {
632646 if ( deployId ) {
633647 await cancelDeploy ( { api, deployId } )
634648 }
635- reportDeployError ( { error_, failAndExit : logAndThrowError } )
649+
650+ return reportDeployError ( { error : error as DeployError , failAndExit : logAndThrowError } )
636651 }
637652
638653 const siteUrl = results . deploy . ssl_url || results . deploy . url
@@ -690,7 +705,7 @@ const handleBuild = async ({
690705 } )
691706 const { configMutations, exitCode, newConfig, logs } = await runBuild ( resolvedOptions )
692707 // Without this, the deploy command fails silently
693- if ( options . json && exitCode !== 0 ) {
708+ if ( exitCode !== 0 ) {
694709 let message = ''
695710
696711 if ( options . verbose && logs ?. stdout . length ) {
@@ -703,9 +718,6 @@ const handleBuild = async ({
703718
704719 logAndThrowError ( `Error while running build${ message } ` )
705720 }
706- if ( exitCode !== 0 ) {
707- exit ( exitCode )
708- }
709721 return { newConfig, configMutations }
710722}
711723
@@ -849,10 +861,12 @@ const prepAndRunDeploy = async ({
849861 siteData,
850862 siteId,
851863 workingDir,
864+ deployId,
852865} : {
853866 options : DeployOptionValues
854867 command : BaseCommand
855868 workingDir : string
869+ deployId ?: string
856870 // eslint-disable-next-line @typescript-eslint/no-explicit-any -- FIXME(serhalp)
857871 [ key : string ] : any
858872} ) => {
@@ -933,6 +947,7 @@ const prepAndRunDeploy = async ({
933947 siteId,
934948 skipFunctionsCache : options . skipFunctionsCache ,
935949 title : options . message ,
950+ deployId,
936951 } )
937952
938953 return results
@@ -1080,29 +1095,75 @@ export const deploy = async (options: DeployOptionValues, command: BaseCommand)
10801095 let results = { } as Awaited < ReturnType < typeof prepAndRunDeploy > >
10811096
10821097 if ( options . build ) {
1083- const settings = await detectFrameworkSettings ( command , 'build' )
1084- await handleBuild ( {
1085- packagePath : command . workspacePackage ,
1086- cachedConfig : command . netlify . cachedConfig ,
1087- defaultConfig : getDefaultConfig ( settings ) ,
1088- currentDir : command . workingDir ,
1089- options,
1090- deployHandler : async ( { netlifyConfig } : { netlifyConfig : NetlifyConfig } ) => {
1091- results = await prepAndRunDeploy ( {
1092- command,
1093- options,
1094- workingDir,
1095- api,
1096- site,
1097- config : netlifyConfig ,
1098- siteData,
1099- siteId,
1100- deployToProduction,
1101- } )
1098+ if ( deployToProduction ) {
1099+ await prepareProductionDeploy ( { siteData, api, options, command } )
1100+ }
11021101
1103- return { newEnvChanges : { DEPLOY_ID : results . deployId , DEPLOY_URL : results . deployUrl } }
1104- } ,
1105- } )
1102+ const draft = options . draft || ( ! deployToProduction && ! alias )
1103+ const createDeployBody = { draft, branch : alias , include_upload_url : options . uploadSourceZip }
1104+
1105+ // TODO: Type this properly in `@netlify/api`.
1106+ const deployMetadata = ( await api . createSiteDeploy ( {
1107+ siteId,
1108+ title : options . message ,
1109+ body : createDeployBody ,
1110+ } ) ) as Awaited < ReturnType < typeof api . createSiteDeploy > > & {
1111+ source_zip_upload_url ?: string
1112+ source_zip_filename ?: string
1113+ }
1114+ const deployId = deployMetadata . id || ''
1115+ const deployUrl = deployMetadata . deploy_ssl_url || deployMetadata . deploy_url || ''
1116+
1117+ command . netlify . cachedConfig . env . DEPLOY_ID = { sources : [ 'internal' ] , value : deployId }
1118+ command . netlify . cachedConfig . env . DEPLOY_URL = { sources : [ 'internal' ] , value : deployUrl }
1119+
1120+ process . env . DEPLOY_ID = deployId
1121+ process . env . DEPLOY_URL = deployUrl
1122+
1123+ if (
1124+ options . uploadSourceZip &&
1125+ deployMetadata . source_zip_upload_url &&
1126+ deployMetadata . source_zip_filename &&
1127+ site . root
1128+ ) {
1129+ await uploadSourceZip ( {
1130+ sourceDir : site . root ,
1131+ uploadUrl : deployMetadata . source_zip_upload_url ,
1132+ filename : deployMetadata . source_zip_filename ,
1133+ statusCb : options . json || options . silent ? ( ) => { } : deployProgressCb ( ) ,
1134+ } )
1135+ }
1136+ try {
1137+ const settings = await detectFrameworkSettings ( command , 'build' )
1138+ await handleBuild ( {
1139+ packagePath : command . workspacePackage ,
1140+ cachedConfig : command . netlify . cachedConfig ,
1141+ defaultConfig : getDefaultConfig ( settings ) ,
1142+ currentDir : command . workingDir ,
1143+ options,
1144+ deployHandler : async ( { netlifyConfig } : { netlifyConfig : NetlifyConfig } ) => {
1145+ results = await prepAndRunDeploy ( {
1146+ command,
1147+ options,
1148+ workingDir,
1149+ api,
1150+ site,
1151+ config : netlifyConfig ,
1152+ siteData,
1153+ siteId,
1154+ deployToProduction,
1155+ deployId,
1156+ } )
1157+
1158+ return { }
1159+ } ,
1160+ } )
1161+ } catch ( error ) {
1162+ // The build has failed, so let's cancel the deploy we created.
1163+ await cancelDeploy ( { api, deployId } )
1164+
1165+ throw error
1166+ }
11061167 } else {
11071168 results = await prepAndRunDeploy ( {
11081169 command,
0 commit comments