@@ -5,6 +5,7 @@ import { getResourceDeclaration } from "../planner/declaration.ts";
55import { computeReplacementFingerprint , computeResourceHash } from "../planner/hasher.ts" ;
66import { buildReadinessBaseline } from "../planner/plan-semantics.ts" ;
77import { ApiError , ConflictError } from "../providers/base-client.ts" ;
8+ import { DeploymentCreateConflictError } from "../providers/deployment-conflict.ts" ;
89import { readComparableIfSupported } from "../providers/drift-support.ts" ;
910import type { RemoteResource } from "../providers/interface.ts" ;
1011import type { DriftReadAdapter , ResourceCrudAdapter } from "../providers/resource-workflow.ts" ;
@@ -597,16 +598,41 @@ async function executeActionInner(
597598 case "deployment" : {
598599 const decl = ctx . config . deployments ! [ name ] ! ;
599600 const refs = resolveDeploymentRefs ( name , ctx . config , address . provider , ctx . state ) ;
600- if ( isUpdate ) {
601- result = await provider . updateDeployment ( existingId ! , name , decl , refs , ctx . configPath ?? "" ) ;
602- } else {
601+ const hasLocalFileSources = decl . resources ?. some (
602+ ( resource ) => resource . type === "file" && ! resource . file_id && Boolean ( resource . source ) ,
603+ ) ;
604+ const materializeDeployment = async ( ) : Promise < RemoteResource > => {
603605 try {
604- result = await provider . createDeployment ( name , decl , refs , ctx . configPath ?? "" ) ;
606+ return await provider . createDeployment ( name , decl , refs , ctx . configPath ?? "" ) ;
605607 } catch ( err ) {
606- result = await adoptOnConflict ( err , address , provider , ctx . onFeedback , {
607- onExisting : ( existing ) => provider . updateDeployment ( existing . id ! , name , decl , refs , ctx . configPath ?? "" ) ,
608+ const preparedFiles = err instanceof DeploymentCreateConflictError ? err . preparedFiles : undefined ;
609+ const existing = await adoptOnConflict ( err , address , provider , ctx . onFeedback , {
610+ onExisting : ( existing ) =>
611+ provider . updateDeployment ( existing . id ! , name , decl , refs , ctx . configPath ?? "" , preparedFiles ) ,
612+ } ) ;
613+ adopted = true ;
614+ return existing ;
615+ }
616+ } ;
617+ if ( isUpdate && existingId ) {
618+ result = await provider . updateDeployment ( existingId , name , decl , refs , ctx . configPath ?? "" ) ;
619+ } else {
620+ // A deployment with local file sources uploads before its create request. If the
621+ // remote deployment already exists, an optimistic create would upload once,
622+ // conflict, then upload again during adoption. Preflight this side-effecting
623+ // path so the existing deployment is updated with a single set of uploads.
624+ const existing = hasLocalFileSources ? await findExistingByNames ( provider , "deployment" , [ name ] ) : null ;
625+ if ( existing ) {
626+ result = await provider . updateDeployment ( existing . resource . id ! , name , decl , refs , ctx . configPath ?? "" ) ;
627+ emitRuntimeFeedback ( ctx . onFeedback , {
628+ type : "resource_adopted" ,
629+ level : "info" ,
630+ resource : address ,
631+ message : `adopt deployment.${ name } (${ address . provider } ) — already existed remotely as "${ existing . name } "` ,
608632 } ) ;
609633 adopted = true ;
634+ } else {
635+ result = await materializeDeployment ( ) ;
610636 }
611637 }
612638 break ;
0 commit comments